You are here

hs_taxonomy.module in Hierarchical Select 6.3

Same filename and directory in other branches
  1. 5.3 modules/hs_taxonomy.module
  2. 7.3 modules/hs_taxonomy.module

Implementation of the Hierarchical Select API for the Taxonomy module.

File

modules/hs_taxonomy.module
View source
<?php

/**
 * @file
 * Implementation of the Hierarchical Select API for the Taxonomy module.
 */

//----------------------------------------------------------------------------

// Drupal core hooks.
// Add per-vocabulary settings for Hierarchical Select.
function hs_taxonomy_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
  module_load_include('inc', 'hierarchical_select', 'includes/common');
  $vid = isset($form['vid']) ? $form['vid']['#value'] : NULL;
  $vocabulary = $vid ? taxonomy_vocabulary_load($vid) : NULL;

  // Don't add the per-vocabulary settings for Hierarchical Select when the
  // vocabulary still needs to be created or is a freetagging vocabulary.
  if (!$vid || $vocabulary->tags) {
    return;
  }
  if (variable_get("taxonomy_hierarchical_select_{$vid}", 0)) {
    $form['settings']['tags']['#attributes']['disabled'] = TRUE;
    $form['settings']['tags']['#description'] = t("This setting is irrelevant when you're using Hierarchical Select.\n      <br />Use Hierarchical Select's %editability-settings instead.", array(
      '%editability-settings' => t('Editability settings'),
    ));
    $form['settings']['multiple']['#attributes']['disabled'] = TRUE;
    $form['settings']['multiple']['#description'] = t("This setting is managed by the Hierarchical Select configuration, by\n      the %enable-dropbox setting.", array(
      '%enable-dropbox' => t('Enable the dropbox'),
    ));
  }
  $split = array_search('weight', array_keys($form)) + 1;
  $first_part = array_slice($form, 0, $split);
  $second_part = array_slice($form, $split);
  $form = $first_part;
  $form['hierarchical_select_status'] = array(
    '#type' => 'checkbox',
    '#title' => '<strong>' . t('Use the Hierarchical Select form element for this vocabulary.') . '</strong>',
    '#default_value' => variable_get("taxonomy_hierarchical_select_{$vid}", 0),
    '#description' => t('When checked, the %free_tagging and %multiple_values settings will
      be managed by the Hierarchical Select configuration.', array(
      '%free_tagging' => t('Free tagging'),
      '%multiple_values' => t('Multiple values'),
    )),
  );

  // Add the Hierarchical Select config form.
  $module = 'hs_taxonomy';
  $params = array(
    'vid' => $vid,
    'exclude_tid' => NULL,
    'root_term' => NULL,
    'entity_count_for_node_type' => NULL,
  );
  $config_id = "taxonomy-{$vid}";
  $defaults = array(
    // Enable the save_lineage setting by default if the multiple parents
    // vocabulary option is enabled.
    'save_lineage' => (int) ($vocabulary->hierarchy == 2),
    'editability' => array(
      'max_levels' => _hs_taxonomy_hierarchical_select_get_depth($vid),
    ),
  );
  $strings = array(
    'hierarchy' => t('vocabulary'),
    'hierarchies' => t('vocabularies'),
    'item' => t('term'),
    'items' => t('terms'),
    'item_type' => t('term type'),
    'entity' => t('node'),
    'entities' => t('nodes'),
  );
  $max_hierarchy_depth = _hs_taxonomy_hierarchical_select_get_depth($vid);
  $preview_is_required = $vocabulary->required;
  $form['hierarchical_select'] = hierarchical_select_common_config_form($module, $params, $config_id, $defaults, $strings, $max_hierarchy_depth, $preview_is_required);

  // The forum selection requires that only the deepest term is saved!
  // See http://drupal.org/node/241766#comment-808464.
  if ($vid == variable_get('forum_nav_vocabulary', -1)) {
    $form['hierarchical_select']['save_lineage']['#value'] = 0;
    $form['hierarchical_select']['save_lineage']['#attributes'] = array(
      'disabled' => 'disabled',
    );
    $form['hierarchical_select']['save_lineage']['#description'] .= '<br />' . t('This is the vocabulary that will be used for forum navigation and it
      <strong>always</strong> requires the %dont_save_lineage setting to be
      set!', array(
      '%dont_save_lineage' => t('Save only the deepest term'),
    ));
  }

  // Add the the submit handler for the Hierarchical Select config form.
  $parents = array(
    'hierarchical_select',
  );
  $form['#submit'][] = 'hierarchical_select_common_config_form_submit';
  $form['#hs_common_config_form_parents'] = $parents;

  // Add a validate callback to override the freetagging and multiple select
  // settings if necessary.
  $form['#validate'][] = 'hierarchical_select_taxonomy_form_vocabulary_validate';
  $form['#submit'][] = 'hierarchical_select_taxonomy_form_vocabulary_submit';

  // The original #submit callback(s) has/have to be executed afterwards.
  $form['#submit'] = array_merge($form['#submit'], $second_part['#submit']);
  $form += $second_part;
}

