diff --git a/core/modules/forum/config/optional/taxonomy.vocabulary.forums.yml b/core/modules/forum/config/optional/taxonomy.vocabulary.forums.yml index b5c51eb..ef2e54a 100644 --- a/core/modules/forum/config/optional/taxonomy.vocabulary.forums.yml +++ b/core/modules/forum/config/optional/taxonomy.vocabulary.forums.yml @@ -9,3 +9,4 @@ vid: forums description: 'Forum navigation vocabulary' hierarchy: 1 weight: -10 +prevent_duplicates: false diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php index abada74..dc9f8cc 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/Vocabulary/VocabularyResourceTestBase.php @@ -56,6 +56,7 @@ protected function getExpectedNormalizedEntity() { 'description' => NULL, 'hierarchy' => 0, 'weight' => 0, + 'prevent_duplicates' => FALSE, ]; } diff --git a/core/modules/taxonomy/config/schema/taxonomy.schema.yml b/core/modules/taxonomy/config/schema/taxonomy.schema.yml index efa08e1..e3c6c82 100644 --- a/core/modules/taxonomy/config/schema/taxonomy.schema.yml +++ b/core/modules/taxonomy/config/schema/taxonomy.schema.yml @@ -33,6 +33,9 @@ taxonomy.vocabulary.*: weight: type: integer label: 'Weight' + prevent_duplicates: + type: boolean + label: 'Prevent duplicates' field.formatter.settings.entity_reference_rss_category: type: mapping diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php index 0c3a0eb..e89cda6 100644 --- a/core/modules/taxonomy/src/Entity/Term.php +++ b/core/modules/taxonomy/src/Entity/Term.php @@ -128,7 +128,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { 'type' => 'string_textfield', 'weight' => -5, )) - ->setDisplayConfigurable('form', TRUE); + ->setDisplayConfigurable('form', TRUE) + ->addConstraint('TermName', []); $fields['description'] = BaseFieldDefinition::create('text_long') ->setLabel(t('Description')) diff --git a/core/modules/taxonomy/src/Entity/Vocabulary.php b/core/modules/taxonomy/src/Entity/Vocabulary.php index a2d7eef..9bde33e 100644 --- a/core/modules/taxonomy/src/Entity/Vocabulary.php +++ b/core/modules/taxonomy/src/Entity/Vocabulary.php @@ -43,6 +43,7 @@ * "description", * "hierarchy", * "weight", + * "prevent_duplicates", * } * ) */ @@ -89,6 +90,13 @@ class Vocabulary extends ConfigEntityBundleBase implements VocabularyInterface { protected $weight = 0; /** + * Prevents to create terms with the same name. + * + * @var bool + */ + protected $prevent_duplicates = FALSE; + + /** * {@inheritdoc} */ public function getHierarchy() { @@ -120,6 +128,13 @@ public function getDescription() { /** * {@inheritdoc} */ + public function preventDuplicates() { + return $this->prevent_duplicates; + } + + /** + * {@inheritdoc} + */ public static function preDelete(EntityStorageInterface $storage, array $entities) { parent::preDelete($storage, $entities); diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TermNameConstraint.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TermNameConstraint.php new file mode 100644 index 0000000..4bf8fbe --- /dev/null +++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TermNameConstraint.php @@ -0,0 +1,20 @@ +first()) { + return; + } + + $field_name = $items->getFieldDefinition()->getName(); + /** @var ContentEntityInterface $entity */ + $entity = $items->getEntity(); + + if (!Vocabulary::load($entity->bundle())->preventDuplicates()) { + return; + } + + $entity_type_id = $entity->getEntityTypeId(); + $bundle_key = $entity->getEntityType()->getKey('bundle'); + $id_key = $entity->getEntityType()->getKey('id'); + + $value_taken = (bool) \Drupal::entityQuery($entity_type_id) + // The id could be NULL, so we cast it to 0 in that case. + ->condition($id_key, (int) $items->getEntity()->id(), '<>') + ->condition($field_name, $item->value) + ->condition($bundle_key, $entity->bundle()) + ->range(0, 1) + ->count() + ->execute(); + + if ($value_taken) { + $this->context->addViolation($constraint->message, [ + '%value' => $item->value, + '%bundle' => $entity->bundle(), + '@entity_type' => $entity->getEntityType()->getLowercaseLabel(), + '@field_name' => Unicode::strtolower($items->getFieldDefinition()->getLabel()), + ]); + } + + } + +} diff --git a/core/modules/taxonomy/src/VocabularyForm.php b/core/modules/taxonomy/src/VocabularyForm.php index 1868ddc..f96e831 100644 --- a/core/modules/taxonomy/src/VocabularyForm.php +++ b/core/modules/taxonomy/src/VocabularyForm.php @@ -73,6 +73,11 @@ public function form(array $form, FormStateInterface $form_state) { '#title' => $this->t('Description'), '#default_value' => $vocabulary->getDescription(), ); + $form['prevent_duplicates'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Prevent duplicates'), + '#default_value' => $vocabulary->preventDuplicates(), + ); // $form['langcode'] is not wrapped in an // if ($this->moduleHandler->moduleExists('language')) check because the diff --git a/core/profiles/standard/config/install/taxonomy.vocabulary.tags.yml b/core/profiles/standard/config/install/taxonomy.vocabulary.tags.yml index 8fac8f5..b9991f0 100644 --- a/core/profiles/standard/config/install/taxonomy.vocabulary.tags.yml +++ b/core/profiles/standard/config/install/taxonomy.vocabulary.tags.yml @@ -6,3 +6,4 @@ vid: tags description: 'Use tags to group articles on similar topics into categories.' hierarchy: 0 weight: 0 +prevent_duplicates: false