4

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?

2 Answers 2

4

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

6
  • 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? Aug 24, 2021 at 7:34
  • 2
    @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
    Aug 24, 2021 at 10:17
  • 3
    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,]);
    – apaderno
    Aug 24, 2021 at 10:19
  • 3
    @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.
    – apaderno
    Aug 24, 2021 at 10:37
  • 1
    @PatrickKenny, you can use hook_entity_base_field_info_alter api.drupal.org/api/drupal/… to change base field definitions like id or title or ... Aug 24, 2021 at 11:34
3

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.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.