// The taxonomy term form.
function hs_taxonomy_form_taxonomy_form_term_alter(&$form, &$form_state) {

  // Don't alter the form when it's in confirmation mode.
  if (isset($form_state['confirm_delete']) || isset($form_state['confirm_parents'])) {
    return;
  }
  require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';

  // Build an appropriate config, that inherits the level_labels settings
  // from the vocabulary's Hierarchical Select config.
  $vid = $form['#vocabulary']['vid'];
  $vocabulary_config = hierarchical_select_common_config_get("taxonomy-{$vid}");
  $config = array(
    'module' => 'hs_taxonomy',
    'params' => array(
      'vid' => $vid,
      'exclude_tid' => $form['#term']['tid'],
    ),
    'enforce_deepest' => 0,
    'save_lineage' => 0,
    'level_labels' => $vocabulary_config['level_labels'],
    'dropbox' => array(
      'status' => 1,
    ),
    'params' => array(
      'vid' => isset($form['vid']['#value']) ? $form['vid']['#value'] : NULL,
      'exclude_tid' => isset($form['tid']['#value']) ? $form['tid']['#value'] : NULL,
      'root_term' => TRUE,
    ),
  );

  // Use Hierarchical Select for selecting the parent term(s).
  $parent = isset($form['tid']['#value']) ? array_keys(taxonomy_get_parents($form['tid']['#value'])) : array();
  $form['advanced']['parent'] = array(
    '#type' => 'hierarchical_select',
    '#config' => $config,
    // Copied from core.
    '#title' => t('Parents'),
    '#description' => t('Parent terms') . '.',
    '#weight' => -15,
    '#default_value' => $parent,
  );
  $form['advanced']['parent']['#config']['dropbox']['title'] = t('All parent terms');

  // Use Hierarchical Select for selecting the related term(s).
  $related = isset($form['tid']['#value']) ? array_keys(taxonomy_get_related($form['tid']['#value'])) : array();
  $form['advanced']['relations'] = array(
    '#type' => 'hierarchical_select',
    '#config' => $config,
    // Copied from core.
    '#title' => t('Related terms'),
    '#weight' => -15,
    '#default_value' => $related,
  );
  $form['advanced']['relations']['#config']['dropbox']['title'] = t('All related terms');
}

// The forum 'container' form.
function hs_taxonomy_form_forum_form_container_alter(&$form, &$form_state) {
  unset($form['parent'][0]['#options']);
  unset($form['parent'][0]['#theme']);
  unset($form['parent'][0]['#required']);
  $form['parent'][0]['#type'] = 'hierarchical_select';
  $form['parent'][0]['#config'] = array(
    'module' => 'hs_taxonomy',
    'enforce_deepest' => 0,
    'save_lineage' => 0,
    'params' => array(
      'vid' => $form['vid']['#value'],
      'exclude_tid' => $form['tid']['#value'],
      'root_term' => TRUE,
    ),
  );
}

// The forum 'forum' form.
function hs_taxonomy_form_forum_form_forum_alter(&$form, &$form_state) {
  forum_form_container($form, $form_state);
}
function hs_taxonomy_form($vid, $value = 0, $help = NULL) {
  require_once drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
  $vocabulary = taxonomy_vocabulary_load($vid);
  if (module_exists('i18ntaxonomy')) {
    $title = check_plain(tt("taxonomy:vocabulary:{$vid}:name", $vocabulary->name));
    $description = check_plain($help ? $help : tt("taxonomy:vocabulary:{$vid}:help", $vocabulary->help));
  }
  else {
    $title = check_plain($vocabulary->name);
    $description = check_plain($help ? $help : $vocabulary->help);
  }
  $form_item = array(
    '#type' => 'hierarchical_select',
    '#config' => array(
      'module' => 'hs_taxonomy',
      'params' => array(
        'vid' => $vid,
        'exclude_tid' => NULL,
        'root_term' => NULL,
        'entity_count_for_node_type' => NULL,
      ),
    ),
    '#title' => $title,
    '#default_value' => $value,
    '#description' => $description,
    '#weight' => -15,
  );
  hierarchical_select_common_config_apply($form_item, "taxonomy-{$vid}");
  return $form_item;
}

