<?php
namespace MailPoetVendor\Symfony\Component\Validator\Validator;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Validator\Constraint;
use MailPoetVendor\Symfony\Component\Validator\Constraints\Composite;
use MailPoetVendor\Symfony\Component\Validator\Constraints\Existence;
use MailPoetVendor\Symfony\Component\Validator\Constraints\GroupSequence;
use MailPoetVendor\Symfony\Component\Validator\Constraints\Valid;
use MailPoetVendor\Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use MailPoetVendor\Symfony\Component\Validator\Context\ExecutionContext;
use MailPoetVendor\Symfony\Component\Validator\Context\ExecutionContextInterface;
use MailPoetVendor\Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use MailPoetVendor\Symfony\Component\Validator\Exception\NoSuchMetadataException;
use MailPoetVendor\Symfony\Component\Validator\Exception\RuntimeException;
use MailPoetVendor\Symfony\Component\Validator\Exception\UnexpectedValueException;
use MailPoetVendor\Symfony\Component\Validator\Exception\UnsupportedMetadataException;
use MailPoetVendor\Symfony\Component\Validator\Exception\ValidatorException;
use MailPoetVendor\Symfony\Component\Validator\Mapping\CascadingStrategy;
use MailPoetVendor\Symfony\Component\Validator\Mapping\ClassMetadataInterface;
use MailPoetVendor\Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
use MailPoetVendor\Symfony\Component\Validator\Mapping\GenericMetadata;
use MailPoetVendor\Symfony\Component\Validator\Mapping\GetterMetadata;
use MailPoetVendor\Symfony\Component\Validator\Mapping\MetadataInterface;
use MailPoetVendor\Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use MailPoetVendor\Symfony\Component\Validator\Mapping\TraversalStrategy;
use MailPoetVendor\Symfony\Component\Validator\ObjectInitializerInterface;
use MailPoetVendor\Symfony\Component\Validator\Util\PropertyPath;
class RecursiveContextualValidator implements ContextualValidatorInterface
{
 private $context;
 private $defaultPropertyPath;
 private $defaultGroups;
 private $metadataFactory;
 private $validatorFactory;
 private $objectInitializers;
 public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = [])
 {
 $this->context = $context;
 $this->defaultPropertyPath = $context->getPropertyPath();
 $this->defaultGroups = [$context->getGroup() ?: Constraint::DEFAULT_GROUP];
 $this->metadataFactory = $metadataFactory;
 $this->validatorFactory = $validatorFactory;
 $this->objectInitializers = $objectInitializers;
 }
 public function atPath(string $path)
 {
 $this->defaultPropertyPath = $this->context->getPropertyPath($path);
 return $this;
 }
 public function validate($value, $constraints = null, $groups = null)
 {
 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
 $previousValue = $this->context->getValue();
 $previousObject = $this->context->getObject();
 $previousMetadata = $this->context->getMetadata();
 $previousPath = $this->context->getPropertyPath();
 $previousGroup = $this->context->getGroup();
 $previousConstraint = null;
 if ($this->context instanceof ExecutionContext || \method_exists($this->context, 'getConstraint')) {
 $previousConstraint = $this->context->getConstraint();
 }
 // If explicit constraints are passed, validate the value against
 // those constraints
 if (null !== $constraints) {
 // You can pass a single constraint or an array of constraints
 // Make sure to deal with an array in the rest of the code
 if (!\is_array($constraints)) {
 $constraints = [$constraints];
 }
 $metadata = new GenericMetadata();
 $metadata->addConstraints($constraints);
 $this->validateGenericNode($value, $previousObject, \is_object($value) ? $this->generateCacheKey($value) : null, $metadata, $this->defaultPropertyPath, $groups, null, TraversalStrategy::IMPLICIT, $this->context);
 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
 $this->context->setGroup($previousGroup);
 if (null !== $previousConstraint) {
 $this->context->setConstraint($previousConstraint);
 }
 return $this;
 }
 // If an object is passed without explicit constraints, validate that
 // object against the constraints defined for the object's class
 if (\is_object($value)) {
 $this->validateObject($value, $this->defaultPropertyPath, $groups, TraversalStrategy::IMPLICIT, $this->context);
 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
 $this->context->setGroup($previousGroup);
 return $this;
 }
 // If an array is passed without explicit constraints, validate each
 // object in the array
 if (\is_array($value)) {
 $this->validateEachObjectIn($value, $this->defaultPropertyPath, $groups, $this->context);
 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
 $this->context->setGroup($previousGroup);
 return $this;
 }
 throw new RuntimeException(\sprintf('Cannot validate values of type "%s" automatically. Please provide a constraint.', \get_debug_type($value)));
 }
 public function validateProperty(object $object, string $propertyName, $groups = null)
 {
 $classMetadata = $this->metadataFactory->getMetadataFor($object);
 if (!$classMetadata instanceof ClassMetadataInterface) {
 throw new ValidatorException(\sprintf('The metadata factory should return instances of "\\Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface", got: "%s".', \get_debug_type($classMetadata)));
 }
 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
 $cacheKey = $this->generateCacheKey($object);
 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
 $previousValue = $this->context->getValue();
 $previousObject = $this->context->getObject();
 $previousMetadata = $this->context->getMetadata();
 $previousPath = $this->context->getPropertyPath();
 $previousGroup = $this->context->getGroup();
 foreach ($propertyMetadatas as $propertyMetadata) {
 $propertyValue = $propertyMetadata->getPropertyValue($object);
 $this->validateGenericNode($propertyValue, $object, $cacheKey . ':' . \get_class($object) . ':' . $propertyName, $propertyMetadata, $propertyPath, $groups, null, TraversalStrategy::IMPLICIT, $this->context);
 }
 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
 $this->context->setGroup($previousGroup);
 return $this;
 }
 public function validatePropertyValue($objectOrClass, string $propertyName, $value, $groups = null)
 {
 $classMetadata = $this->metadataFactory->getMetadataFor($objectOrClass);
 if (!$classMetadata instanceof ClassMetadataInterface) {
 throw new ValidatorException(\sprintf('The metadata factory should return instances of "\\Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface", got: "%s".', \get_debug_type($classMetadata)));
 }
 $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
 $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
 if (\is_object($objectOrClass)) {
 $object = $objectOrClass;
 $class = \get_class($object);
 $cacheKey = $this->generateCacheKey($objectOrClass);
 $propertyPath = PropertyPath::append($this->defaultPropertyPath, $propertyName);
 } else {
 // $objectOrClass contains a class ?name
 $object = null;
 $class = $objectOrClass;
 $cacheKey = null;
 $propertyPath = $this->defaultPropertyPath;
 }
 $previousValue = $this->context->getValue();
 $previousObject = $this->context->getObject();
 $previousMetadata = $this->context->getMetadata();
 $previousPath = $this->context->getPropertyPath();
 $previousGroup = $this->context->getGroup();
 foreach ($propertyMetadatas as $propertyMetadata) {
 $this->validateGenericNode($value, $object, $cacheKey . ':' . $class . ':' . $propertyName, $propertyMetadata, $propertyPath, $groups, null, TraversalStrategy::IMPLICIT, $this->context);
 }
 $this->context->setNode($previousValue, $previousObject, $previousMetadata, $previousPath);
 $this->context->setGroup($previousGroup);
 return $this;
 }
 public function getViolations()
 {
 return $this->context->getViolations();
 }
 protected function normalizeGroups($groups)
 {
 if (\is_array($groups)) {
 return $groups;
 }
 return [$groups];
 }
 private function validateObject(object $object, string $propertyPath, array $groups, int $traversalStrategy, ExecutionContextInterface $context)
 {
 try {
 $classMetadata = $this->metadataFactory->getMetadataFor($object);
 if (!$classMetadata instanceof ClassMetadataInterface) {
 throw new UnsupportedMetadataException(\sprintf('The metadata factory should return instances of "Symfony\\Component\\Validator\\Mapping\\ClassMetadataInterface", got: "%s".', \get_debug_type($classMetadata)));
 }
 $this->validateClassNode($object, $this->generateCacheKey($object), $classMetadata, $propertyPath, $groups, null, $traversalStrategy, $context);
 } catch (NoSuchMetadataException $e) {
 // Rethrow if not Traversable
 if (!$object instanceof \Traversable) {
 throw $e;
 }
 // Rethrow unless IMPLICIT or TRAVERSE
 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
 throw $e;
 }
 $this->validateEachObjectIn($object, $propertyPath, $groups, $context);
 }
 }
 private function validateEachObjectIn(iterable $collection, string $propertyPath, array $groups, ExecutionContextInterface $context)
 {
 foreach ($collection as $key => $value) {
 if (\is_array($value)) {
 // Also traverse nested arrays
 $this->validateEachObjectIn($value, $propertyPath . '[' . $key . ']', $groups, $context);
 continue;
 }
 // Scalar and null values in the collection are ignored
 if (\is_object($value)) {
 $this->validateObject($value, $propertyPath . '[' . $key . ']', $groups, TraversalStrategy::IMPLICIT, $context);
 }
 }
 }
 private function validateClassNode(object $object, ?string $cacheKey, ClassMetadataInterface $metadata, string $propertyPath, array $groups, ?array $cascadedGroups, int $traversalStrategy, ExecutionContextInterface $context)
 {
 $context->setNode($object, $object, $metadata, $propertyPath);
 if (!$context->isObjectInitialized($cacheKey)) {
 foreach ($this->objectInitializers as $initializer) {
 $initializer->initialize($object);
 }
 $context->markObjectAsInitialized($cacheKey);
 }
 foreach ($groups as $key => $group) {
 // If the "Default" group is replaced by a group sequence, remember
 // to cascade the "Default" group when traversing the group
 // sequence
 $defaultOverridden = \false;
 // Use the object hash for group sequences
 $groupHash = \is_object($group) ? $this->generateCacheKey($group, \true) : $group;
 if ($context->isGroupValidated($cacheKey, $groupHash)) {
 // Skip this group when validating the properties and when
 // traversing the object
 unset($groups[$key]);
 continue;
 }
 $context->markGroupAsValidated($cacheKey, $groupHash);
 // Replace the "Default" group by the group sequence defined
 // for the class, if applicable.
 // This is done after checking the cache, so that
 // spl_object_hash() isn't called for this sequence and
 // "Default" is used instead in the cache. This is useful
 // if the getters below return different group sequences in
 // every call.
 if (Constraint::DEFAULT_GROUP === $group) {
 if ($metadata->hasGroupSequence()) {
 // The group sequence is statically defined for the class
 $group = $metadata->getGroupSequence();
 $defaultOverridden = \true;
 } elseif ($metadata->isGroupSequenceProvider()) {
 // The group sequence is dynamically obtained from the validated
 // object
 $group = $object->getGroupSequence();
 $defaultOverridden = \true;
 if (!$group instanceof GroupSequence) {
 $group = new GroupSequence($group);
 }
 }
 }
 // If the groups (=[<G1,G2>,G3,G4]) contain a group sequence
 // (=<G1,G2>), then call validateClassNode() with each entry of the
 // group sequence and abort if necessary (G1, G2)
 if ($group instanceof GroupSequence) {
 $this->stepThroughGroupSequence($object, $object, $cacheKey, $metadata, $propertyPath, $traversalStrategy, $group, $defaultOverridden ? Constraint::DEFAULT_GROUP : null, $context);
 // Skip the group sequence when validating properties, because
 // stepThroughGroupSequence() already validates the properties
 unset($groups[$key]);
 continue;
 }
 $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
 }
 // If no more groups should be validated for the property nodes,
 // we can safely quit
 if (0 === \count($groups)) {
 return;
 }
 // Validate all properties against their constraints
 foreach ($metadata->getConstrainedProperties() as $propertyName) {
 // If constraints are defined both on the getter of a property as
 // well as on the property itself, then getPropertyMetadata()
 // returns two metadata objects, not just one
 foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
 if (!$propertyMetadata instanceof PropertyMetadataInterface) {
 throw new UnsupportedMetadataException(\sprintf('The property metadata instances should implement "Symfony\\Component\\Validator\\Mapping\\PropertyMetadataInterface", got: "%s".', \get_debug_type($propertyMetadata)));
 }
 if ($propertyMetadata instanceof GetterMetadata) {
 $propertyValue = new LazyProperty(static function () use($propertyMetadata, $object) {
 return $propertyMetadata->getPropertyValue($object);
 });
 } else {
 $propertyValue = $propertyMetadata->getPropertyValue($object);
 }
 $this->validateGenericNode($propertyValue, $object, $cacheKey . ':' . \get_class($object) . ':' . $propertyName, $propertyMetadata, PropertyPath::append($propertyPath, $propertyName), $groups, $cascadedGroups, TraversalStrategy::IMPLICIT, $context);
 }
 }
 // If no specific traversal strategy was requested when this method
 // was called, use the traversal strategy of the class' metadata
 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
 $traversalStrategy = $metadata->getTraversalStrategy();
 }
 // Traverse only if IMPLICIT or TRAVERSE
 if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
 return;
 }
 // If IMPLICIT, stop unless we deal with a Traversable
 if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) {
 return;
 }
 // If TRAVERSE, fail if we have no Traversable
 if (!$object instanceof \Traversable) {
 throw new ConstraintDefinitionException(\sprintf('Traversal was enabled for "%s", but this class does not implement "\\Traversable".', \get_debug_type($object)));
 }
 $this->validateEachObjectIn($object, $propertyPath, $groups, $context);
 }
 private function validateGenericNode($value, ?object $object, ?string $cacheKey, ?MetadataInterface $metadata, string $propertyPath, array $groups, ?array $cascadedGroups, int $traversalStrategy, ExecutionContextInterface $context)
 {
 $context->setNode($value, $object, $metadata, $propertyPath);
 foreach ($groups as $key => $group) {
 if ($group instanceof GroupSequence) {
 $this->stepThroughGroupSequence($value, $object, $cacheKey, $metadata, $propertyPath, $traversalStrategy, $group, null, $context);
 // Skip the group sequence when cascading, as the cascading
 // logic is already done in stepThroughGroupSequence()
 unset($groups[$key]);
 continue;
 }
 $this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
 }
 if (0 === \count($groups)) {
 return;
 }
 if (null === $value) {
 return;
 }
 $cascadingStrategy = $metadata->getCascadingStrategy();
 // Quit unless we cascade
 if (!($cascadingStrategy & CascadingStrategy::CASCADE)) {
 return;
 }
 // If no specific traversal strategy was requested when this method
 // was called, use the traversal strategy of the node's metadata
 if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
 $traversalStrategy = $metadata->getTraversalStrategy();
 }
 // The $cascadedGroups property is set, if the "Default" group is
 // overridden by a group sequence
 // See validateClassNode()
 $cascadedGroups = null !== $cascadedGroups && \count($cascadedGroups) > 0 ? $cascadedGroups : $groups;
 if ($value instanceof LazyProperty) {
 $value = $value->getPropertyValue();
 if (null === $value) {
 return;
 }
 }
 if (\is_array($value)) {
 // Arrays are always traversed, independent of the specified
 // traversal strategy
 $this->validateEachObjectIn($value, $propertyPath, $cascadedGroups, $context);
 return;
 }
 if (!\is_object($value)) {
 throw new NoSuchMetadataException(\sprintf('Cannot create metadata for non-objects. Got: "%s".', \gettype($value)));
 }
 $this->validateObject($value, $propertyPath, $cascadedGroups, $traversalStrategy, $context);
 // Currently, the traversal strategy can only be TRAVERSE for a
 // generic node if the cascading strategy is CASCADE. Thus, traversable
 // objects will always be handled within validateObject() and there's
 // nothing more to do here.
 // see GenericMetadata::addConstraint()
 }
 private function stepThroughGroupSequence($value, ?object $object, ?string $cacheKey, ?MetadataInterface $metadata, string $propertyPath, int $traversalStrategy, GroupSequence $groupSequence, ?string $cascadedGroup, ExecutionContextInterface $context)
 {
 $violationCount = \count($context->getViolations());
 $cascadedGroups = $cascadedGroup ? [$cascadedGroup] : null;
 foreach ($groupSequence->groups as $groupInSequence) {
 $groups = (array) $groupInSequence;
 if ($metadata instanceof ClassMetadataInterface) {
 $this->validateClassNode($value, $cacheKey, $metadata, $propertyPath, $groups, $cascadedGroups, $traversalStrategy, $context);
 } else {
 $this->validateGenericNode($value, $object, $cacheKey, $metadata, $propertyPath, $groups, $cascadedGroups, $traversalStrategy, $context);
 }
 // Abort sequence validation if a violation was generated
 if (\count($context->getViolations()) > $violationCount) {
 break;
 }
 }
 }
 private function validateInGroup($value, ?string $cacheKey, MetadataInterface $metadata, string $group, ExecutionContextInterface $context)
 {
 $context->setGroup($group);
 foreach ($metadata->findConstraints($group) as $constraint) {
 if ($constraint instanceof Existence) {
 continue;
 }
 // Prevent duplicate validation of constraints, in the case
 // that constraints belong to multiple validated groups
 if (null !== $cacheKey) {
 $constraintHash = $this->generateCacheKey($constraint, \true);
 // instanceof Valid: In case of using a Valid constraint with many groups
 // it makes a reference object get validated by each group
 if ($constraint instanceof Composite || $constraint instanceof Valid) {
 $constraintHash .= $group;
 }
 if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
 continue;
 }
 $context->markConstraintAsValidated($cacheKey, $constraintHash);
 }
 $context->setConstraint($constraint);
 $validator = $this->validatorFactory->getInstance($constraint);
 $validator->initialize($context);
 if ($value instanceof LazyProperty) {
 $value = $value->getPropertyValue();
 }
 try {
 $validator->validate($value, $constraint);
 } catch (UnexpectedValueException $e) {
 $context->buildViolation('This value should be of type {{ type }}.')->setParameter('{{ type }}', $e->getExpectedType())->addViolation();
 }
 }
 }
 private function generateCacheKey(object $object, bool $dependsOnPropertyPath = \false) : string
 {
 if ($this->context instanceof ExecutionContext) {
 $cacheKey = $this->context->generateCacheKey($object);
 } else {
 $cacheKey = \spl_object_hash($object);
 }
 if ($dependsOnPropertyPath) {
 $cacheKey .= $this->context->getPropertyPath();
 }
 return $cacheKey;
 }
}
