You are here

taxonomy_menu.module in Taxonomy menu 8

Generates menu links for all selected taxonomy terms.

File

taxonomy_menu.module
View source
<?php

/**
 * @file
 * Generates menu links for all selected taxonomy terms.
 */
use Drupal\Core\Language\Language;
use Drupal\Core\Entity\EntityInterface;
use Drupal\taxonomy\Entity\Term;
use Drupal\taxonomy\Entity\Vocabulary;

// Include the database layer.
module_load_include('inc', 'taxonomy_menu', 'taxonomy_menu.database');

// Include the batch functions.
module_load_include('inc', 'taxonomy_menu', 'taxonomy_menu.batch');

// Include the admin functions. Submit function on the confirmation page of the
// taxonomy overview page cannot be found if inc file is included on form alter
// only.
module_load_include('inc', 'taxonomy_menu', 'taxonomy_menu.admin');

/**
 * Implements hook_help().
 */
function taxonomy_menu_help($path, $arg) {

  // @TODO Implement the hook. Make documentation available to the user.
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function taxonomy_menu_form_taxonomy_vocabulary_form_alter(&$form, &$form_state) {
  taxonomy_menu_form_taxonomy_form_vocabulary($form, $form_state);
}

/**
 * Rebuilds all the menu items.
 *
 * @param $vid
 *   The vocabulary ID of the taxonomy terms from which to rebuild the menu links.
 */
function taxonomy_menu_items_rebuild($vid) {

  // Get the name of the menu from the administration settings.
  $menu_exists = taxonomy_menu_variable_get('vocab_menu', $vid, FALSE);

  // Delete the menu links associated to this vocabulary.
  taxonomy_menu_menu_links_delete($vid);

  // Re-create the menu links if a menu is set.
  if ($menu_exists) {
    taxonomy_menu_menu_links_insert($vid);
  }
}

/**
 * Inserts menu links associated to a vocabulary.
 *
 * @param $vid
 *   The ID of the vocabulary.
 */
function taxonomy_menu_menu_links_insert($vid) {

  // Get a list of all the taxonomy terms for that vocabulary and process them
  // using the bacth API.
  $menu_name = taxonomy_menu_variable_get('vocab_menu', $vid, FALSE);
  $terms = taxonomy_get_tree($vid, 0, NULL, TRUE);
  _taxonomy_menu_save_menu_links_batch($terms, $menu_name);
  menu_cache_clear_all($menu_name);
  drupal_set_message(t('The Taxonomy menu has been created.'), 'status');
}

/**
 * Updates menu links associated to a vocabulary.
 *
 * @param $vid
 *   The ID of the vocabulary.
 */
function taxonomy_menu_menu_links_update($vid) {

  // Get a list of all the existing taxonomy terms for that vocabulary and
  // process them using the bacth API.
  $menu_name = taxonomy_menu_variable_get('vocab_menu', $vid, FALSE);
  $tm_menu_links = _taxonomy_menu_get_menu_items($vid);
  $term_ids = array_values($tm_menu_links);
  $terms = entity_load_multiple('taxonomy_term', $term_ids);
  _taxonomy_menu_save_menu_links_batch($terms, $menu_name);
  menu_cache_clear_all($menu_name);
  drupal_set_message(t('The Taxonomy menu has been updated.'), 'status');
}

/**
 * Deletes all the menu links associated to a vocabulary.
 *
 * @param $vid
 *   The vocabulary ID from which to delete the menu items.
 */
function taxonomy_menu_menu_links_delete($vid) {

  // Get a list of all the taxonomy terms for this vocabulary and delete them.
  // Deleting means that the menu links are deleted and their respective
  // associations in {taxonomy_menu} table as well.
  $menu_items = _taxonomy_menu_get_menu_items($vid);
  $mlids = array_keys($menu_items);
  foreach ($mlids as $mlid) {
    menu_link_delete($mlid);
  }
  _taxonomy_menu_delete_all($vid);

  // Remove orphaned links.
  $menu_items = _taxonomy_menu_get_orphaned_menu_items($vid);
  if (!empty($menu_items)) {
    $mlids = array_values($menu_items);
    foreach ($mlids as $mlid) {
      menu_link_delete($mlid);
    }
  }
  drupal_set_message(t('The Taxonomy menu has been removed.'), 'status');
}

/**
 * Prepares a taxonomy item to be saved as a menu link.
 *
 * A menu item has the following properties:
 *  - link_path: (required)
 *  - link_title: (required)
 *  - router_path: (required)
 *  - menu_name: (optional)
 *  - weight: (optional)
 *  - expanded: (optional)
 *  - options: (optional)
 *  - mlid: (optional)
 *  - plid: (optional)
 *
 * @param $term
 *   A taxonomy term used to save a respective menu item.
 * @param $menu_name
 *   The machine name of the menu in which the menu link should be saved.
 *
 * @return
 *   A menu link built upon a taxonomy term, to be saved in the menu.
 */
function taxonomy_menu_menu_link_prepare($term, $menu_name) {
  static $weight = 0;
  $langcode = isset($term
    ->language()->id) ? $term
    ->language()->id : Language::LANGCODE_NOT_SPECIFIED;
  $recursive_count = FALSE;

  // Count nodes attached to a taxonomy term if the settings require it.
  // TODO Make the recursivity of node count optional.
  $display_count = taxonomy_menu_variable_get('display_num', $term
    ->bundle(), FALSE);
  $hide_term = taxonomy_menu_variable_get('hide_empty_terms', $term
    ->bundle(), FALSE);
  if ($hide_term || $display_count) {
    $nodes_count = taxonomy_menu_term_count_nodes($term
      ->id(), $recursive_count);
    $is_hidden = $hide_term && (!$nodes_count || $nodes_count == 0) ? 1 : 0;
  }

  // Load or create a menu link corresponding the taxonomy term being processed.
  $menu_link = taxonomy_menu_existing_menu_link_load($term, $langcode);

  // Menu to be attached to.
  $menu_link['menu_name'] = $menu_name;

  // Expanded.
  $menu_link['expanded'] = taxonomy_menu_variable_get('expanded', $term
    ->bundle(), 0);

  // Has children.
  $has_children = taxonomy_term_load_children($term
    ->id(), $term
    ->bundle());
  $menu_link['has_children'] = empty($has_children) ? 0 : 1;

  // Flatten.
  $flatten_menu = taxonomy_menu_variable_get('flat', $term
    ->bundle(), 0);
  if ($flatten_menu) {
    $menu_link['weight'] = $weight++;
    $menu_link['has_children'] = 0;
    $menu_link['plid'] = taxonomy_menu_variable_get('vocab_parent', $term
      ->bundle(), NULL);
    $menu_link['expanded'] = 0;
  }
  else {
    $menu_link['weight'] = $term->weight->value;
    $menu_link['plid'] = taxonomy_menu_term_get_plid($term, $langcode);
  }

  // Empty terms.
  $menu_link['hidden'] = isset($is_hidden) ? $is_hidden : 0;

  // Menu link title.
  $menu_link['link_title'] = $term->name->value;
  if ($display_count && $nodes_count > 0) {
    $menu_link['link_title'] .= " (" . $nodes_count . ")";
  }

  // HTML title attribute.
  if (taxonomy_menu_variable_get('display_title_attr', $term
    ->bundle(), TRUE)) {
    $term_description = taxonomy_menu_variable_get('term_item_description', $term
      ->bundle(), 0);
  }
  $menu_link['options']['attributes']['title'] = isset($term_description) && $term_description == 1 ? trim($term->description->value) : '';

  // Path.
  $link_path = taxonomy_menu_path_get($term);
  $menu_link['link_path'] = \Drupal::service('path.alias_manager.cached')
    ->getSystemPath($link_path, $langcode);
  return $menu_link;
}

/**
 * Loads an existing menu link or creates an initialized one from a taxonomy
 * term.
 *
 * @param array $term
 *   A taxonomy term to be used to load or create its corresponding menu link.
 * @param string $langcode
 *   The language code corresponding to the menu link to be loaded.
 *
 * @return array
 *    A menu link corresponding to the taxonomy term.
 */
function taxonomy_menu_existing_menu_link_load($term, $langcode) {
  $menu_link = array();

  // Try to get an existing menu link, else initialize a new one with the right
  // settings.
  $mlid = _taxonomy_menu_get_mlid($term
    ->id(), $term
    ->bundle(), $langcode);
  if ($mlid) {
    $menu_link = menu_link_load($mlid);

    // Flag that we want to update a reference in {taxonomy_menu} table later.
    $menu_link['taxonomy_menu']['update'] = TRUE;
  }
  else {

    // Only use the term's weight for menu links to be created, else you will
    // reset weights that may have been changed by other processes than
    // Taxonomy Menu.
    $menu_link = entity_create('menu_link', array(
      'module' => 'taxonomy_menu',
      'hidden' => 0,
      'weight' => $term->weight->value,
      'has_children' => 1,
      'language' => $langcode,
      'taxonomy_menu' => array(
        'update' => FALSE,
      ),
    ));
  }
  return $menu_link;
}

/**
 * Helper function to determine a parent mlid for a specific taxonomy term, in
 * function of the settings of the administration pages.
 *
 * @param $term
 *   The term, which we want to find the parent mlid.
 * @param $langcode
 *   The language of the term.
 *
 * @return $plid
 *   The corresponding parent mlid.
 */
function taxonomy_menu_term_get_plid($term, $langcode) {
  $plid = 0;
  $parents = taxonomy_term_load_parents($term
    ->id());
  if (empty($parents)) {

    // Try to get the vocabulary parent from the settings
    // Returns for example:
    //  - "0:0" : DISABLED
    //  - "main-menu:0" : MENU ROOT
    //  - "main-menu:123" : MENU ITEM ROOT
    $vocab_parent = taxonomy_menu_variable_get('vocab_parent', $term
      ->bundle(), NULL);
    if ($vocab_parent) {

      // "main-menu:123" case
      $plid = $vocab_parent;
    }
    else {

      // "main-menu:0" OR "0:0" cases
      $plid = 0;
    }
  }
  else {

    // We have parents for this taxonomy term. Only get the first one, we don't
    // support multiple parents yet.
    // @TODO Support multiple parents.
    foreach ($parents as $parent) {
      $plid = _taxonomy_menu_get_mlid($parent
        ->id(), $term
        ->bundle(), $langcode);
      break;
    }
    if ($plid == FALSE) {
      $plid = 0;
    }
  }
  return $plid;
}

/**
 * Saves a menu link in a menu, based on a taxonomy term.
 *
 * @param $term
 *   A taxonomy term used to save a respective menu item.
 * @param $menu_name
 *   The machine name of the menu in which the menu link should be saved.
 *
 * @return
 *   The menu link ID of the menu item that has been saved. FALSE, if no item
 *   could be saved.
 */
function taxonomy_menu_menu_link_save($term, $menu_name) {
  $mlid = FALSE;

  // Prepare a menu link based on the settings of the vocabulary edit page and
  // save it.
  $menu_link = taxonomy_menu_menu_link_prepare($term, $menu_name);
  drupal_alter('taxonomy_menu_link', $menu_link, $term, $menu_name);
  if (menu_link_save($menu_link)) {
    $mlid = entity_load_by_uuid('menu_link', $menu_link->uuid)->mlid;

    // Let other modules perform actions after the menu item has been saved.
    foreach (\Drupal::moduleHandler()
      ->getImplementations('taxonomy_menu_save') as $module) {
      $function = $module . '_taxonomy_menu_save';
      $function($term, $menu_link, $mlid);
    }
  }
  return $mlid;
}

/**
 * Implements hook_taxonomy_menu_save().
 *
 * Updates {taxonomy_menu} table using the newly created menu item.
 */
function taxonomy_menu_taxonomy_menu_save($term, $menu_link, $mlid) {
  if ($menu_link->taxonomy_menu['update'] == FALSE) {
    _taxonomy_menu_insert_menu_item($mlid, $term
      ->id(), $term
      ->bundle(), $menu_link->language);
  }
}

/**
 * Implements hook_taxonomy_vocabulary_delete().
 */
function taxonomy_menu_taxonomy_vocabulary_delete(Vocabulary $vocabulary) {
  taxonomy_menu_menu_links_delete($vocabulary
    ->id());
}

/**
 * Implements hook_taxonomy_term_insert($term).
 */
function taxonomy_menu_taxonomy_term_insert(Term $term) {
  _taxonomy_menu_termapi_helper($term, 'insert');
}

/**
 * Implements hook_taxonomy_term_update().
 */
function taxonomy_menu_taxonomy_term_update(Term $term) {
  _taxonomy_menu_termapi_helper($term, 'update');
}

/**
 * Implements hook_taxonomy_term_delete().
 */
function taxonomy_menu_taxonomy_term_delete(Term $term) {
  _taxonomy_menu_termapi_helper($term, 'delete');
}

/**
 * Implements hook_node_insert().
 *
 * @TODO Update the menu items count, empty terms.
 */
function taxonomy_menu_node_insert(EntityInterface $node) {
}

/**
 * Implements hook_node_update().
 *
 * @TODO Update the menu items count, empty terms.
 */
function taxonomy_menu_node_update(EntityInterface $node) {
}

/**
 * Implements hook_node_presave().
 *
 * @TODO Update the menu items count, empty terms.
 */
function taxonomy_menu_node_presave(EntityInterface $node) {
}

/**
 * Implements hook_node_delete().
 *
 * @TODO Update the menu items count, empty terms.
 */
function taxonomy_menu_node_delete(EntityInterface $node) {
}

/**
 * Abstraction of hook_node_<op>().
 *
 * @param $node
 *   The node to process.
 * @param $operation
 *   A string of the operation to be performed [update|insert|delete].
 *
 * @TODO Rebuild the function.
 */
function _taxonomy_menu_nodeapi_helper($node, $operation) {
  $terms = array();

  // Update the taxonomy menu for each terms
  foreach ($terms as $key => $tid) {
    $menu_name = taxonomy_menu_variable_get('vocab_menu', $term->vid, FALSE);
    $sync = taxonomy_menu_variable_get('sync', $term->vid, TRUE);
    $display_num = taxonomy_menu_variable_get('display_num', $term->vid, TRUE);
    $hide_empty_terms = taxonomy_menu_variable_get('hide_empty_terms', $term->vid, FALSE);
    $term = entity_load('taxonomy_term', $tid);

    // taxonomy_term_load($tid) return FALSE if the term was not found
    // if taxonomy $term is false, then go to the next $term
    if (!$term) {
      continue;
    }
    if ($term && $menu_name && $sync && ($display_num || $hide_empty)) {
      switch ($operation) {
        case 'insert':
          break;
        case 'update':
          if ($hide_empty_terms) {
            _taxonomy_menu_update_all_parents($term, $menu_name);
          }
          break;
        case 'delete':
          break;
      }
    }

    // Report status.

    //drupal_set_message($message, 'status');

    // Rebuild the menu.
    \Drupal::state()
      ->set('menu_rebuild_needed', TRUE);
  }
}

/**
 * Abstraction of hook_termapi_<op>().
 *
 * @param $term
 *   The term to process.
 * @param $operation
 *   A string of the operation to be performed [update|insert|delete].
 */
function _taxonomy_menu_termapi_helper($term, $operation) {

  // Only sync if taxonomy_menu is enabled for this vocab and the 'sync'
  // option has been checked.
  $menu_name = taxonomy_menu_variable_get('vocab_menu', $term
    ->bundle(), 0);
  $sync = taxonomy_menu_variable_get('sync', $term
    ->bundle(), 0);
  if ($menu_name && $sync) {
    switch ($operation) {
      case 'insert':
        $text = 'Added term %term to taxonomy menu %menu_name.';
        break;
      case 'update':
        $text = 'Updated term %term in taxonomy menu %menu_name.';
        break;
      case 'delete':
        $text = 'Deleted term %term from taxonomy menu %menu_name.';
        break;
    }
    $message = t($text, array(
      '%term' => $term->name->value,
      '%menu_name' => $menu_name,
    ));
    if ($operation == 'delete') {
      $mlid = _taxonomy_menu_get_mlid($term
        ->id(), $term
        ->bundle());
      _taxonomy_menu_delete_item($term
        ->bundle(), $term
        ->id());
      menu_link_delete($mlid);
    }
    else {
      taxonomy_menu_menu_link_save($term, $menu_name);
    }
    drupal_set_message($message, 'status');
    \Drupal::state()
      ->set('menu_rebuild_needed', TRUE);
  }
}

/**
 * Update all parent items.
 *
 * @param $term
 *   The taxonomy term from which to update the parents.
 * @param $menu_name
 *   The menu name of the resulting menu links.
 */
function _taxonomy_menu_update_all_parents($term, $menu_name) {
  $parents = taxonomy_term_load_parents($term
    ->id());
  if ($parents) {
    _taxonomy_menu_save_menu_links_batch($parents, $menu_name);
  }
}

/**
 * Helper function to see if any of the children have any nodes.
 *
 * @param $tid
 * @param $vid
 *
 * @return boolean
 */
function _taxonomy_menu_children_has_nodes($tid, $vid, $return = FALSE) {
  $children = taxonomy_term_load_children($tid, $vid);
  foreach ($children as $tid => $term) {
    if (_taxonomy_menu_term_count($tid) > 0) {
      $return = TRUE;
    }
    else {
      $return = _taxonomy_menu_children_has_nodes($tid, $vid, $return);
    }
  }
  return $return;
}

/**
 * Calculates the number of nodes linked to a term. It can be either recursive
 * and process all the children or just for this very term.
 *
 * This is inspired by taxonomy_select_nodes from taxonomy.module.
 *
 * @param $tid
 *   The term ID.
 * @param $recursive
 *   Process all the children or not. Default is FALSE according to Drupal standard.
 *
 * @return int
 *   The number of nodes attached to a term and optionally its children.
 *
 * @TODO Make function recursive.
 */
function taxonomy_menu_term_count_nodes($tid, $recursive = FALSE) {
  if ($tid == 0 || !\Drupal::config('taxonomy.settings')
    ->get('maintain_index_table') ?: TRUE) {
    return FALSE;
  }
  if ($recursive) {

    //@TODO Make it recursive.
  }
  else {
    $query = db_select('taxonomy_index', 't');
    $query
      ->condition('tid', $tid);
    $query
      ->addField('t', 'nid');
    $query
      ->addField('t', 'tid');
    $count = $query
      ->countQuery()
      ->execute()
      ->fetchField();
  }
  return $count;
}

/**
 * Creates the path for the vid/tid combination.
 *
 * @param $vid
 * @param $tid
 *
 * @return string
 */
function taxonomy_menu_path_get($term) {

  // Get the path function for this vocabulary and run it.
  $function = taxonomy_menu_variable_get('path', $term
    ->bundle(), 'taxonomy_menu_path_default');
  return $function($term);
}

/**
 * Implements hook_taxonomy_menu_path().
 *
 * Invoked from taxonomy_menu_get_paths.
 *
 * @return array
 *   An array composed of the name of the function to be run to  build the path
 *   (key) and a description (value).
 */
function taxonomy_menu_taxonomy_menu_path() {
  $output = array(
    'taxonomy_menu_path_default' => t('Default (taxonomy/term/tid)'),
    'taxonomy_menu_path_multiple_terms' => t('Multiple terms (taxonomy/term/tid1+tid2+tid3...)'),
  );
  return $output;
}

/**
 * Callback for hook_taxonomy_menu_path.
 */
function taxonomy_menu_path_default($term) {

  // When tid equals 0, we are dealing with a vocabulary item. We cannot use
  // default path with vocabulary items.
  if ($term
    ->id() == 0) {
    return FALSE;
  }
  else {
    $path = 'taxonomy/term/' . $term
      ->id();
  }
  return $path;
}

/**
 * Callback for hook_taxonomy_menu_path.
 */
function taxonomy_menu_path_multiple_terms($term) {

  // When tid equals 0, we are dealing with a vocabulary item. We want the path
  // to be a mulitple term path.
  if ($term
    ->id() == 0) {
    $tids = _taxonomy_menu_get_tids($term
      ->bundle());
  }
  else {
    $tids = array(
      $term
        ->id(),
    );
    $terms = taxonomy_get_tree($term
      ->bundle(), $term
      ->id());
    foreach ($terms as $term) {
      $tids[] = $term->tid;
    }
  }

  // Build the path.
  if ($tids) {
    $path = 'taxonomy/term/' . implode('+', $tids);
  }
  else {
    $uri = $term
      ->uri();
    $path = !empty($uri['path']) ? $uri['path'] : 'taxonomy/term/' . $term->tid;
  }
  return $path;
}

/**
 * Creates the path to use in a menu item.
 *
 * @return array
 *   An array of paths' selections.
 */
function taxonomy_menu_get_paths() {
  return \Drupal::moduleHandler()
    ->invokeAll('taxonomy_menu_path');
}

/**
 * Builds a variable from the supplied name and machine name of the vocabulary.
 *
 * @param $name
 *   The name of the variable.
 * @param $vid
 *   The vocabulary id from which the machine name will be taken.
 *
 * @return
 *   A variable name if a vocabulary could be found from the vid, FALSE otherwise.
 */
function _taxonomy_menu_build_variable($name, $vid) {
  if ($vocabulary = entity_load('taxonomy_vocabulary', $vid)) {
    return 'taxonomy_menu_' . $name . '_' . $vocabulary->vid;
  }
  else {
    return FALSE;
  }
}

/**
 * Helper function to replace Drupal's config for Taxonomy menu.
 * Gets a value per vocabulary.
 */
function taxonomy_menu_variable_get($key, $vid, $default) {
  $config = \Drupal::config('taxonomy_menu.settings')
    ->get(_taxonomy_menu_build_variable($key, $vid));
  if (isset($config)) {
    return $config;
  }
  return $default;
}

/**
 * Helper function to replace Drupal's config for Taxonomy menu.
 * Sets a value per vocabulary.
 */
function taxonomy_menu_variable_set($key, $vid, $value) {
  Drupal::config('taxonomy_menu.settings')
    ->set(_taxonomy_menu_build_variable($key, $vid), $value)
    ->save();
}

Functions

Namesort descending Description
taxonomy_menu_existing_menu_link_load Loads an existing menu link or creates an initialized one from a taxonomy term.
taxonomy_menu_form_taxonomy_vocabulary_form_alter Implements hook_form_FORM_ID_alter().
taxonomy_menu_get_paths Creates the path to use in a menu item.
taxonomy_menu_help Implements hook_help().
taxonomy_menu_items_rebuild Rebuilds all the menu items.
taxonomy_menu_menu_links_delete Deletes all the menu links associated to a vocabulary.
taxonomy_menu_menu_links_insert Inserts menu links associated to a vocabulary.
taxonomy_menu_menu_links_update Updates menu links associated to a vocabulary.
taxonomy_menu_menu_link_prepare Prepares a taxonomy item to be saved as a menu link.
taxonomy_menu_menu_link_save Saves a menu link in a menu, based on a taxonomy term.
taxonomy_menu_node_delete Implements hook_node_delete().
taxonomy_menu_node_insert Implements hook_node_insert().
taxonomy_menu_node_presave Implements hook_node_presave().
taxonomy_menu_node_update Implements hook_node_update().
taxonomy_menu_path_default Callback for hook_taxonomy_menu_path.
taxonomy_menu_path_get Creates the path for the vid/tid combination.
taxonomy_menu_path_multiple_terms Callback for hook_taxonomy_menu_path.
taxonomy_menu_taxonomy_menu_path Implements hook_taxonomy_menu_path().
taxonomy_menu_taxonomy_menu_save Implements hook_taxonomy_menu_save().
taxonomy_menu_taxonomy_term_delete Implements hook_taxonomy_term_delete().
taxonomy_menu_taxonomy_term_insert Implements hook_taxonomy_term_insert($term).
taxonomy_menu_taxonomy_term_update Implements hook_taxonomy_term_update().
taxonomy_menu_taxonomy_vocabulary_delete Implements hook_taxonomy_vocabulary_delete().
taxonomy_menu_term_count_nodes Calculates the number of nodes linked to a term. It can be either recursive and process all the children or just for this very term.
taxonomy_menu_term_get_plid Helper function to determine a parent mlid for a specific taxonomy term, in function of the settings of the administration pages.
taxonomy_menu_variable_get Helper function to replace Drupal's config for Taxonomy menu. Gets a value per vocabulary.
taxonomy_menu_variable_set Helper function to replace Drupal's config for Taxonomy menu. Sets a value per vocabulary.
_taxonomy_menu_build_variable Builds a variable from the supplied name and machine name of the vocabulary.
_taxonomy_menu_children_has_nodes Helper function to see if any of the children have any nodes.
_taxonomy_menu_nodeapi_helper Abstraction of hook_node_<op>().
_taxonomy_menu_termapi_helper Abstraction of hook_termapi_<op>().
_taxonomy_menu_update_all_parents Update all parent items.