/**
 * Implementation of hook_form_alter().
 */
function hs_taxonomy_form_alter(&$form, $form_state, $form_id) {

  // Change the term selection of nodes. Only affects hierarchical
  // vocabularies.
  if (isset($form['type']) && isset($form['#node']) && variable_get('taxonomy_override_selector', FALSE) && $form['type']['#value'] . '_node_form' == $form_id) {
    $node = $form['#node'];
    if (!isset($node->taxonomy)) {
      $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
    }
    else {

      // After preview the terms must be converted to objects.
      reset($node->taxonomy);
      if (!is_object(current($node->taxonomy))) {
        $node->taxonomy = taxonomy_preview_terms($node);
      }
      $terms = $node->taxonomy;
    }
    $c = db_query(db_rewrite_sql("SELECT v.* FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' ORDER BY v.weight, v.name", 'v', 'vid'), $node->type);
    while ($vocabulary = db_fetch_object($c)) {
      if ($vocabulary->tags) {
        if (isset($form_state['node_preview'])) {

          // Typed string can be changed by the user before preview,
          // so we just insert the tags directly as provided in the form.
          $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
        }
        else {
          $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
        }
        if ($vocabulary->help) {
          $help = filter_xss($vocabulary->help);
        }
        else {
          $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
        }
        $form['taxonomy']['tags'][$vocabulary->vid] = array(
          '#type' => 'textfield',
          '#title' => $vocabulary->name,
          '#description' => $help,
          '#required' => $vocabulary->required,
          '#default_value' => $typed_string,
          '#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
          '#weight' => $vocabulary->weight,
          '#maxlength' => 255,
        );
        if (module_exists('i18ntaxonomy')) {
          $vid = $vocabulary->vid;

          // Special treatment for tags, add some help texts
          $type = i18ntaxonomy_vocabulary($vid);
          if ($type == I18N_TAXONOMY_LOCALIZE || $type == I18N_TAXONOMY_TRANSLATE) {
            $form['taxonomy']['tags'][$vid]['#title'] = i18nstrings_string("taxonomy:vocabulary:{$vid}:name", $form['taxonomy']['tags'][$vid]['#title']);
            $form['taxonomy']['tags'][$vid]['#description'] = i18nstrings("taxonomy:vocabulary:{$vid}:help", $form['taxonomy']['tags'][$vid]['#description']);
          }
          if ($type == I18N_TAXONOMY_LOCALIZE) {
            $form['taxonomy']['tags'][$vid]['#description'] .= ' ' . t('This is a localizable vocabulary, so only terms in %language are allowed here.', array(
              '%language' => language_default('name'),
            ));
          }
        }
      }
      else {

        // Just looking at $vocabulary->required is not sufficient: the forum
        // module does not correctly set $vocabulary->required and hence the
        // forum vocabulary does not have it set. It's made required through
        // some hardcoded piece of Forms API logic. Therefore we have to do
        // more extensive checks to decide to make the form item required.
        $required = isset($form['taxonomy'][$vocabulary->vid]['#required']) ? $form['taxonomy'][$vocabulary->vid]['#required'] : $vocabulary->required;

        // Extract terms belonging to the vocabulary in question.
        $default_terms = array();
        foreach ($terms as $term) {

          // Free tagging has no default terms and also no vid after preview.
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
            $default_terms[$term->tid] = $term;
          }
        }
        $form_function = variable_get("taxonomy_hierarchical_select_{$vocabulary->vid}", 0) ? 'hs_taxonomy_form' : 'taxonomy_form';
        if ($form_function == 'hs_taxonomy_form' && module_exists('i18ntaxonomy')) {
          $vid = $vocabulary->vid;
          if (is_numeric($vid) && i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE) {

            // Rebuild this vocabulary's form.
            $vocabulary = taxonomy_vocabulary_load($vid);

            // Extract terms belonging to the vocabulary in question.
            $default_terms = array();
            foreach ($terms as $term) {
              if ($term->vid == $vid) {
                $default_terms[$term->tid] = $term;
              }
            }
            $form['taxonomy'][$vid] = i18ntaxonomy_vocabulary_form($vocabulary->vid, array_keys($default_terms));
            $form['taxonomy'][$vid]['#weight'] = $vocabulary->weight;
            $form['taxonomy'][$vid]['#required'] = $vocabulary->required;
            $form['taxonomy'][$vid]['#description'] = i18nstrings("taxonomy:vocabulary:{$vid}:help", $vocabulary->help);
          }
          elseif (is_numeric($vid) && i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_TRANSLATE) {

            // Rebuild this vocabulary's form.
            $vocabulary = taxonomy_vocabulary_load($vid);
            $form['taxonomy'][$vid]['#title'] = i18nstrings_string("taxonomy:vocabulary:{$vid}:name", $vocabulary->name);
            $form['taxonomy'][$vid]['#description'] = i18nstrings("taxonomy:vocabulary:{$vid}:help", $vocabulary->help);
          }
        }
        $form['taxonomy'][$vocabulary->vid] = $form_function($vocabulary->vid, array_keys($default_terms), filter_xss($vocabulary->help));
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
        $form['taxonomy'][$vocabulary->vid]['#required'] = $required;
      }
    }
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
      if (count($form['taxonomy']) > 1) {

        // Add fieldset only if form has more than 1 element.
        $form['taxonomy'] += array(
          '#type' => 'fieldset',
          '#title' => t('Vocabularies'),
          '#collapsible' => TRUE,
          '#collapsed' => FALSE,
        );
      }
      $form['taxonomy']['#weight'] = -3;
      $form['taxonomy']['#tree'] = TRUE;
    }
  }
}

