diff --git a/config/schema/entity_reference_validators.schema.yml b/config/schema/entity_reference_validators.schema.yml index 7b28556..1e0938b 100644 --- a/config/schema/entity_reference_validators.schema.yml +++ b/config/schema/entity_reference_validators.schema.yml @@ -5,3 +5,6 @@ field.field.*.*.*.third_party.entity_reference_validators: circular_reference: type: boolean label: 'Prevent entity from referencing itself' + duplicate_reference: + type: boolean + label: 'Prevent multiple references to the same entity' diff --git a/entity_reference_validators.module b/entity_reference_validators.module index f7dc7a9..2dfaa1c 100644 --- a/entity_reference_validators.module +++ b/entity_reference_validators.module @@ -17,6 +17,9 @@ function entity_reference_validators_entity_bundle_field_info_alter(&$fields, \D if ($field instanceof ThirdPartySettingsInterface && $field->getThirdPartySetting('entity_reference_validators', 'circular_reference', FALSE)) { $field->addConstraint('CircularReference'); } + if ($field instanceof ThirdPartySettingsInterface && $field->getThirdPartySetting('entity_reference_validators', 'duplicate_reference', FALSE)) { + $field->addConstraint('DuplicateReference'); + } } } @@ -27,21 +30,30 @@ function entity_reference_validators_form_field_config_edit_form_alter(array &$f /** @var \Drupal\field\Entity\FieldConfig $field */ $field = $form_state->getFormObject()->getEntity(); - if ($field->getType() === 'entity_reference' && $field instanceof ThirdPartySettingsInterface - // Only add circular reference when the target type is the same as the - // field's. - && $field->getTargetEntityTypeId() === $field->getItemDefinition()->getSetting('target_type') - ) { + if ($field->getType() === 'entity_reference' && $field instanceof ThirdPartySettingsInterface) { + // Add a fieldset for the validators. $form['third_party_settings']['entity_reference_validators']['container'] = [ '#type' => 'details', '#title' => t('Reference validators'), '#open' => TRUE, ]; - $form['third_party_settings']['entity_reference_validators']['container']['circular_reference'] = [ + + // Only check for circular references when the target type is the same as + // the field's. + if ($field->getTargetEntityTypeId() === $field->getItemDefinition()->getSetting('target_type')) { + $form['third_party_settings']['entity_reference_validators']['container']['circular_reference'] = [ + '#type' => 'checkbox', + '#title' => t('Prevent entity from referencing itself'), + '#default_value' => $field->getThirdPartySetting('entity_reference_validators', 'circular_reference', FALSE), + '#parents' => ['third_party_settings', 'entity_reference_validators', 'circular_reference'], + ]; + } + + $form['third_party_settings']['entity_reference_validators']['container']['duplicate_reference'] = [ '#type' => 'checkbox', - '#title' => t('Prevent entity from referencing itself'), - '#default_value' => $field->getThirdPartySetting('entity_reference_validators', 'circular_reference', FALSE), - '#parents' => ['third_party_settings', 'entity_reference_validators', 'circular_reference'], + '#title' => t('Prevent entity from referencing duplicates'), + '#default_value' => $field->getThirdPartySetting('entity_reference_validators', 'duplicate_reference', FALSE), + '#parents' => ['third_party_settings', 'entity_reference_validators', 'duplicate_reference'], ]; } } diff --git a/src/Plugin/Validation/Constraint/DuplicateReferenceConstraint.php b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraint.php new file mode 100644 index 0000000..e224f81 --- /dev/null +++ b/src/Plugin/Validation/Constraint/DuplicateReferenceConstraint.php @@ -0,0 +1,26 @@ +filterEmptyItems()->getValue()); + + $values = array_filter($values); + $occurrences = array_count_values($values); + + $duplicate_keys = array_keys(array_filter($values, function ($target_id) use ($occurrences) { + return $occurrences[$target_id] > 1; + })); + + foreach ($duplicate_keys as $key) { + $entity = $value->get($key)->entity; + $this->context->buildViolation($constraint->message) + ->setParameter('%label', EntityAutocomplete::getEntityLabels([$entity])) + ->setInvalidValue($entity) + ->atPath((string) $key . '.target_id') + ->addViolation(); + } + } + +}