diff --git a/src/Plugin/Field/FieldType/DynamicEntityReferenceFieldItemList.php b/src/Plugin/Field/FieldType/DynamicEntityReferenceFieldItemList.php index efe72a0..a80ad39 100644 --- a/src/Plugin/Field/FieldType/DynamicEntityReferenceFieldItemList.php +++ b/src/Plugin/Field/FieldType/DynamicEntityReferenceFieldItemList.php @@ -8,8 +8,10 @@ namespace Drupal\dynamic_entity_reference\Plugin\Field\FieldType; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Entity\Plugin\Validation\Constraint\ValidReferenceConstraint; use Drupal\Core\Field\EntityReferenceFieldItemList; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemList; use Drupal\Core\Form\FormStateInterface; /** @@ -22,6 +24,25 @@ class DynamicEntityReferenceFieldItemList extends EntityReferenceFieldItemList { /** * {@inheritdoc} */ + public function getConstraints() { + $constraints = parent::getConstraints(); + // Remove the 'ValidReferenceConstraint' validation constraint because + // dynamic entity reference fields already use the 'ValidDynamicReference' + // constraint. + foreach ($constraints as $key => $constraint) { + if ($constraint instanceof ValidReferenceConstraint) { + unset($constraints[$key]); + } + } + $constraints = array_values($constraints); + $constraint_manager = $this->getTypedDataManager()->getValidationConstraintManager(); + $constraints[] = $constraint_manager->create('ValidDynamicReference', []); + return $constraints; + } + + /** + * {@inheritdoc} + */ public function referencedEntities() { if (empty($this->list)) { return array(); diff --git a/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php b/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php index 0935377..bcb4343 100644 --- a/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php +++ b/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php @@ -39,8 +39,7 @@ use Drupal\dynamic_entity_reference\DataDynamicReferenceDefinition; * no_ui = FALSE, * list_class = "\Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceFieldItemList", * default_widget = "dynamic_entity_reference_default", - * default_formatter = "dynamic_entity_reference_label", - * constraints = {"ValidDynamicReference" = {}} + * default_formatter = "dynamic_entity_reference_label" * ) */ class DynamicEntityReferenceItem extends EntityReferenceItem { diff --git a/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraint.php b/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraint.php index 0a62850..78ebfb8 100644 --- a/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraint.php +++ b/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraint.php @@ -26,6 +26,27 @@ class ValidDynamicReferenceConstraint extends Constraint { * * @var string */ + public $message = 'This entity (%type: %id) cannot be referenced.'; + + /** + * Violation message when the entity does not exist. + * + * @var string + */ + public $nonExistingMessage = 'The referenced entity (%type: %id) does not exist.'; + + /** + * Violation message when a new entity ("autocreate") is invalid. + * + * @var string + */ + public $invalidAutocreateMessage = 'This entity (%type: %label) cannot be referenced.'; + + /** + * The default violation message. + * + * @var string + */ public $wrongIDMessage = 'The referenced entity (%type: %id) does not exist.'; public $wrongTypeMessage = 'The referenced entity type (%type) is not allowed for this field.'; public $wrongBundleMessage = 'The entity bundle must be one of %bundles, the selected entity is of bundle %bundle.'; diff --git a/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraintValidator.php b/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraintValidator.php index 2734763..0047611 100644 --- a/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraintValidator.php +++ b/src/Plugin/Validation/Constraint/ValidDynamicReferenceConstraintValidator.php @@ -21,22 +21,41 @@ class ValidDynamicReferenceConstraintValidator extends ConstraintValidator { */ public function validate($value, Constraint $constraint) { /* @var \Drupal\dynamic_entity_reference\Plugin\Validation\Constraint\ValidDynamicReferenceConstraint $constraint */ - /* @var \Drupal\Core\Field\FieldItemInterface $value */ + /* @var \Drupal\Core\Field\FieldItemListInterface $value */ if (!isset($value)) { return; } - // We don't use a regular NotNull constraint for the target_id property as - // a NULL value is valid if the entity property contains an unsaved entity. - // @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints - if (!$value->isEmpty() && $value->target_id === NULL && !$value->entity->isNew()) { - $this->context->addViolation($constraint->nullMessage, ['%property' => 'target_id']); - return; + + // Collect new entities and IDs of existing entities across the field items. + $new_entities = []; + $target_ids = []; + $target_types = DynamicEntityReferenceItem::getTargetTypes($value->getFieldDefinition()->getSettings()); + foreach ($value as $delta => $item) { + $target_id = $item->target_id; + $target_type = $item->target_type; + // 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() && ($target_id === NULL || $target_type === NULL)) { + $property = $target_id ? $target_type : $target_id; + if (!$item->entity->isNew()) { + $this->context->buildViolation($constraint->nullMessage) + ->setParameter('%property', $property) + ->atPath((string) $delta . $property) + ->addViolation(); + return; + } + $new_entities[$delta] = $item->entity; + } + $valid_type = empty($target_type) || (!empty($target_type) && in_array($target_type, $target_types)); + // '0' or NULL are considered valid empty references. + if (!empty($target_id)) { + $target_ids[$delta] = $target_id; + } } - // We don't use a regular NotNull constraint for the target_id property as - // a NULL value is valid if the entity property contains an unsaved entity. - // @see \Drupal\Core\TypedData\DataReferenceTargetDefinition::getConstraints - if (!$value->isEmpty() && $value->target_type === NULL && !$value->entity->isNew()) { - $this->context->addViolation($constraint->nullMessage, ['%property' => 'target_type']); + + // Early opt-out if nothing to validate. + if (!$new_entities && !$target_ids) { return; } $id = $value->get('target_id')->getValue(); @@ -70,31 +89,5 @@ class ValidDynamicReferenceConstraintValidator extends ConstraintValidator { } } - /** - * {@inheritdoc} - * - * The field-item list and the field-item refer validation of bundles to the - * entity property, as depending on the selected target-type, the bundle may - * need validation. - */ - public function getConstraints() { - /* @var \Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceFieldItemList $field */ - $field = $this->parent->parent; - $values = $this->parent->getValue(); - // We cannot validate the bundle until we know the target-type of this - // item. - if (isset($values['target_type'])) { - $target_type = $values['target_type']; - // Get the handler settings for the target type of this item. - $target_type_settings = $field->getSetting($target_type); - if (isset($target_type_settings['handler_settings']['target_bundles'])) { - // Add the constraint to limit the bundles of this item. - $this->definition->addConstraint('Bundle', ['bundle' => $target_type_settings['handler_settings']['target_bundles']]); - } - } - $constraints = parent::getConstraints(); - return $constraints; - } - } diff --git a/src/Tests/DynamicEntityReferenceFieldTest.php b/src/Tests/DynamicEntityReferenceFieldTest.php index d3d3af0..583750e 100644 --- a/src/Tests/DynamicEntityReferenceFieldTest.php +++ b/src/Tests/DynamicEntityReferenceFieldTest.php @@ -19,6 +19,7 @@ use Drupal\system\Tests\Entity\EntityUnitTestBase; * @group dynamic_entity_reference */ class DynamicEntityReferenceFieldTest extends EntityUnitTestBase { + use SchemaCheckTestTrait; /**