//----------------------------------------------------------------------------

// Hierarchical Select hooks.

/**
 * Implementation of hook_hierarchical_select_params().
 */
function hs_taxonomy_hierarchical_select_params() {
  $params = array(
    'vid',
    'exclude_tid',
    // Allows a term to be excluded (necessary for the taxonomy_form_term form).
    'root_term',
    // Displays a fake "<root>" term in the root level (necessary for the taxonomy_form-term form).
    'entity_count_for_node_type',
  );
  return $params;
}

/**
 * Implementation of hook_hierarchical_select_root_level().
 */
function hs_taxonomy_hierarchical_select_root_level($params) {
  $terms = _hs_taxonomy_hierarchical_select_get_tree($params['vid'], 0, -1, 1);

  // If the root_term parameter is enabled, then prepend a fake "<root>" term.
  if (isset($params['root_term']) && $params['root_term'] === TRUE) {
    $root_term = new StdClass();
    $root_term->tid = 0;
    $root_term->name = '<' . t('root') . '>';
    $terms = array_merge(array(
      $root_term,
    ), $terms);
  }

  // Unset the term that's being excluded, if it is among the terms.
  if (isset($params['exclude_tid'])) {
    foreach ($terms as $key => $term) {
      if ($term->tid == $params['exclude_tid']) {
        unset($terms[$key]);
      }
    }
  }

  // If the Term Permissions module is installed, honor its settings.
  if (function_exists('term_permissions_allowed')) {
    global $user;
    foreach ($terms as $key => $term) {
      if (!term_permissions_allowed($term->tid, $user)) {
        unset($terms[$key]);
      }
    }
  }
  return _hs_taxonomy_hierarchical_select_terms_to_options($terms);
}

/**
 * Implementation of hook_hierarchical_select_children().
 */
function hs_taxonomy_hierarchical_select_children($parent, $params) {
  if (isset($params['root_term']) && $params['root_term'] && $parent == 0) {
    return array();
  }
  $terms = taxonomy_get_children($parent, $params['vid']);

  // Unset the term that's being excluded, if it is among the children.
  unset($terms[$params['exclude_tid']]);

  // If the Term Permissions module is installed, honor its settings.
  if (function_exists('term_permissions_allowed')) {
    global $user;
    foreach ($terms as $key => $term) {
      if (!term_permissions_allowed($term->tid, $user)) {
        unset($terms[$key]);
      }
    }
  }
  return _hs_taxonomy_hierarchical_select_terms_to_options($terms);
}

/**
 * Implementation of hook_hierarchical_select_lineage().
 */
