Skip to content
taxonomy.module 27.5 KiB
Newer Older
Dries Buytaert's avatar
Dries Buytaert committed
<?php
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * @file
 * Enables the organization of content into categories.
 */

use Drupal\Component\Utility\Tags;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\Core\Entity\EntityInterface;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\taxonomy\TermInterface;
use Drupal\taxonomy\VocabularyInterface;
/**
 * Denotes that no term in the vocabulary has a parent.
 */
const TAXONOMY_HIERARCHY_DISABLED = 0;

/**
 * Denotes that one or more terms in the vocabulary has a single parent.
 */
const TAXONOMY_HIERARCHY_SINGLE = 1;

/**
 * Denotes that one or more terms in the vocabulary have multiple parents.
 */
const TAXONOMY_HIERARCHY_MULTIPLE = 2;

/**
 * Users can create new terms in a free-tagging vocabulary when
 * submitting a taxonomy_autocomplete_widget. We store a term object
 * whose tid is 'autocreate' as a field data item during widget
 * validation and then actually create the term if/when that field
 * data item makes it to taxonomy_field_insert/update().
 */

function taxonomy_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.taxonomy':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Taxonomy module allows you to classify the content of your website. To classify content, you define <em>vocabularies</em> that contain related <em>terms</em>, and then assign the vocabularies to content types. For more information, see the online handbook entry for the <a href="@taxonomy">Taxonomy module</a>.', array('@taxonomy' => 'http://drupal.org/documentation/modules/taxonomy')) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Creating vocabularies') . '</dt>';
      $output .= '<dd>' . t('Users with sufficient <a href="@perm">permissions</a> can create <em>vocabularies</em> and <em>terms</em> through the <a href="@taxo">Taxonomy page</a>. The page listing the terms provides a drag-and-drop interface for controlling the order of the terms and sub-terms within a vocabulary, in a hierarchical fashion. A <em>controlled vocabulary</em> classifying music by genre with terms and sub-terms could look as follows:', array('@taxo' => \Drupal::url('taxonomy.vocabulary_list'), '@perm' => \Drupal::url('user.admin_permissions', [], array('fragment'=>'module-taxonomy'))));
      $output .= '<ul><li>' . t('<em>vocabulary</em>: Music') . '</li>';
      $output .= '<ul><li>' . t('<em>term</em>: Jazz') . '</li>';
      $output .= '<ul><li>' . t('<em>sub-term</em>: Swing') . '</li>';
      $output .= '<li>' . t('<em>sub-term</em>: Fusion') . '</li></ul></ul>';
      $output .= '<ul><li>' . t('<em>term</em>: Rock') . '</li>';
      $output .= '<ul><li>' . t('<em>sub-term</em>: Country rock') . '</li>';
      $output .= '<li>' . t('<em>sub-term</em>: Hard rock') . '</li></ul></ul></ul>';
      $output .= t('You can assign a sub-term to multiple parent terms. For example, <em>fusion</em> can be assigned to both <em>rock</em> and <em>jazz</em>.') . '</dd>';
      $output .= '<dd>' . t('Terms in a <em>free-tagging vocabulary</em> can be built gradually as you create or edit content. This is often done used for blogs or photo management applications.') . '</dd>';
      $output .= '<dt>' . t('Assigning vocabularies to content types') . '</dt>';
      $output .= '<dd>' . t('Before you can use a new vocabulary to classify your content, a new Taxonomy term field must be added to a <a href="@ctedit">content type</a> on its <em>manage fields</em> page. When adding a taxonomy field, you choose a <em>widget</em> to use to enter the taxonomy information on the content editing page: a select list, checkboxes, radio buttons, or an auto-complete field (to build a free-tagging vocabulary). After choosing the field type and widget, on the subsequent <em>field settings</em> page you can choose the desired vocabulary, whether one or multiple terms can be chosen from the vocabulary, and other settings. The same vocabulary can be added to multiple content types, by using the "Re-use existing field" section on the manage fields page.', array('@ctedit' => \Drupal::url('node.overview_types'))) . '</dd>';
      $output .= '<dt>' . t('Classifying content') . '</dt>';
      $output .= '<dd>' . t('After the vocabulary is assigned to the content type, you can start classifying content. The field with terms will appear on the content editing screen when you edit or <a href="@addnode">add new content</a>.', array('@addnode' => \Drupal::url('node.add_page'))) . '</dd>';
      $output .= '<dt>' . t('Viewing listings') . '</dt>';
      $output .= '<dd>' . t("Each taxonomy term automatically provides a page listing content that has its classification. For example, if the taxonomy term <em>country rock</em> has the ID 123 (you can see this by looking at the URL when hovering on the linked term, which you can click to navigate to the listing page), then you will find this list at the path <em>taxonomy/term/123</em>.") . '</dd>';
      $output .= '<dt>' . t('Extending Taxonomy module') . '</dt>';
      $output .= '<dd>' . t('There are <a href="@taxcontrib">many contributed modules</a> that extend the behavior of the Taxonomy module for both display and organization of terms.', array('@taxcontrib' => 'http://drupal.org/project/modules?filters=tid:71&solrsort=sis_project_release_usage%20desc'));
      $output .= '</dl>';
      return $output;
      $output = '<p>' . t('Taxonomy is for categorizing content. Terms are grouped into vocabularies. For example, a vocabulary called "Fruit" would contain the terms "Apple" and "Banana".') . '</p>';
    case 'entity.taxonomy_vocabulary.overview_form':
      $vocabulary = $route_match->getParameter('taxonomy_vocabulary');
          return '<p>' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
          return '<p>' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
          return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) . '</p>';
  return new Url('entity.taxonomy_term.canonical', array(
/**
 * Implements hook_page_build().
 */
function taxonomy_page_build(&$page) {
  $route_match = \Drupal::routeMatch();
  if ($route_match->getRouteName() == 'entity.taxonomy_term.canonical' && ($term = $route_match->getParameter('taxonomy_term')) && $term instanceof TermInterface) {
    foreach ($term->uriRelationships() as $rel) {
      // Set the URI relationships, like canonical.
      $page['#attached']['html_head_link'][] = array(
        array(
          'rel' => $rel,
          'href' => $term->url($rel),
        ),
        TRUE,
      );

      // Set the term path as the canonical URL to prevent duplicate content.
      if ($rel == 'canonical') {
        // Set the non-aliased canonical path as a default shortlink.
        $page['#attached']['html_head_link'][] = array(
          array(
            'rel' => 'shortlink',
            'href' => $term->url($rel, array('alias' => TRUE)),
          ),
          TRUE,
        );
      }
    }
  }
}

 * Return nodes attached to a term across all fields.
 *
 * This function requires taxonomy module to be maintaining its own tables,
 * and will return an empty array if it is not. If using other field storage
 * methods alternatives methods for listing terms will need to be used.
 *
 * @param $pager
 *   Boolean to indicate whether a pager should be used.
 * @param $limit
 *   Integer. The maximum number of nodes to find.
 *   Set to FALSE for no limit.
 *   An array of fields and directions.
 *
 * @return
 *   An array of nids matching the query.
 */
function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
  if (!\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
    return array();
  }
  $query = db_select('taxonomy_index', 't');
  $query->addTag('node_access');
  $query->addMetaData('base_table', 'taxonomy_index');
  if ($pager) {
    $count_query = clone $query;
    $count_query->addExpression('COUNT(t.nid)');

    $query = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender');
    if ($limit !== FALSE) {
      $query = $query->limit($limit);
    }
    if ($limit !== FALSE) {
      $query->range(0, $limit);
    }
  }
  $query->addField('t', 'nid');
  $query->addField('t', 'tid');
  foreach ($order as $field => $direction) {
    $query->orderBy($field, $direction);
    // ORDER BY fields need to be loaded too, assume they are in the form
    // table_alias.name
    list($table_alias, $name) = explode('.', $field);
    $query->addField($table_alias, $name);
  }
  return $query->execute()->fetchCol();
}

    'taxonomy_term' => array(
      'render element' => 'elements',
    ),
 * Checks and updates the hierarchy flag of a vocabulary.
 * Checks the current parents of all terms in a vocabulary and updates the
 * vocabulary's hierarchy setting to the lowest possible level. If no term
 * has parent terms then the vocabulary will be given a hierarchy of
 * TAXONOMY_HIERARCHY_DISABLED. If any term has a single parent then the
 * vocabulary will be given a hierarchy of TAXONOMY_HIERARCHY_SINGLE. If any
 * term has multiple parents then the vocabulary will be given a hierarchy of
 * TAXONOMY_HIERARCHY_MULTIPLE.
 * @param \Drupal\taxonomy\VocabularyInterface $vocabulary
 * @param $changed_term
 *   An array of the term structure that was updated.
 *
 * @return
 *   An integer that represents the level of the vocabulary's hierarchy.
function taxonomy_check_vocabulary_hierarchy(VocabularyInterface $vocabulary, $changed_term) {
  $tree = taxonomy_get_tree($vocabulary->id());
  $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
    // Update the changed term with the new parent value before comparison.
    if ($term->tid == $changed_term['tid']) {
      $term->parents = $term->parent;
    }
    // Check this term's parent count.
    if (count($term->parents) > 1) {
      $hierarchy = TAXONOMY_HIERARCHY_MULTIPLE;
    elseif (count($term->parents) == 1 && !isset($term->parents[0])) {
      $hierarchy = TAXONOMY_HIERARCHY_SINGLE;
  if ($hierarchy != $vocabulary->hierarchy) {
    $vocabulary->hierarchy = $hierarchy;
 * Generates an array which displays a term detail page.
 * @param \Drupal\taxonomy\Entity\Term $term
 *   A taxonomy term object.
 * @param string $view_mode
 *   View mode, e.g. 'full', 'teaser'...
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 *   A $page element suitable for use by drupal_render().
function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
  return entity_view($term, $view_mode, $langcode);
 * Constructs a drupal_render() style array from an array of loaded terms.
 *   An array of taxonomy terms as returned by Term::loadMultiple().
 * @param string $view_mode
 *   View mode, e.g. 'full', 'teaser'...
 * @param string $langcode
 *   (optional) A language code to use for rendering. Defaults to the global
 *   content language of the current request.
 *
 * @return array
 *   An array in the format expected by drupal_render().
function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcode = NULL) {
  return entity_view_multiple($terms, $view_mode, $langcode);
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function taxonomy_theme_suggestions_taxonomy_term(array $variables) {
  $suggestions = array();

  /** @var \Drupal\taxonomy\TermInterface $term */
  $term = $variables['elements']['#taxonomy_term'];

  $suggestions[] = 'taxonomy_term__' . $term->bundle();
  $suggestions[] = 'taxonomy_term__' . $term->id();

  return $suggestions;
}

 * Prepares variables for taxonomy term templates.
 *
 * Default template: taxonomy-term.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the taxonomy term and any
 *     fields attached to the term. Properties used:
 *     - #taxonomy_term: A \Drupal\taxonomy\TermInterface object.
 *     - #view_mode: The current view mode for this taxonomy term, e.g.
 *       'full' or 'teaser'.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_taxonomy_term(&$variables) {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  $variables['term'] = $variables['elements']['#taxonomy_term'];
  /** @var \Drupal\taxonomy\TermInterface $term */
  // We use name here because that is what appears in the UI.
  $variables['name'] = $variables['elements']['name'];
  unset($variables['elements']['name']);
  $variables['page'] = $variables['view_mode'] == 'full' && taxonomy_term_is_page($term);

  // Helpful $content variable for templates.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Returns whether the current page is the page of the passed-in term.
 * @param \Drupal\taxonomy\Entity\Term $term
  if (\Drupal::routeMatch()->getRouteName() == 'entity.taxonomy_term.canonical' && $page_term_id = \Drupal::routeMatch()->getRawParameter('taxonomy_term')) {
    return $page_term_id == $term->id();
 * Clear all static cache variables for terms.
  \Drupal::entityManager()->getStorage('taxonomy_term')->resetCache();
/**
 * Clear all static cache variables for vocabularies.
 *   An array of ids to reset in the entity cache.
function taxonomy_vocabulary_static_reset(array $ids = NULL) {
  \Drupal::entityManager()->getStorage('taxonomy_vocabulary')->resetCache($ids);
/**
 * Get names for all taxonomy vocabularies.
 *
 * @return array
 *   A list of existing vocabulary IDs.
 */
function taxonomy_vocabulary_get_names() {
  $names = &drupal_static(__FUNCTION__);

  if (!isset($names)) {
    $config_names = \Drupal::configFactory()->listAll('taxonomy.vocabulary.');
    foreach ($config_names as $config_name) {
      $id = substr($config_name, strlen('taxonomy.vocabulary.'));
      $names[$id] = $id;
    }
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Finds all parents of a given term ID.
 *
 * @param $tid
 *   A taxonomy term ID.
 *
 * @return
 *   An array of term objects which are the parents of the term $tid, or an
 *   empty array if parents are not found.
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\taxonomy\TermStorageController::loadParents()
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_term_load_parents($tid) {
  return \Drupal::entityManager()->getStorage('taxonomy_term')->loadParents($tid);
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Find all ancestors of a given term ID.
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\taxonomy\TermStorageController::loadAllParents()
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_term_load_parents_all($tid) {
  return \Drupal::entityManager()->getStorage('taxonomy_term')->loadAllParents($tid);
Dries Buytaert's avatar
 
Dries Buytaert committed
}

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Finds all children of a term ID.
 *
 * @param $tid
 *   A taxonomy term ID.
 *
 * @return
 *   An array of term objects that are the children of the term $tid, or an
 *   empty array when no children exist.
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\taxonomy\TermStorageController::loadChildren()
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_term_load_children($tid) {
  return \Drupal::entityManager()->getStorage('taxonomy_term')->loadChildren($tid);
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Create a hierarchical representation of a vocabulary.
 *
 * @param $vid
 *   The vocabulary ID to generate the tree for.
Dries Buytaert's avatar
 
Dries Buytaert committed
 * @param $parent
 *   The term ID under which to generate the tree. If 0, generate the tree
 *   for the entire vocabulary.
Dries Buytaert's avatar
 
Dries Buytaert committed
 * @param $max_depth
 *   The number of levels of the tree to return. Leave NULL to return all levels.
 * @param $load_entities
 *   If TRUE, a full entity load will occur on the term objects. Otherwise they
 *   are partial objects queried directly from the {taxonomy_term_field_data}
 *   table to save execution time and memory consumption when listing large
 *   numbers of terms. Defaults to FALSE.
Dries Buytaert's avatar
 
Dries Buytaert committed
 * @return
 *   An array of all term objects in the tree. Each term object is extended
 *   to have "depth" and "parents" attributes in addition to its normal ones.
 *   Results are statically cached. Term objects will be partial or complete
 *   depending on the $load_entities parameter.
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal\taxonomy\TermStorageController::loadTree()
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE) {
  return \Drupal::entityManager()->getStorage('taxonomy_term')->loadTree($vid, $parent, $max_depth, $load_entities);
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
Dries Buytaert's avatar
 
Dries Buytaert committed
 * Try to map a string to an existing term, as for glossary use.
Dries Buytaert's avatar
 
Dries Buytaert committed
 *
Dries Buytaert's avatar
 
Dries Buytaert committed
 * Provides a case-insensitive and trimmed mapping, to maximize the
 * likelihood of a successful match.
 *
Dries Buytaert's avatar
 
Dries Buytaert committed
 *   Name of the term to search for.
 * @param $vocabulary
 *   (optional) Vocabulary machine name to limit the search. Defaults to NULL.
Dries Buytaert's avatar
 
Dries Buytaert committed
 *
 * @return
 *   An array of matching term objects.
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_term_load_multiple_by_name($name, $vocabulary = NULL) {
  $values = array('name' => trim($name));
  if (isset($vocabulary)) {
    $vocabularies = taxonomy_vocabulary_get_names();
    if (isset($vocabularies[$vocabulary])){
    }
    else {
      // Return an empty array when filtering by a non-existing vocabulary.
      return array();
    }
  }
  return entity_load_multiple_by_properties('taxonomy_term', $values);
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * Load multiple taxonomy terms based on certain conditions.
 *
 * This function should be used whenever you need to load more than one term
 * from the database. Terms are loaded into memory and will not require
 * database access if loaded again during the same page request.
 *
 * @see \Drupal\Core\Entity\Query\EntityQueryInterface
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\taxonomy\Entity\Term::loadMultiple().
 * @param array $tids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
 *   An array of taxonomy term entities, indexed by tid. When no results are
 *   found, an empty array is returned.
function taxonomy_term_load_multiple(array $tids = NULL) {
  return Term::loadMultiple($tids);
 * Loads multiple taxonomy vocabularies based on certain conditions.
 *
 * This function should be used whenever you need to load more than one
 * vocabulary from the database. Terms are loaded into memory and will not
 * require database access if loaded again during the same page request.
 *
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\taxonomy\Entity\Vocabulary::loadMultiple().
 * @param array $vids
 *   (optional) An array of entity IDs. If omitted, all entities are loaded.
 *  An array of vocabulary objects, indexed by vid.
 */
function taxonomy_vocabulary_load_multiple(array $vids = NULL) {
  return Vocabulary::loadMultiple($vids);
 * Return the taxonomy vocabulary entity matching a vocabulary ID.
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\taxonomy\Entity\Vocabulary::load().
 * @param int $vid
 *   The vocabulary's ID.
 * @return \Drupal\taxonomy\Entity\Vocabulary|null
 *   The taxonomy vocabulary entity, if exists, NULL otherwise. Results are
function taxonomy_vocabulary_load($vid) {
  return Vocabulary::load($vid);
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Return the taxonomy term entity matching a term ID.
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\taxonomy\Entity\Term::load().
 * @return \Drupal\taxonomy\Entity\Term|null
 *   A taxonomy term entity, or NULL if the term was not found. Results are
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
 * Implodes a list of tags of a certain vocabulary into a string.
 * @see \Drupal\Component\Utility\Tags::explode()
 */
function taxonomy_implode_tags($tags, $vid = NULL) {
  $typed_tags = array();
  foreach ($tags as $tag) {
    // Extract terms belonging to the vocabulary in question.
    if (!isset($vid) || $tag->bundle() == $vid) {
      // Make sure we have a completed loaded taxonomy term.
      if ($tag instanceof EntityInterface && $label = $tag->label()) {
        // Commas and quotes in tag names are special cases, so encode 'em.
        $typed_tags[] = Tags::encode($label);
 * Implements hook_field_widget_info_alter().
 */
function taxonomy_field_widget_info_alter(&$info) {
  if (isset($info['options_select'])) {
    $info['options_select']['field_types'][] = 'taxonomy_term_reference';
  }
  if (isset($info['options_buttons'])) {
    $info['options_buttons']['field_types'][] = 'taxonomy_term_reference';
  }
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function taxonomy_field_formatter_info_alter(array &$info) {
  if (!\Drupal::moduleHandler()->moduleExists('entity_reference')) {
    unset($info['entity_reference_rss_category']);
  }
 * @param \Drupal\taxonomy\Entity\Term $term
 * @return
 *   The term name to be used as the page title.
 */
 * Form element validate handler for taxonomy term autocomplete element.
function taxonomy_autocomplete_validate($element, FormStateInterface $form_state) {
  // @see \Drupal\taxonomy\Plugin\Field\FieldWidget\TaxonomyAutocompleteWidget:massageFormValues()
    $typed_terms = Tags::explode($tags);
  form_set_value($element, $typed_terms, $form_state);
 * @defgroup taxonomy_index Taxonomy indexing
 * @{
 * Functions to maintain taxonomy indexing.
 *
 * Taxonomy uses default field storage to store canonical relationships
 * between terms and fieldable entities. However its most common use case
 * requires listing all content associated with a term or group of terms
 * sorted by creation date. To avoid slow queries due to joining across
 * multiple node and field tables with various conditions and order by criteria,
 * we maintain a denormalized table with all relationships between terms,
 * published nodes and common sort criteria such as sticky and created.
 * This is used as a lookup table by taxonomy_select_nodes(). When using other
 * field storage engines or alternative methods of denormalizing this data
 * you should set the taxonomy.settings:maintain_index_table to '0' to avoid
 * unnecessary writes in SQL.
 * Implements hook_ENTITY_TYPE_insert() for node entities.
function taxonomy_node_insert(EntityInterface $node) {
  // Add taxonomy index entries for the node.
  taxonomy_build_node_index($node);
 * Builds and inserts taxonomy index entries for a given node.
 *
 * The index lists all terms that are related to a given node entity, and is
 * therefore maintained at the entity level.
 *
 * @param \Drupal\node\Entity\Node $node
function taxonomy_build_node_index($node) {
  // We maintain a denormalized table of term/node relationships, containing
  // only data for current, published nodes.
  if (!\Drupal::config('taxonomy.settings')->get('maintain_index_table') || !(\Drupal::entityManager()->getStorage('node') instanceof SqlContentEntityStorage)) {

  $status = $node->isPublished();
  $sticky = (int) $node->isSticky();
  // We only maintain the taxonomy index for published nodes.
  if ($status && $node->isDefaultRevision()) {
    // Collect a unique list of all the term IDs from all node fields.
    $tid_all = array();
    foreach ($node->getFieldDefinitions() as $field) {
      if ($field->getType() == 'taxonomy_term_reference') {
        foreach ($node->getTranslationLanguages() as $language) {
          foreach ($node->getTranslation($language->id)->$field_name as $item) {
            if (!$item->isEmpty()) {
              $tid_all[$item->target_id] = $item->target_id;
            }
          }
        }
      }
    }
    // Insert index entries for all the node's terms.
    if (!empty($tid_all)) {
      foreach ($tid_all as $tid) {
        db_merge('taxonomy_index')
          ->key(array('nid' => $node->id(), 'tid' => $tid))
          ->fields(array('sticky' => $sticky, 'created' => $node->getCreatedTime()))
          ->execute();
 * Implements hook_ENTITY_TYPE_update() for node entities.
function taxonomy_node_update(EntityInterface $node) {
  // Always rebuild the node's taxonomy index entries on node save.
  taxonomy_delete_node_index($node);
  taxonomy_build_node_index($node);
}

 * Implements hook_ENTITY_TYPE_predelete() for node entities.
function taxonomy_node_predelete(EntityInterface $node) {
  // Clean up the {taxonomy_index} table when nodes are deleted.
  taxonomy_delete_node_index($node);
}

/**
 * Deletes taxonomy index entries for a given node.
 *
 * @param \Drupal\Core\Entity\EntityInterface $node
function taxonomy_delete_node_index(EntityInterface $node) {
  if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
    db_delete('taxonomy_index')->condition('nid', $node->id())->execute();
 * Implements hook_ENTITY_TYPE_delete() for taxonomy_term entities.
function taxonomy_taxonomy_term_delete(Term $term) {
  if (\Drupal::config('taxonomy.settings')->get('maintain_index_table')) {
    // Clean up the {taxonomy_index} table when terms are deleted.
    db_delete('taxonomy_index')->condition('tid', $term->id())->execute();
 * @} End of "defgroup taxonomy_index".