diff --git a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php index 864e717..f240c19 100644 --- a/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityReferenceSelection/SelectionInterface.php @@ -49,6 +49,20 @@ public function countReferenceableEntities($match = NULL, $match_operator = 'CON public function validateReferenceableEntities(array $ids); /** + * Validates that entities can be referenced by this field. + * + * @todo naming... + * + * @param \Drupal\Core\Entity\EntityInterface[] $entities + * An array of entities to check. + * + * @return \Drupal\Core\Entity\EntityInterface[] + * The incoming $entities parameter, filtered for valid entities. Array keys + * are preserved. + */ + public function validateReferenceableNewEntities(array $entities); + + /** * Validates input from an autocomplete widget that has no ID. * * @param string $input diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php index c67dc68..7522d86 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/SelectionBase.php @@ -254,12 +254,12 @@ public function countReferenceableEntities($match = NULL, $match_operator = 'CON /** * {@inheritdoc} */ - public function validateReferenceableEntities(array $ids) { + public function validateReferenceableEntities(array $ids, $strict = TRUE) { $result = array(); if ($ids) { $target_type = $this->configuration['target_type']; $entity_type = $this->entityManager->getDefinition($target_type); - $query = $this->buildEntityQuery(); + $query = $this->buildEntityQuery(NULL, 'CONTAINS', $strict); $result = $query ->condition($entity_type->getKey('id'), $ids, 'IN') ->execute(); @@ -271,6 +271,18 @@ public function validateReferenceableEntities(array $ids) { /** * {@inheritdoc} */ + public function validateReferenceableNewEntities(array $entities) { + return array_filter($entities, function ($entity) { + if (isset($this->configuration['handler_settings']['target_bundles'])) { + return in_array($entity->bundle(), $this->configuration['handler_settings']['target_bundles']); + } + return TRUE; + }); + } + + /** + * {@inheritdoc} + */ public function validateAutocompleteInput($input, &$element, FormStateInterface $form_state, $form, $strict = TRUE) { $bundled_entities = $this->getReferenceableEntities($input, '=', 6); $entities = array(); diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraint.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraint.php index bf8658a..79cf120 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraint.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraint.php @@ -26,7 +26,7 @@ class ValidReferenceConstraint extends Constraint { * * @var string */ - public $message = 'The referenced entity (%type: %id) does not exist.'; + public $message = 'This entity (%type: %id) cannot be referenced.'; /** * Validation message when the target_id is empty. diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php index 8f704c1..557ebb0 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php +++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/ValidReferenceConstraintValidator.php @@ -19,27 +19,68 @@ class ValidReferenceConstraintValidator extends ConstraintValidator { * {@inheritdoc} */ public function validate($value, Constraint $constraint) { - /** @var \Drupal\Core\Field\FieldItemInterface $value */ + /** @var \Drupal\Core\Field\FieldItemListInterface $value */ /** @var ValidReferenceConstraint $constraint */ 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); - return; + + // Collect new entities and IDs of existing entities across the field items. + $new_entities = []; + $target_ids = []; + foreach ($value as $delta => $item) { + $target_id = $item->target_id; + // 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) { + if (!$item->entity->isNew()) { + $this->context->buildViolation($constraint->nullMessage) + ->atPath((string) $delta) + ->addViolation(); + return; + } + $new_entities[$delta] = $item->entity; + } + + // '0' or NULL are considered valid empty references. + if (!empty($target_id)) { + $target_ids[$delta] = $target_id; + } } - $id = $value->get('target_id')->getValue(); - // '0' or NULL are considered valid empty references. - if (empty($id)) { - return; - } - $referenced_entity = $value->get('entity')->getValue(); - if (!$referenced_entity) { - $type = $value->getFieldDefinition()->getSetting('target_type'); - $this->context->addViolation($constraint->message, array('%type' => $type, '%id' => $id)); + + if ($new_entities || $target_ids) { + /** @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface $handler * */ + $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($value->getFieldDefinition()); + $target_type = $value->getFieldDefinition()->getSetting('target_type'); + + // Add violations on deltas with a new entity that is not valid. + if ($new_entities) { + $valid_new_entities = $handler->validateReferenceableNewEntities($new_entities); + foreach (array_diff_key($new_entities, $valid_new_entities) as $delta => $entity) { + // @todo adjust message + $this->context->buildViolation('Invalid new entity') + ->setParameter('%type', $target_type) + ->atPath((string) $delta . '.entity') + ->setInvalidValue($entity) + ->addViolation(); + } + } + + // Add violations on deltas with a target_id that is not valid. + if ($target_ids) { + $valid_target_ids = $handler->validateReferenceableEntities($target_ids, FALSE); + foreach (array_diff($target_ids, $valid_target_ids) as $delta => $target_id) { + $this->context->buildViolation($constraint->message) + ->setParameter('%type', $target_type) + ->setParameter('%id', $target_id) + ->atPath((string) $delta . '.target_id') + ->setInvalidValue($target_id) + ->addViolation(); + } + } } + } + } diff --git a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php index 4217b2e..63587bb 100644 --- a/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php +++ b/core/lib/Drupal/Core/Field/EntityReferenceFieldItemList.php @@ -15,6 +15,13 @@ */ class EntityReferenceFieldItemList extends FieldItemList implements EntityReferenceFieldItemListInterface { + public function getConstraints() { + $constraints = parent::getConstraints(); + $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); + $constraints[] = $constraint_manager->create('ValidReference', []); + return $constraints; + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php index 70397fa..ea0ee73 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php @@ -22,9 +22,6 @@ * * Supported settings (below the definition's 'settings' key) are: * - target_type: The entity type to reference. Required. - * - target_bundle: (optional): If set, restricts the entity bundles which may - * may be referenced. May be set to an single bundle, or to an array of - * allowed bundles. * * @FieldType( * id = "entity_reference", @@ -33,8 +30,7 @@ * category = @Translation("Reference"), * no_ui = TRUE, * default_formatter = "entity_reference_label", - * list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList", - * constraints = {"ValidReference" = {}} + * list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList" * ) */ class EntityReferenceItem extends FieldItemBase { @@ -53,7 +49,7 @@ public static function defaultStorageSettings() { */ public static function defaultFieldSettings() { return array( - 'handler' => 'default:' . (\Drupal::moduleHandler()->moduleExists('node') ? 'node' : 'user'), + 'handler' => 'default', 'handler_settings' => array(), ) + parent::defaultFieldSettings(); } @@ -148,28 +144,6 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) /** * {@inheritdoc} */ - public function getConstraints() { - $constraints = parent::getConstraints(); - list($current_handler) = explode(':', $this->getSetting('handler'), 2); - if ($current_handler === 'default') { - $handler_settings = $this->getSetting('handler_settings'); - if (!empty($handler_settings['target_bundles'])) { - $constraint_manager = \Drupal::typedDataManager()->getValidationConstraintManager(); - $constraints[] = $constraint_manager->create('ComplexData', [ - 'entity' => [ - 'Bundle' => [ - 'bundle' => $handler_settings['target_bundles'], - ], - ], - ]); - } - } - return $constraints; - } - - /** - * {@inheritdoc} - */ public function setValue($values, $notify = TRUE) { if (isset($values) && !is_array($values)) { // If either a scalar or an object was passed as the value for the item, diff --git a/core/modules/comment/src/Tests/CommentValidationTest.php b/core/modules/comment/src/Tests/CommentValidationTest.php index 62acec0..5c30d89 100644 --- a/core/modules/comment/src/Tests/CommentValidationTest.php +++ b/core/modules/comment/src/Tests/CommentValidationTest.php @@ -39,7 +39,7 @@ protected function setUp() { */ public function testValidation() { // Add a user. - $user = User::create(array('name' => 'test')); + $user = User::create(array('name' => 'test', 'status' => TRUE)); $user->save(); // Add comment type. diff --git a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php index 1c157d0..1a59d58 100644 --- a/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php +++ b/core/modules/file/src/Plugin/EntityReferenceSelection/FileSelection.php @@ -25,9 +25,11 @@ class FileSelection extends SelectionBase { /** * {@inheritdoc} */ - protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { + protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS', $strict = TRUE) { $query = parent::buildEntityQuery($match, $match_operator); - $query->condition('status', FILE_STATUS_PERMANENT); + if ($strict) { + $query->condition('status', FILE_STATUS_PERMANENT); + } return $query; } diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index 8bbe278..16eae1c 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -28,7 +28,7 @@ * default_widget = "file_generic", * default_formatter = "file_default", * list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList", - * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}} + * constraints = {"ReferenceAccess" = {}} * ) */ class FileItem extends EntityReferenceItem { diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php index 442929f..35e5004 100644 --- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php +++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php @@ -43,7 +43,7 @@ * }, * }, * list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList", - * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}} + * constraints = {"ReferenceAccess" = {}} * ) */ class ImageItem extends FileItem { diff --git a/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php b/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php index 0fdd9af..6cdef4d 100644 --- a/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php +++ b/core/modules/system/src/Tests/Entity/Element/EntityAutocompleteElementFormTest.php @@ -259,7 +259,7 @@ public function testInvalidEntityAutocompleteElement() { ]); $form_builder->submitForm($this, $form_state); $this->assertEqual(count($form_state->getErrors()), 1); - $this->assertEqual($form_state->getErrors()['single'], t('The referenced entity (%type: %id) does not exist.', array('%type' => 'entity_test', '%id' => 42))); + $this->assertEqual($form_state->getErrors()['single'], t('This entity (%type: %id) cannot be referenced.', array('%type' => 'entity_test', '%id' => 42))); // Do the same tests as above but on an element with '#validate_reference' // set to FALSE. diff --git a/core/modules/system/src/Tests/Entity/EntityFieldTest.php b/core/modules/system/src/Tests/Entity/EntityFieldTest.php index 6fae79b..5c9161e 100644 --- a/core/modules/system/src/Tests/Entity/EntityFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityFieldTest.php @@ -691,8 +691,8 @@ public function testEntityConstraintValidation() { ->setSetting('target_type', 'node') ->setSetting('handler_settings', ['target_bundles' => ['article' => 'article']]); $reference_field = \Drupal::TypedDataManager()->create($definition); - $reference = $reference_field->appendItem(array('entity' => $node)); - $violations = $reference->validate(); + $reference_field->appendItem(array('entity' => $node)); + $violations = $reference_field->validate(); $this->assertEqual($violations->count(), 1); $node = entity_create('node', array( @@ -701,8 +701,8 @@ public function testEntityConstraintValidation() { 'title' => $this->randomString(), )); $node->save(); - $reference->setValue($node); - $violations = $reference->validate(); + $reference_field->entity = $node; + $violations = $reference_field->validate(); $this->assertEqual($violations->count(), 0); } diff --git a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php index f74410f..3195e1d 100644 --- a/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php +++ b/core/modules/system/src/Tests/Entity/EntityReferenceFieldTest.php @@ -103,7 +103,7 @@ public function testEntityReferenceFieldValidation() { $entity->{$this->fieldName}->target_id = 9999; $violations = $entity->{$this->fieldName}->validate(); $this->assertEqual($violations->count(), 1, 'Validation throws a violation.'); - $this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%type: %id) does not exist.', array('%type' => $this->referencedEntityType, '%id' => 9999))); + $this->assertEqual($violations[0]->getMessage(), t('This entity (%type: %id) cannot be referenced.', array('%type' => $this->referencedEntityType, '%id' => 9999))); // @todo Implement a test case for invalid bundle references after // https://www.drupal.org/node/2064191 is fixed. diff --git a/core/modules/system/src/Tests/Entity/EntityValidationTest.php b/core/modules/system/src/Tests/Entity/EntityValidationTest.php index aa85fa7..81951df 100644 --- a/core/modules/system/src/Tests/Entity/EntityValidationTest.php +++ b/core/modules/system/src/Tests/Entity/EntityValidationTest.php @@ -165,7 +165,7 @@ protected function checkValidation($entity_type) { $test_entity->set('user_id', 9999); $violations = $test_entity->validate(); $this->assertEqual($violations->count(), 1, 'Validation failed.'); - $this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%type: %id) does not exist.', array('%type' => 'user', '%id' => 9999))); + $this->assertEqual($violations[0]->getMessage(), t('This entity (%type: %id) cannot be referenced.', array('%type' => 'user', '%id' => 9999))); $test_entity = clone $entity; $test_entity->field_test_text->format = $this->randomString(33); diff --git a/core/modules/system/src/Tests/Entity/ValidReferenceConstraintValidatorTest.php b/core/modules/system/src/Tests/Entity/ValidReferenceConstraintValidatorTest.php index ef38189..d81a124 100644 --- a/core/modules/system/src/Tests/Entity/ValidReferenceConstraintValidatorTest.php +++ b/core/modules/system/src/Tests/Entity/ValidReferenceConstraintValidatorTest.php @@ -65,7 +65,7 @@ public function testValidation() { // Make sure the information provided by a violation is correct. $violation = $violations[0]; - $this->assertEqual($violation->getMessage(), t('The referenced entity (%type: %id) does not exist.', array( + $this->assertEqual($violation->getMessage(), t('This entity (%type: %id) cannot be referenced.', array( '%type' => 'user', '%id' => $entity->id(), )), 'The message for invalid value is correct.'); diff --git a/core/modules/taxonomy/src/Tests/TermValidationTest.php b/core/modules/taxonomy/src/Tests/TermValidationTest.php index b369737..a0db7fc 100644 --- a/core/modules/taxonomy/src/Tests/TermValidationTest.php +++ b/core/modules/taxonomy/src/Tests/TermValidationTest.php @@ -63,7 +63,7 @@ public function testValidation() { $term->set('parent', 9999); $violations = $term->validate(); $this->assertEqual(count($violations), 1, 'Violation found when term parent is invalid.'); - $this->assertEqual($violations[0]->getMessage(), format_string('The referenced entity (%type: %id) does not exist.', array('%type' => 'taxonomy_term', '%id' => 9999))); + $this->assertEqual($violations[0]->getMessage(), format_string('This entity (%type: %id) cannot be referenced.', array('%type' => 'taxonomy_term', '%id' => 9999))); $term->set('parent', 0); $violations = $term->validate(); diff --git a/core/modules/user/src/Tests/UserValidationTest.php b/core/modules/user/src/Tests/UserValidationTest.php index f39e11e..f7d2ffa 100644 --- a/core/modules/user/src/Tests/UserValidationTest.php +++ b/core/modules/user/src/Tests/UserValidationTest.php @@ -179,7 +179,7 @@ function testValidation() { $violations = $user->validate(); $this->assertEqual(count($violations), 1); $this->assertEqual($violations[0]->getPropertyPath(), 'roles.1'); - $this->assertEqual($violations[0]->getMessage(), t('The referenced entity (%entity_type: %name) does not exist.', array('%entity_type' => 'user_role', '%name' => 'unknown_role'))); + $this->assertEqual($violations[0]->getMessage(), t('This entity (%entity_type: %name) cannot be referenced.', array('%entity_type' => 'user_role', '%name' => 'unknown_role'))); } /** diff --git a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php index 25e7393..b488cfe 100644 --- a/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php +++ b/core/modules/views/src/Plugin/EntityReferenceSelection/ViewsSelection.php @@ -184,6 +184,14 @@ public function validateReferenceableEntities(array $ids) { } /** + * {@inheritdoc} + */ + public function validateReferenceableNewEntities(array $entities) { + // @todo adjust + return []; + } + + /** * Element validate; Check View is valid. */ public static function settingsFormValidate($element, FormStateInterface $form_state, $form) {