function hs_taxonomy_hierarchical_select_lineage($item, $params) {
  $lineage = array();
  if (isset($params['root_term']) && $params['root_term'] && $item == 0) {
    return array(
      0,
    );
  }
  $terms = array_reverse(hs_taxonomy_get_parents_all($item));
  foreach ($terms as $term) {
    $lineage[] = $term->tid;
  }
  return $lineage;
}

/**
 * Alternative version of taxonomy_get_parents_all(): instead of using all
 * parents of a term (i.e. when multiple parents are being used), only the
 * first is kept.
 */
function hs_taxonomy_get_parents_all($tid) {
  $parents = array();
  if ($tid) {
    $parents[] = taxonomy_get_term($tid);
    $n = 0;
    while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
      $parents = array_merge($parents, array(
        reset($parent),
      ));
      $n++;
    }
  }
  return $parents;
}

/**
 * Implementation of hook_hierarchical_select_valid_item().
 */
function hs_taxonomy_hierarchical_select_valid_item($item, $params) {
  if (isset($params['root_term']) && $params['root_term'] && $item == 0) {
    return TRUE;
  }
  else {
    if (!is_numeric($item) || $item < 1 || isset($params['exclude_tid']) && $item == $params['exclude_tid']) {
      return FALSE;
    }
  }
  $term = taxonomy_get_term($item);

  // If the Term Permissions module is installed, honor its settings.
  if (function_exists('term_permissions_allowed')) {
    global $user;
    if (!term_permissions_allowed($term->tid, $user)) {
      return FALSE;
    }
  }
  return $term->vid == $params['vid'];
}

/**
 * Implementation of hook_hierarchical_select_item_get_label().
 */
function hs_taxonomy_hierarchical_select_item_get_label($item, $params) {
  static $labels = array();
  if (!isset($labels[$item])) {
    $term = taxonomy_get_term($item);

    // Use the translated term when available!
    if (module_exists('i18ntaxonomy') && i18ntaxonomy_vocabulary($term->vid) == I18N_TAXONOMY_LOCALIZE) {
      $labels[$item] = tt("taxonomy:term:{$term->tid}:name", $term->name);
    }
    else {
      $labels[$item] = t($term->name);
    }
  }
  return $labels[$item];
}

/**
 * Implementation of hook_hierarchical_select_create_item().
 */
function hs_taxonomy_hierarchical_select_create_item($label, $parent, $params) {
  $form_state['values'] = array(
    'name' => html_entity_decode($label, ENT_QUOTES),
    'parent' => $parent,
    'vid' => $params['vid'],
  );
  $status = taxonomy_save_term($form_state['values']);
  if ($status == SAVED_NEW) {

    // Reset the cached tree.
    _hs_taxonomy_hierarchical_select_get_tree($params['vid'], 0, -1, 1, TRUE);

    // Retrieve the tid.
    $children = taxonomy_get_children($parent, $params['vid']);
    foreach ($children as $tid => $term) {
      if ($term->name == $label) {
        return $tid;
      }
    }
  }
  else {
    return FALSE;
  }
}

/**
 * Implementation of hook_hierarchical_select_entity_count().
 */
function hs_taxonomy_hierarchical_select_entity_count($item, $params) {

  // Allow restricting entity count by node type.
  if ($params['entity_count_for_node_type']) {
    return hs_taxonomy_term_count_nodes($item, $params['entity_count_for_node_type']);
  }
  else {
    return hs_taxonomy_term_count_nodes($item);
  }
}

/**
 * Implementation of hook_hierarchical_select_implementation_info().
 */
function hs_taxonomy_hierarchical_select_implementation_info() {
  return array(
    'hierarchy type' => t('Taxonomy'),
    'entity type' => t('Node'),
  );
}

/**
 * Implementation of hook_hierarchical_select_config_info().
 */
function hs_taxonomy_hierarchical_select_config_info() {
  static $config_info;
  if (!isset($config_info)) {
    $config_info = array();
    $content_types = node_get_types();
    $vocabularies = taxonomy_get_vocabularies();
    foreach ($vocabularies as $vid => $vocabulary) {
      if (variable_get("taxonomy_hierarchical_select_{$vid}", 0)) {

        // Collect the human-readable names of each content type for which this
        // vocabulary is used.
        $entities = array();
        foreach ($vocabulary->nodes as $content_type) {
          $entities[] = $content_types[$content_type]->name;
        }
        $config_id = "taxonomy-{$vid}";
        $config_info[$config_id] = array(
          'config_id' => $config_id,
          'hierarchy type' => t('Taxonomy'),
          'hierarchy' => t($vocabulary->name),
          'entity type' => t('Node'),
          'entity' => implode(', ', array_map('t', $entities)),
          'context type' => t('Node form'),
          'context' => '',
          'edit link' => "admin/content/taxonomy/edit/vocabulary/{$vid}",
        );
      }
    }
  }
  return $config_info;
}

