Score:4

How to make taxonomy term names unique per user?

cn flag

I have a taxonomy vocabulary, Groups.

Requirements

  • Different users can create groups with the same name. (User A can make an apples group, and User B can also make an apples group.)
  • The same user cannot make two groups with the same name (User A cannot make two apples groups.)

How can I implement this?

D8/9 offers a field constraint as explained in this answer and this blog post, but this makes a field unique for all users.

I want to make the taxonomy term name unique per user. Also, this needs to work with JSON:API. How can I do this?

Score:4
cn flag

You are allowed to add a custom validation constrains using this steps:

  1. Step 1: Define the constraint:

    The constraint definition will go in the namespace Drupal[MODULENAME]\Plugin\Validation\Constraint, and will extend Symfony\Component\Validator\Constraint. In this class, the types of constraint violations are defined, along with the error messages that will be displayed for the given violations.

  2. Step 2: Create the validation for the constraint

    The next step is to create the class that will validate the constraint. The constraint validation will go in the namespace Drupal[MODULENAME]\Plugin\Validation\Constraint, and will extend Symfony\Component\Validator\ConstraintValidator. In this class, the submitted values will be returned, and any violations will be registered.

    Please note, that the name of your validator class is expected to be ${ConstraintClassName}Validator by default. If you want to use a different name, you may overwrite the validatedBy() method of your Constraint class, that you created in step 1.

and for the final step you can add the new constrain to your field just like the answer you have mentioned

now having this information you can add a user reference field to your taxonomy so each term will hold info about the author, you can use hook_Entity_Type_presave to set this field programmatically

then you can create a custom constrain called notUniquePerArthor (or any name you like) then in step 2 where you define isUniquePerUser method, you can run a query and get a list of taxonomy term which have the label equal to value and author equal to current user, if this query return a value then this means that this term is not unique (user has defined this term before) and you can raise an error

cn flag
Awesome! This looks like a great way to do it, except the term name is not a field, right? It's like the node/page title-- it's not accessible (as far as I know) as a field. So what do I do in that case?
apaderno avatar
us flag
@PatrickKenny The node title is an entity field: `$fields['title'] = BaseFieldDefinition::create('string')->setLabel(t('Title'))->setRequired(TRUE)->setTranslatable(TRUE)->setRevisionable(TRUE)->setSetting('max_length', 255)->setDisplayOptions('view', ['label' => 'hidden','type' => 'string','weight' => -5,]);` Don't get confused from Drupal showing the page title in a block.
apaderno avatar
us flag
The same is true for the taxonomy term name: `$fields['name'] = BaseFieldDefinition::create('string')->setLabel(t('Name'))->setTranslatable(TRUE)->setRevisionable(TRUE)->setRequired(TRUE)->setSetting('max_length', 255)->setDisplayOptions('view', ['label' => 'hidden','type' => 'string','weight' => -5,]);`
cn flag
@apaderno Thanks, that's very helpful. Unfortunately, in `hook_entity_bundle_field_info_alter()`, it seems the `name` field is not available, which is what confused me. Follow-up question here: https://drupal.stackexchange.com/questions/305789/how-to-set-a-validation-constraint-for-the-taxonomy-term-name
apaderno avatar
us flag
@PatrickKenny That's because `hook_entity_bundle_field_info_alter()` is for bundle fields, while the taxonomy name is a base field, which means it's present in every taxonomy bundle.
Alireza Tabatabaeian avatar
cn flag
@PatrickKenny, you can use `hook_entity_base_field_info_alter` https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21entity.api.php/function/hook_entity_base_field_info_alter/8.2.x to change base field definitions like id or title or ...
Score:3
cn flag

Thanks to @Alireza Tabatabaeian for this. I'm simply posting my code that I used based on that answer.

To the taxonomy that I wanted to apply the constraint, I added a field_term_ref_author entity reference to the user.

mymodule.php

function MYMOUDLE_taxonomy_term_presave(TermInterface $term) {
  $vocabulary = $term->bundle();
  switch ($vocabulary) {
    case 'my_vocab_type':
      if ($term->isNew()) {
        // Set the author (via custom field).
        $current_user = \Drupal::currentUser()->id();
        $term->set('field_term_ref_author', $current_user);
      }
    break;

function MYMODULE_entity_base_field_info_alter(&$fields, $entity_type) {
  if ($entity_type->id() === 'taxonomy_term') {
    if (isset($fields['name'])) {
      $fields['name']->addConstraint('TermNameUniquePerUser');
    }
  }
}

mymodule/src/Plugin/Validation/Constraint/TermNameUniquePerUser.php

<?php

namespace Drupal\MYMODULE\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;

/**
 * Checks that the submitted value is unique to the user.
 *
 * @Constraint(
 *   id = "TermNameUniquePerUser",
 *   label = @Translation("Term Name Unique Per User", context = "Validation"),
 *   type = "string"
 * )
 */
class TermNameUniquePerUser extends Constraint {
  // The message that will be shown if the value is not unique.
  public $notUnique = 'You already created a term named %value.';

}

mymodule/src/Plugin/Validation/Constraint/TermNameUniquePerUserValidator.php

<?php

namespace Drupal\MYMODULE\Plugin\Validation\Constraint;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
 * Validates the TermNameUniquePerUser constraint.
 */
class TermNameUniquePerUserValidator extends ConstraintValidator {

  /**
   * {@inheritdoc}
   */
  public function validate($items, Constraint $constraint) {
    foreach ($items as $item) {
      // Next check if the value is unique.
      if (!$this->isUnique($item->value)) {
        $this->context->addViolation($constraint->notUnique, ['%value' => $item->value]);
      }
    }
  }

  /**
   * Is unique?
   *
   * @param string $value
   */
  private function isUnique($value) {
    $uid = \Drupal::currentUser()->id();
    $term_name_query = \Drupal::entityQuery('taxonomy_term')
      ->condition('name', $value, '=')
      ->condition('field_term_ref_author', $uid);
    // If the query has any results, then it is not unique.
    // So we should return the opposite of whether there are any results.
    return !($term_name_query->execute());
  }

}

The good thing about writing the entity query like this is that if the taxonomy vocabulary does not have a field_term_ref_author field, there will never be any results, so the constraint will evaluate correctly with terms that are in bundles not subject to the constraint (although this will result in a wasted database query.)

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.