You are here

ValidDynamicReferenceConstraintValidator.php in Dynamic Entity Reference 8

File

src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraintValidator.php
View source
<?php

namespace Drupal\dynamic_entity_reference\Plugin\Validation\Constraint;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionWithAutocreateInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Checks if referenced entities are valid.
 */
class ValidDynamicReferenceConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {

  /**
   * The selection plugin manager.
   *
   * @var \Drupal\dynamic_entity_reference\SelectionPluginManager
   */
  protected $selectionManager;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a ValidReferenceConstraintValidator object.
   *
   * @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager
   *   The selection plugin manager.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(SelectionPluginManagerInterface $selection_manager, EntityTypeManagerInterface $entity_type_manager) {
    $this->selectionManager = $selection_manager;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container
      ->get('plugin.manager.dynamic_entity_reference_selection'), $container
      ->get('entity_type.manager'));
  }

  /**
   * {@inheritdoc}
   */
  public function validate($value, Constraint $constraint) {

    /** @var \Drupal\dynamic_entity_reference\Plugin\Validation\Constraint\ValidDynamicReferenceConstraint $constraint */

    /** @var \Drupal\Core\Field\FieldItemListInterface $value */
    if (!isset($value)) {
      return;
    }

    // Collect new entities, IDs and target types of existing entities across
    // the field items.
    $new_entities = [];
    $target_ids = [];
    $target_types = [];
    $valid_target_types = DynamicEntityReferenceItem::getTargetTypes($value
      ->getFieldDefinition()
      ->getSettings());
    foreach ($value as $delta => $item) {

      // We don't use a regular NotNull constraint for the target_id property as
      // NULL is allowed if the entity property contains an unsaved entity.
      // @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints()
      if (!$item
        ->isEmpty() && ($item->target_id === NULL || $item->target_type === NULL)) {
        $property = $item->target_id ? 'target_type' : 'target_id';
        if (!$item->entity
          ->isNew()) {
          $this->context
            ->buildViolation($constraint->nullMessage)
            ->setParameter('%property', $property)
            ->atPath((string) $delta . '.' . $property)
            ->addViolation();
          continue;
        }
        $new_entities[$delta] = $item->entity;
      }

      // Validate target type.
      $valid_type = empty($item->target_type) || !empty($item->target_type) && DynamicEntityReferenceItem::entityHasIntegerId($item->target_type) && in_array($item->target_type, $valid_target_types);
      if (!$valid_type) {

        // Remove new entity if the target type is not valid.
        if (isset($new_entities[$delta])) {
          unset($new_entities[$delta]);
        }
        $this->context
          ->buildViolation($constraint->wrongTypeMessage)
          ->setParameter('%type', $item->target_type)
          ->atPath((string) $delta . '.target_type')
          ->setInvalidValue($item->target_type)
          ->addViolation();
        continue;
      }

      // '0' or NULL are considered valid empty references.
      if (!empty($item->target_id)) {
        $target_ids[$delta] = $item->target_id;
      }
      $target_types[$delta] = $item->target_type;
    }

    // Collect all new and created valid entities fot bundle validation.
    $valid_entities = [];
    $entity = !empty($value
      ->getParent()) ? $value
      ->getEntity() : NULL;

    // Validate new entities.
    foreach ($new_entities as $delta => $new_entity) {

      /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler */
      $handler = $this->selectionManager
        ->getSelectionHandler($value
        ->getFieldDefinition(), $entity, $target_types[$delta]);
      if ($handler instanceof SelectionWithAutocreateInterface && ($new_entity
        ->getEntityTypeId() == $target_types[$delta] || empty($target_types[$delta]))) {
        if (!$handler
          ->validateReferenceableNewEntities([
          $new_entity,
        ])) {
          $this->context
            ->buildViolation($constraint->invalidAutocreateMessage)
            ->setParameter('%type', $new_entity
            ->getEntityTypeId())
            ->setParameter('%label', $new_entity
            ->label())
            ->atPath((string) $delta . '.entity')
            ->setInvalidValue($new_entity)
            ->addViolation();
          continue;
        }
      }
      $valid_entities[$delta] = $new_entity;
    }

    // Validate id for non existent entities.
    foreach ($target_ids as $delta => $target_id) {
      $definition = $this->entityTypeManager
        ->getDefinition($target_types[$delta]);
      $existing_id = $this->entityTypeManager
        ->getStorage($target_types[$delta])
        ->getQuery()
        ->accessCheck(FALSE)
        ->condition($definition
        ->getKey('id'), $target_id)
        ->execute();
      if (!$existing_id) {
        $this->context
          ->buildViolation($constraint->nonExistingMessage)
          ->setParameter('%type', $target_types[$delta])
          ->setParameter('%id', $target_id)
          ->atPath((string) $delta . '.target_id')
          ->setInvalidValue($target_id)
          ->addViolation();
        unset($target_ids[$delta]);
        continue;
      }
      $valid_entities[$delta] = $this->entityTypeManager
        ->getStorage($target_types[$delta])
        ->load($target_id);
    }

    // Validate bundles.
    foreach ($valid_entities as $delta => $entity) {
      if (array_key_exists('target_bundles', $value
        ->getFieldDefinition()
        ->getSetting($target_types[$delta])['handler_settings'])) {
        $valid_bundle = FALSE;
        $target_bundles = $value
          ->getFieldDefinition()
          ->getSetting($target_types[$delta])['handler_settings']['target_bundles'];
        if ($target_bundles === []) {
          $this->context
            ->buildViolation($constraint->noBundleAllowed)
            ->setParameter('%type', $target_types[$delta])
            ->atPath((string) $delta . '.entity')
            ->setInvalidValue($entity)
            ->addViolation();
          if (isset($target_ids[$delta])) {
            unset($target_ids[$delta]);
          }
          continue;
        }
        if ($target_bundles === NULL || in_array($entity
          ->bundle(), $target_bundles)) {
          $valid_bundle = TRUE;
        }
        if (!$valid_bundle) {
          $this->context
            ->buildViolation($constraint->wrongBundleMessage)
            ->setParameter('%label', $entity
            ->label())
            ->setParameter('%bundles', implode(', ', $target_bundles))
            ->atPath((string) $delta . '.entity')
            ->setInvalidValue($entity)
            ->addViolation();
          if (isset($target_ids[$delta])) {
            unset($target_ids[$delta]);
          }
        }
      }
    }

    // Validate target ids.
    foreach ($target_ids as $delta => $id) {
      $handler = $this->selectionManager
        ->getSelectionHandler($value
        ->getFieldDefinition(), $entity, $target_types[$delta]);
      $valid_target_id = $handler
        ->validateReferenceableEntities([
        $id,
      ]);
      if (!$valid_target_id) {
        $this->context
          ->buildViolation($constraint->message)
          ->setParameter('%type', $target_types[$delta])
          ->setParameter('%id', $id)
          ->atPath((string) $delta . '.target_id')
          ->setInvalidValue($id)
          ->addViolation();
      }
    }
  }

}

Classes

Namesort descending Description
ValidDynamicReferenceConstraintValidator Checks if referenced entities are valid.