//----------------------------------------------------------------------------

// Token hooks.

/**
 * Implementation of hook_token_values().
 */
function hs_taxonomy_token_values($type, $object = NULL, $options = array()) {
  static $hs_vids;
  static $all_vids;
  $separator = variable_get('hs_taxonomy_separator', variable_get('pathauto_separator', '-'));
  $values = array();
  switch ($type) {
    case 'node':
      $node = $object;

      // Default values.
      $values['save-lineage-termpath'] = $values['save-lineage-termpath-raw'] = '';

      // If $node->taxonomy doesn't exist, these tokens cannot be created!
      if (!is_object($node) || !isset($node->taxonomy) || !is_array($node->taxonomy)) {
        return $values;
      }

      // Find out which vocabularies are using Hierarchical Select.
      if (!isset($hs_vids)) {
        $hs_vids = array();
        $result = db_query("SELECT SUBSTRING(name, 30, 3) AS vid FROM {variable} WHERE name LIKE 'taxonomy_hierarchical_select_%' AND value LIKE 'i:1\\;';");
        while ($o = db_fetch_object($result)) {
          $hs_vids[] = $o->vid;
        }
      }

      // Get a list of all existent vids, so we can generate an empty token
      // when a token is requested for a vocabulary that's not associated with
      // the current content type.
      if (!isset($all_vids)) {
        $all_vids = array();
        $result = db_query("SELECT vid FROM {vocabulary}");
        while ($row = db_fetch_object($result)) {
          $all_vids[] = $row->vid;
        }
      }

      // Generate the per-vid "save-lineage-termpath" tokens.
      foreach ($all_vids as $vid) {
        $terms = array();
        if (in_array($vid, $hs_vids) && isset($node->taxonomy[$vid])) {
          $selection = $node->taxonomy[$vid];
          $terms = _hs_taxonomy_token_termpath_for_vid($selection, $vid);
        }
        $terms_raw = $terms;
        $terms = array_map('check_plain', $terms);
        $values["save-lineage-termpath:{$vid}"] = !empty($options['pathauto']) ? $terms : implode($separator, $terms);
        $values["save-lineage-termpath-raw:{$vid}"] = !empty($options['pathauto']) ? $terms_raw : implode($separator, $terms_raw);
      }

      // We use the terms of the first vocabulary that uses Hierarchical
      // Select for the default "save-lineage-termpath" tokens.
      $vids = array_intersect(array_keys($node->taxonomy), $hs_vids);
      if (!empty($vids)) {
        $vid = $vids[0];
        $values['save-lineage-termpath'] = $values["save-lineage-termpath:{$vid}"];
        $values['save-lineage-termpath-raw'] = $values["save-lineage-termpath-raw:{$vid}"];
      }
      break;
  }
  return $values;
}

/**
 * Implementation of hook_token_list().
 */
function hs_taxonomy_token_list($type = 'all') {
  if ($type == 'node' || $type == 'all') {
    $tokens['node']['save-lineage-termpath'] = t('Only use when you have enabled the "save lineage" setting of Hierarchical Select. Will show the term\'s parent terms separated by /.');
    $tokens['node']['save-lineage-termpath-raw'] = t('As [save-linage-termpath]. WARNING - raw user input.');
    $tokens['node']['save-lineage-termpath:vid'] = t('Only has output when terms are present for the vocabulary with the specified vid. Only use when you have enabled the "save lineage" setting of Hierarchical Select. Will show the term\'s parent terms separated by /.');
    $tokens['node']['save-lineage-termpath-raw:vid'] = t('Only has output when terms are present for the vocabulary with the specified vid. As [save-linage-termpath]. WARNING - raw user input.');
    return $tokens;
  }
}

/**
 * Helper function for hs_taxonomy_token_values().
 */
function _hs_taxonomy_token_termpath_for_vid($selection, $vid) {
  $terms = array();
  $selection = is_array($selection) ? $selection : array(
    $selection,
  );

  // Generate the part we'll need of the Hierarchical Select configuration.
  $config = array(
    'module' => 'hs_taxonomy',
    'save_lineage' => 1,
    'params' => array(
      'vid' => $vid,
      'exclude_tid' => NULL,
      'root_term' => NULL,
    ),
  );

  // Validate all items in the selection, if any.
  if (!empty($selection)) {
    foreach ($selection as $key => $item) {
      $valid = module_invoke($config['module'], 'hierarchical_select_valid_item', $selection[$key], $config['params']);
      if (!$valid) {
        unset($selection[$key]);
      }
    }
  }

  // Generate a dropbox out of the selection. This will automatically
  // calculate all lineages for us.
  // If the selection is empty, then the tokens will be as well.
  if (!empty($selection)) {
    $dropbox = _hierarchical_select_dropbox_generate($config, $selection);

    // If no lineages could be generated, these tokens cannot be created!
    if (empty($dropbox->lineages)) {
      return $terms;
    }

    // We pick the first lineage.
    $lineage = $dropbox->lineages[0];

    // Finally, we build the tokens.
    foreach ($lineage as $item) {
      $terms[] = $item['label'];
    }
  }
  return $terms;
}

//----------------------------------------------------------------------------

// FAPI callbacks.

/**
 * Additional validate callback for the taxonomy_form_vocabulary form.
 */
function hierarchical_select_taxonomy_form_vocabulary_validate($form, &$form_state) {

  // The "multiple select" setting doesn't exist for the forum vocabulary!
  if ($form_state['values']['hierarchical_select_status'] && isset($form['settings']['multiple'])) {

    // Enable Taxonomy's "multiple select" setting when:
    // - Hierarchical Select's "multiple select" setting is enabled, or:
    // - Hierarchical Select's "save term lineage" setting is enabled
    $multiple_select_enabled = isset($form_state['values']['hierarchical_select']['dropbox']['status']) && $form_state['values']['hierarchical_select']['dropbox']['status'] || $form_state['values']['hierarchical_select']['save_lineage'];
    form_set_value($form['settings']['multiple'], (int) $multiple_select_enabled, $form_state);
  }

  // If Hierarchical Select is enabled, disable freetagging.
  if ($form_state['values']['hierarchical_select_status']) {
    form_set_value($form['settings']['tags'], 0, $form_state);
  }
}

/**
 * Additional submit callback for the taxonomy_form_vocabulary form.
 */
function hierarchical_select_taxonomy_form_vocabulary_submit($form, &$form_state) {
  $vid = $form_state['values']['vid'];
  variable_set("taxonomy_hierarchical_select_{$vid}", $form_state['values']['hierarchical_select_status'], $form_state);
}

//----------------------------------------------------------------------------

// Private functions.

/**
 * Drupal core's taxonomy_get_tree() doesn't allow us to reset the cached
 * trees, which obviously causes problems when you create new items between
 * two calls to it.
 */
function _hs_taxonomy_hierarchical_select_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL, $reset = FALSE) {
  static $children, $parents, $terms;
  if ($reset) {
    $children = $parents = $terms = array();
  }
  $depth++;

  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (!isset($children[$vid])) {
    $children[$vid] = array();
    $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN  {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
    while ($term = db_fetch_object($result)) {
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
    }
  }
  $max_depth = is_null($max_depth) ? count($children[$vid]) : $max_depth;
  if (isset($children[$vid][$parent])) {
    foreach ($children[$vid][$parent] as $child) {
      if ($max_depth > $depth) {
        $term = drupal_clone($terms[$vid][$child]);
        $term->depth = $depth;

        // The "parent" attribute is not useful, as it would show one parent only.
        unset($term->parent);
        $term->parents = $parents[$vid][$child];
        $tree[] = $term;
        if (isset($children[$vid][$child])) {
          $tree = array_merge($tree, _hs_taxonomy_hierarchical_select_get_tree($vid, $child, $depth, $max_depth));
        }
      }
    }
  }
  return isset($tree) ? $tree : array();
}

/**
 * Drupal core's taxonomy_term_count_nodes() is buggy. See
 * http://drupal.org/node/144969#comment-843000.
 */
function hs_taxonomy_term_count_nodes($tid, $type = 0) {
  static $count;
  $term = taxonomy_get_term($tid);
  $tree = _hs_taxonomy_hierarchical_select_get_tree($term->vid, $tid);
  $tids = array(
    $tid,
  );
  foreach ($tree as $descendant) {
    $tids[] = $descendant->tid;
  }
  if (!isset($count[$type][$tid])) {
    if (is_numeric($type)) {
      $count[$type][$tid] = db_result(db_query(db_rewrite_sql("SELECT COUNT(DISTINCT(n.nid)) AS count FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 AND t.tid IN (%s)"), implode(',', $tids)));
    }
    else {
      $count[$type][$tid] = db_result(db_query(db_rewrite_sql("SELECT COUNT(DISTINCT(n.nid)) AS count FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 AND n.type = '%s' AND t.tid IN (%s)"), $type, implode(',', $tids)));
    }
  }
  return $count[$type][$tid];
}

/**
 * Transform an array of terms into an associative array of options, for use
 * in a select form item.
 *
 * @param $terms
 *  An array of term objects.
 * @return
 *  An associative array of options, keys are tids, values are term names.
 */
function _hs_taxonomy_hierarchical_select_terms_to_options($terms) {
  $options = array();
  foreach ($terms as $key => $term) {

    // Use the translated term when available!
    if (module_exists('i18ntaxonomy') && isset($term->vid) && i18ntaxonomy_vocabulary($term->vid) == I18N_TAXONOMY_LOCALIZE) {
      $options[$term->tid] = tt("taxonomy:term:{$term->tid}:name", $term->name);
    }
    else {
      $options[$term->tid] = t($term->name);
    }
  }
  return $options;
}

/**
 * Get the depth of a vocabulary's tree.
 *
 * @param $vid
 *   A vocabulary id.
 * @return
 *   The depth of the vocabulary's tree.
 */
function _hs_taxonomy_hierarchical_select_get_depth($vid) {
  $depth = -99999;
  $tree = _hs_taxonomy_hierarchical_select_get_tree($vid);
  foreach ($tree as $term) {
    if ($term->depth > $depth) {
      $depth = $term->depth;
    }
  }
  return $depth;
}

Functions

Namesort descending Description
hierarchical_select_taxonomy_form_vocabulary_submit Additional submit callback for the taxonomy_form_vocabulary form.
hierarchical_select_taxonomy_form_vocabulary_validate Additional validate callback for the taxonomy_form_vocabulary form.
hs_taxonomy_form
hs_taxonomy_form_alter Implementation of hook_form_alter().
hs_taxonomy_form_forum_form_container_alter
hs_taxonomy_form_forum_form_forum_alter
hs_taxonomy_form_taxonomy_form_term_alter
hs_taxonomy_form_taxonomy_form_vocabulary_alter
hs_taxonomy_get_parents_all Alternative version of taxonomy_get_parents_all(): instead of using all parents of a term (i.e. when multiple parents are being used), only the first is kept.
hs_taxonomy_hierarchical_select_children Implementation of hook_hierarchical_select_children().
hs_taxonomy_hierarchical_select_config_info Implementation of hook_hierarchical_select_config_info().
hs_taxonomy_hierarchical_select_create_item Implementation of hook_hierarchical_select_create_item().
hs_taxonomy_hierarchical_select_entity_count Implementation of hook_hierarchical_select_entity_count().
hs_taxonomy_hierarchical_select_implementation_info Implementation of hook_hierarchical_select_implementation_info().
hs_taxonomy_hierarchical_select_item_get_label Implementation of hook_hierarchical_select_item_get_label().
hs_taxonomy_hierarchical_select_lineage Implementation of hook_hierarchical_select_lineage().
hs_taxonomy_hierarchical_select_params Implementation of hook_hierarchical_select_params().
hs_taxonomy_hierarchical_select_root_level Implementation of hook_hierarchical_select_root_level().
hs_taxonomy_hierarchical_select_valid_item Implementation of hook_hierarchical_select_valid_item().
hs_taxonomy_term_count_nodes Drupal core's taxonomy_term_count_nodes() is buggy. See http://drupal.org/node/144969#comment-843000.
hs_taxonomy_token_list Implementation of hook_token_list().
hs_taxonomy_token_values Implementation of hook_token_values().
_hs_taxonomy_hierarchical_select_get_depth Get the depth of a vocabulary's tree.
_hs_taxonomy_hierarchical_select_get_tree Drupal core's taxonomy_get_tree() doesn't allow us to reset the cached trees, which obviously causes problems when you create new items between two calls to it.
_hs_taxonomy_hierarchical_select_terms_to_options Transform an array of terms into an associative array of options, for use in a select form item.
_hs_taxonomy_token_termpath_for_vid Helper function for hs_taxonomy_token_values().