Skip to content
taxonomy.module 68.1 KiB
Newer Older
Dries Buytaert's avatar
Dries Buytaert committed
<?php
Kjartan Mannes's avatar
Kjartan Mannes committed
// $Id$
Dries Buytaert's avatar
 
Dries Buytaert committed

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

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Implement hook_permission().
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_permission() {
    'administer taxonomy' => array(
      'title' => t('Administer taxonomy'),
      'description' => t('Manage taxonomy vocabularies and terms.'),
    ),
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

  $return = array(
    'taxonomy_term' => array(
      'controller class' => 'TaxonomyTermController',
      'base table' => 'taxonomy_term_data',
      'fieldable' => TRUE,
      'object keys' => array(
        'id' => 'tid',
        'bundle' => 'vocabulary_machine_name',
      ),
      'bundle keys' => array(
        'bundle' => 'machine_name',
      ),
      'bundles' => array(),
  foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
    $return['taxonomy_term']['bundles'][$machine_name] = array(
      'label' => $vocabulary->name,
      'admin' => array(
        'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary',
        'real path' => 'admin/structure/taxonomy/' . $vocabulary->vid,
        'bundle argument' => 3,
        'access arguments' => array('administer taxonomy'),
      ),
    );
  }
  $return['taxonomy_vocabulary'] = array(
    'label' => t('Taxonomy vocabulary'),
    'controller class' => 'TaxonomyVocabularyController',
    'base table' => 'taxonomy_vocabulary',
    'object keys' => array(
      'id' => 'vid',
    ),
    'fieldable' => FALSE,
  );

  return $return;
}

/**
 * Implement hook_field_build_modes();
 *
 * @TODO: build mode for display as a field (when attached to nodes etc.).
 */
function taxonomy_field_build_modes($obj_type) {
  $modes = array();
  if ($obj_type == 'term') {
    $modes = array(
      'full' => t('Taxonomy term page'),
    );
  }
  return $modes;
}

 */
function taxonomy_theme() {
  return array(
    'taxonomy_term_select' => array(
      'arguments' => array('element' => NULL),
    'taxonomy_overview_vocabularies' => array(
      'arguments' => array('form' => array()),
    ),
    'taxonomy_overview_terms' => array(
      'arguments' => array('form' => array()),
    ),
    'field_formatter_taxonomy_term_link' => array(
      'arguments' => array('element' => NULL),
    ),
    'field_formatter_taxonomy_term_plain' => array(
      'arguments' => array('element' => NULL),
    ),
    'taxonomy_autocomplete' => array(
      'arguments' => array('element' => NULL),
    ),
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_node_view($node, $build_mode) {
    // Provide category information for RSS feeds.
    foreach ($node->taxonomy as $term) {
      $node->rss_elements[] = array(
        'key' => 'category',
        'value' => $term->name,
        'attributes' => array('domain' => url(taxonomy_term_path($term), array('absolute' => TRUE))),
      );
    }
  }
  else {
    $links = array();

    // If previewing, the terms must be converted to objects first.
      $node->taxonomy = taxonomy_preview_terms($node);
    }

    foreach ($node->taxonomy as $term) {
      // During preview the free tagging terms are in an array unlike the
      // other terms which are objects. So we have to check if a $term
      // is an object or not.
      if (is_object($term)) {
        $links['taxonomy_term_' . $term->tid] = array(
          'title' => $term->name,
          'href' => taxonomy_term_path($term),
          'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
        );
      }
      // Previewing free tagging terms; we don't link them because the
      // term-page might not exist yet.
      else {
        foreach ($term as $free_typed) {
          $typed_terms = drupal_explode_tags($free_typed);
          foreach ($typed_terms as $typed_term) {
            $links['taxonomy_preview_term_' . $typed_term] = array(
              'title' => $typed_term,
            );
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    $node->content['links']['terms'] = array(
      '#theme' => 'links',
      '#links' => $links,
      '#attributes' => array('class' => array('links', 'inline')),
/**
 * For vocabularies not maintained by taxonomy.module, give the maintaining
 * module a chance to provide a path for terms in that vocabulary.
 *
 * @param $term
 *   A term object.
 * @return
 *   An internal Drupal path.
 */
function taxonomy_term_path($term) {
  $vocabulary = taxonomy_vocabulary_load($term->vid);
  if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
    return $path;
  }
  return 'taxonomy/term/' . $term->tid;
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
    'title' => 'Taxonomy',
    'description' => 'Manage tagging, categorization, and classification of your content.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_overview_vocabularies'),
    'access arguments' => array('administer taxonomy'),
  $items['admin/structure/taxonomy/list'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );

    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_vocabulary'),
    'access arguments' => array('administer taxonomy'),
  $items['taxonomy/term/%taxonomy_term'] = array(
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
    'page callback' => 'taxonomy_term_page',
    'page arguments' => array(2),
    'access arguments' => array('access content'),
  $items['taxonomy/term/%taxonomy_term/view'] = array(
    'title' => 'View',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  $items['taxonomy/term/%taxonomy_term/edit'] = array(
    'title' => 'Edit term',
    'page callback' => 'taxonomy_term_edit',
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  $items['taxonomy/term/%taxonomy_term/feed'] = array(
    'title' => 'Taxonomy term',
    'title callback' => 'taxonomy_term_title',
    'title arguments' => array(2),
    'page callback' => 'taxonomy_term_feed',
    'page arguments' => array(2),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'page callback' => 'taxonomy_autocomplete',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  // TODO: remove with taxonomy_term_node_*
  $items['taxonomy/autocomplete/legacy'] = array(
    'title' => 'Autocomplete taxonomy',
    'page callback' => 'taxonomy_autocomplete_legacy',
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  $items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array(
    'title' => 'Vocabulary', // this is replaced by callback
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_vocabulary', 3),
    'title callback' => 'taxonomy_admin_vocabulary_title_callback',
    'title arguments' => array(3),
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_CALLBACK,
  $items['admin/structure/taxonomy/%taxonomy_vocabulary/edit'] = array(
  $items['admin/structure/taxonomy/%taxonomy_vocabulary/list'] = array(
    'title' => 'List terms',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_overview_terms', 3),
    'access arguments' => array('administer taxonomy'),
    'type' => MENU_LOCAL_TASK,
  $items['admin/structure/taxonomy/%taxonomy_vocabulary/list/add'] = array(
    'page callback' => 'drupal_get_form',
    'page arguments' => array('taxonomy_form_term', 3),
    'access arguments' => array('administer taxonomy'),
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  return $items;
}
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Return the vocabulary name given the vocabulary object.
 */
function taxonomy_admin_vocabulary_title_callback($vocabulary) {
  return check_plain($vocabulary->name);
}

/**
 * Save a vocabulary given a vocabulary object.
function taxonomy_vocabulary_save($vocabulary) {
  if (empty($vocabulary->nodes)) {
    $vocabulary->nodes = array();
  }
  if (!empty($vocabulary->name)) {
    // Prevent leading and trailing spaces in vocabulary names.
    $vocabulary->name = trim($vocabulary->name);
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  if (!isset($vocabulary->module)) {
    $vocabulary->module = 'taxonomy';
  if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
    db_delete('taxonomy_vocabulary_node_type')
      ->condition('vid', $vocabulary->vid)
      ->execute();

    if (!empty($vocabulary->nodes)) {
      $query = db_insert('taxonomy_vocabulary_node_type')
        ->fields(array('vid', 'type'));
      foreach ($vocabulary->nodes as $type => $selected) {
        $query->values(array(
          'vid' => $vocabulary->vid,
          'type' => $type,
        ));
      }
      $query->execute();
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);

    if (!empty($vocabulary->nodes)) {
      $query = db_insert('taxonomy_vocabulary_node_type')
        ->fields(array('vid', 'type'));
      foreach ($vocabulary->nodes as $type => $selected) {
        $query->values(array(
          'vid' => $vocabulary->vid,
          'type' => $type,
        ));
      }
      $query->execute();
Dries Buytaert's avatar
 
Dries Buytaert committed
    }
    field_attach_create_bundle($vocabulary->machine_name);
    module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  cache_clear_all();
  entity_get_controller('taxonomy_vocabulary')->resetCache();
Dries Buytaert's avatar
 
Dries Buytaert committed

Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Delete a vocabulary.
 *
 * @param $vid
 *   A vocabulary ID.
 * @return
 *   Constant indicating items were deleted.
 */
  $vocabulary = (array) taxonomy_vocabulary_load($vid);
Dries Buytaert's avatar
 
Dries Buytaert committed

  db_delete('taxonomy_vocabulary')
    ->condition('vid', $vid)
    ->execute();
  db_delete('taxonomy_vocabulary_node_type')
    ->condition('vid', $vid)
    ->execute();
  $result = db_query('SELECT tid FROM {taxonomy_term_data} WHERE vid = :vid', array(':vid' => $vid))->fetchCol();
  foreach ($result as $tid) {
    taxonomy_term_delete($tid);
Dries Buytaert's avatar
Dries Buytaert committed
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  field_attach_delete_bundle($vocabulary['machine_name']);
Dries Buytaert's avatar
 
Dries Buytaert committed
  module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  cache_clear_all();
  entity_get_controller('taxonomy_vocabulary')->resetCache();
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
}

 * Dynamically check and update the hierarchy flag of a vocabulary.
 * Checks the current parents of all terms in a vocabulary and updates the
 * vocabularies hierarchy setting to the lowest possible level. A hierarchy with
 * no parents in any of its terms will be given a hierarchy of 0. If terms
 * contain at most a single parent, the vocabulary will be given a hierarchy of
 * 1. If any term contain multiple parents, the vocabulary will be given a
 * @param $changed_term
 *   An array of the term structure that was updated.
 */
function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
  $hierarchy = 0;
  foreach ($tree as $term) {
    // Update the changed term with the new parent value before comparison.
    if ($term->tid == $changed_term['tid']) {
      $term = (object)$changed_term;
      $term->parents = $term->parent;
    }
    // Check this term's parent count.
    if (count($term->parents) > 1) {
      $hierarchy = 2;
      break;
    }
    elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) {
      $hierarchy = 1;
    }
  }
  if ($hierarchy != $vocabulary->hierarchy) {
    $vocabulary->hierarchy = $hierarchy;
    taxonomy_vocabulary_save($vocabulary);
 * Save a term object to the database.
 * @param $term
 *  A term object.
 * @return
 *   Status constant indicating if term was inserted or updated.
 */
function taxonomy_term_save($term) {
  if ($term->name) {
    // Prevent leading and trailing spaces in term names.
    $term->name = trim($term->name);
  }
  if (!isset($term->vocabulary_machine_name)) {
    $vocabulary = taxonomy_vocabulary_load($term->vid);
    $term->vocabulary_machine_name = $vocabulary->machine_name;
  }

  field_attach_presave('taxonomy_term', $term);
  if (!empty($term->tid) && $term->name) {
    $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
    field_attach_update('taxonomy_term', $term);
    module_invoke_all('taxonomy_term_update', $term);
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
  else {
    $status = drupal_write_record('taxonomy_term_data', $term);
    field_attach_insert('taxonomy_term', $term);
    module_invoke_all('taxonomy_term_insert', $term);
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
  db_delete('taxonomy_term_hierarchy')
    ->condition('tid', $term->tid)
    ->execute();
Dries Buytaert's avatar
Dries Buytaert committed

  if (!isset($term->parent) || empty($term->parent)) {
    $term->parent = array(0);
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
  $query = db_insert('taxonomy_term_hierarchy')
    ->fields(array('tid', 'parent'));
  if (is_array($term->parent)) {
    foreach ($term->parent as $parent) {
      if (is_array($parent)) {
        foreach ($parent as $tid) {
        $query->values(array(
          'tid' => $term->tid,
          'parent' => $parent
        ));
Dries Buytaert's avatar
Dries Buytaert committed
    }
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
    $query->values(array(
     'tid' => $term->tid,
     'parent' => $parent
    ));
Dries Buytaert's avatar
Dries Buytaert committed

  db_delete('taxonomy_term_synonym')
    ->condition('tid', $term->tid)
    ->execute();
  if (!empty($term->synonyms)) {
    $query = db_insert('taxonomy_term_synonym')
      ->fields(array('tid', 'name'));
    foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) {
Dries Buytaert's avatar
 
Dries Buytaert committed
      if ($synonym) {
        $query->values(array(
          'tid' => $term->tid,
          'name' => rtrim($synonym)
        ));
Dries Buytaert's avatar
 
Dries Buytaert committed
      }
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
  cache_clear_all();
Dries Buytaert's avatar
 
Dries Buytaert committed

Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Delete a term.
 *
 * @param $tid
 *   The term ID.
 * @return
 *   Status constant indicating deletion.
 */
function taxonomy_term_delete($tid) {
  $tids = array($tid);
  while ($tids) {
    $children_tids = $orphans = array();
    foreach ($tids as $tid) {
      // See if any of the term's children are about to be become orphans:
      if ($children = taxonomy_get_children($tid)) {
        foreach ($children as $child) {
          // If the term has multiple parents, we don't delete it.
          $parents = taxonomy_get_parents($child->tid);
          if (count($parents) == 1) {
            $orphans[] = $child->tid;
          }
        }
      }
Dries Buytaert's avatar
 
Dries Buytaert committed

      $term = taxonomy_term_load($tid);
Dries Buytaert's avatar
 
Dries Buytaert committed

      db_delete('taxonomy_term_data')
        ->condition('tid', $tid)
        ->execute();
      db_delete('taxonomy_term_hierarchy')
        ->condition('tid', $tid)
        ->execute();
      db_delete('taxonomy_term_synonym')
        ->condition('tid', $tid)
        ->execute();
      db_delete('taxonomy_term_node')
        ->condition('tid', $tid)
        ->execute();
Dries Buytaert's avatar
 
Dries Buytaert committed

      field_attach_delete('taxonomy_term', $term);
      module_invoke_all('taxonomy_term_delete', $term);
Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
  cache_clear_all();
Dries Buytaert's avatar
 
Dries Buytaert committed
}

/**
 * Clear all static cache variables for terms..
 */
function taxonomy_terms_static_reset() {
  drupal_static_reset('taxonomy_term_count_nodes');
  drupal_static_reset('taxonomy_get_tree');
  drupal_static_reset('taxonomy_get_synonym_root');
  entity_get_controller('taxonomy_term')->resetCache();
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Generate a form element for selecting terms from a vocabulary.
 *
 * @param $vid
 *   The vocabulary ID to generate a form element for
 * @param $value
 *   The existing value of the term(s) in this vocabulary to use by default.
 * @param $help
 *   Optional help text to use for the form element. If specified, this value
 *   MUST be properly sanitized and filtered (e.g. with filter_xss_admin() or
 *   check_plain() if it is user-supplied) to prevent XSS vulnerabilities. If
 *   omitted, the help text stored with the vocaulary (if any) will be used.
 * @return
 *   An array describing a form element to select terms for a vocabulary.
 *
 * @see _taxonomy_term_select()
 * @see filter_xss_admin()
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_form($vid, $value = 0, $help = NULL) {
  $vocabulary = taxonomy_vocabulary_load($vid);
  $help = ($help) ? $help : filter_xss_admin($vocabulary->help);
  if (!$vocabulary->multiple) {
    $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None selected -');
Kjartan Mannes's avatar
Kjartan Mannes committed
  }
  else {
    $blank = ($vocabulary->required) ? 0 : t('- None -');
  }
Dries Buytaert's avatar
 
Dries Buytaert committed

  return _taxonomy_term_select(check_plain($vocabulary->name), $value, $vid, $help, intval($vocabulary->multiple), $blank);
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

 * Generate a set of options for selecting a term from all vocabularies.
 */
function taxonomy_form_all($free_tags = 0) {
  $vocabularies = taxonomy_get_vocabularies();
  $options = array();
  foreach ($vocabularies as $vid => $vocabulary) {
    if ($vocabulary->tags && !$free_tags) {
      continue;
    }
        $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Return an array of all vocabulary objects.
 *
 * @param $type
 *   If set, return only those vocabularies associated with this node type.
 */
function taxonomy_get_vocabularies($type = NULL) {
  $conditions = !empty($type) ? array('type' => $type) : NULL;
  return taxonomy_vocabulary_load_multiple(FALSE, $conditions);
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

/**
 * Get names for all taxonomy vocabularies.
 *
 * @return
 *   An array of vocabulary ids, names, machine names, keyed by machine name.
 */
function taxonomy_vocabulary_get_names() {
  $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
Dries Buytaert's avatar
 
Dries Buytaert committed
 * Generate a form for selecting terms to associate with a node.
 * We check for taxonomy_override_selector before loading the full
 * vocabulary, so contrib modules can intercept before hook_form_alter
 *  and provide scalable alternatives.
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_form_alter(&$form, $form_state, $form_id) {
  if (!variable_get('taxonomy_override_selector', FALSE) && !empty($form['#node_edit_form'])) {
      $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
Kjartan Mannes's avatar
Kjartan Mannes committed
    }
    else {
      // After preview the terms must be converted to objects.
      if (isset($form_state['node_preview'])) {
        $node->taxonomy = taxonomy_preview_terms($node);
      }
Dries Buytaert's avatar
Dries Buytaert committed
    }
    $query = db_select('taxonomy_vocabulary', 'v');
    $query->join('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid');
    $query->addTag('term_access');
Dries Buytaert's avatar
Dries Buytaert committed

    $result = $query
      ->fields('v')
      ->condition('n.type', $node->type)
      ->orderBy('v.weight')
      ->orderBy('v.name')
      ->execute();
        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);
        }
          $help = filter_xss_admin($vocabulary->help);
          $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/legacy/' . $vocabulary->vid,
        // Extract terms belonging to the vocabulary in question.
        $default_terms = array();
          // Free tagging has no default terms and also no vid after preview.
          if (isset($term->vid) && $term->vid == $vocabulary->vid) {
        $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), filter_xss_admin($vocabulary->help));
        $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
        $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
    if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
        // Add fieldset only if form has more than 1 element.
        $form['taxonomy'] += array(
          '#type' => 'fieldset',
          '#collapsible' => TRUE,
          '#collapsed' => FALSE,
        );
      }
      $form['taxonomy']['#weight'] = -3;
      $form['taxonomy']['#tree'] = TRUE;
 * Helper function to convert terms after a preview.
 *
 * After preview the tags are an array instead of proper objects. This function
 * converts them back to objects with the exception of 'free tagging' terms,
 * because new tags can be added by the user before preview and those do not
 * yet exist in the database. We therefore save those tags as a string so
 * we can fill the form again after the preview.
 */
function taxonomy_preview_terms($node) {
  $taxonomy = array();
  if (isset($node->taxonomy)) {
    foreach ($node->taxonomy as $key => $term) {
      unset($node->taxonomy[$key]);
      // A 'Multiple select' and a 'Free tagging' field returns an array.
      if (is_array($term)) {
        foreach ($term as $tid) {
          if ($key == 'tags') {
            // Free tagging; the values will be saved for later as strings
            // instead of objects to fill the form again.
            $taxonomy['tags'] = $term;
          }
          else {
      // A 'Single select' field returns the term id.
      elseif ($term) {
        $taxonomy[$term] = taxonomy_term_load($term);
Dries Buytaert's avatar
Dries Buytaert committed
  }
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Find all terms associated with the given node, within one vocabulary.
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid') {
  $query = db_select('taxonomy_term_data', 't');
  $query->join('taxonomy_term_node', 'r', 'r.tid = t.tid');
  $query->addTag('term_access');

  $result = $query
    ->fields('t')
    ->condition('t.vid', $vid)
    ->condition('r.vid', $node->vid)
    ->orderBy('weight')
    ->execute();

Kjartan Mannes's avatar
Kjartan Mannes committed
  $terms = array();
Kjartan Mannes's avatar
Kjartan Mannes committed
    $terms[$term->$key] = $term;
Dries Buytaert's avatar
Dries Buytaert committed
  }
Kjartan Mannes's avatar
Kjartan Mannes committed
  return $terms;
}

/**
 * Find all term IDs associated with a set of nodes.
 *
 * @param $nodes
 *  An array of node objects.
 *
 * @return
 *  An array of term and node IDs ordered by vocabulary and term weight.
 */
function taxonomy_get_tids_from_nodes($nodes) {
  $node_vids = array();
  foreach ($nodes as $node) {
    $node_vids[] = $node->vid;
  }
  $query = db_select('taxonomy_term_node', 'r');
  $query->join('taxonomy_term_data', 't', 'r.tid = t.tid');
  $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
  return $query
    ->fields('r', array('tid', 'nid', 'vid'))
    ->condition('r.vid', $node_vids, 'IN')
    ->orderBy('v.weight')
    ->orderBy('t.weight')
    ->orderBy('t.name')
    ->execute()
    ->fetchAll();
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Find all terms associated with the given node, ordered by vocabulary and term weight.
Dries Buytaert's avatar
 
Dries Buytaert committed
 */
function taxonomy_node_get_terms($node, $key = 'tid') {
Dries Buytaert's avatar
Dries Buytaert committed

  if (!isset($terms[$node->vid][$key])) {
    $query = db_select('taxonomy_term_node', 'r');
    $query->join('taxonomy_term_data', 't', 'r.tid = t.tid');
    $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
    $query->addTag('term_access');

    $result = $query
      ->fields('r', array('tid', 'nid', 'vid'))
      ->condition('r.vid', $node->vid)
      ->orderBy('v.weight')
      ->orderBy('t.weight')
      ->orderBy('t.name')
      ->execute();
      $terms[$node->vid][$key][$term->$key] = $term;
Dries Buytaert's avatar
Dries Buytaert committed
    }
  }
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Save term associations for a given node.
 */
function taxonomy_node_save($node, $terms) {

  taxonomy_node_delete_revision($node);

  // Free tagging vocabularies do not send their tids in the form,
  // so we'll detect them here and process them independently.
  if (isset($terms['tags'])) {
    $typed_input = $terms['tags'];
    unset($terms['tags']);

    foreach ($typed_input as $vid => $vid_value) {
      $vocabulary = taxonomy_vocabulary_load($vid);
      $typed_terms = drupal_explode_tags($vid_value);
      foreach ($typed_terms as $typed_term) {
        // See if the term exists in the chosen vocabulary
        // and return the tid; otherwise, add a new record.
        $possibilities = taxonomy_get_term_by_name($typed_term);
        $typed_term_tid = NULL; // tid match, if any.
        foreach ($possibilities as $possibility) {
          if ($possibility->vid == $vid) {
            $typed_term_tid = $possibility->tid;
          }
        }

        if (!$typed_term_tid) {
          $edit = array(
            'vid' => $vid,
            'name' => $typed_term,
            'vocabulary_machine_name' => $vocabulary->machine_name,
          );
          $term = (object)$edit;
          $status = taxonomy_term_save($term);
          $typed_term_tid = $term->tid;
        // Defend against duplicate, differently cased tags
        if (!isset($inserted[$typed_term_tid])) {
          db_insert('taxonomy_term_node')
            ->fields(array(
              'nid' => $node->nid,
              'vid' => $node->vid,
              'tid' => $typed_term_tid
            ))
            ->execute();
          $inserted[$typed_term_tid] = TRUE;
        }
Dries Buytaert's avatar
Dries Buytaert committed

  if (is_array($terms) && !empty($terms)) {
    $query = db_insert('taxonomy_term_node')
      ->fields(array('nid', 'vid', 'tid'));
      if (is_array($term)) {
        foreach ($term as $tid) {
          if ($tid) {
            $query->values(array(
              'nid' => $node->nid,
              'vid' => $node->vid,
              'tid' => $tid,
            ));
        $query->values(array(
          'nid' => $node->nid,
          'vid' => $node->vid,
          'tid' => $term->tid,
        ));
        $query->values(array(
          'nid' => $node->nid,
          'vid' => $node->vid,
          'tid' => $term,
        ));
Dries Buytaert's avatar
Dries Buytaert committed
    }
Dries Buytaert's avatar
Dries Buytaert committed
  }
Kjartan Mannes's avatar
Kjartan Mannes committed
}
Dries Buytaert's avatar
Dries Buytaert committed

 * Implement hook_node_type_insert().
function taxonomy_node_type_insert($info) {
  drupal_static_reset('taxonomy_term_count_nodes');
}

/**
 * Implement hook_node_type_update().
 */
function taxonomy_node_type_update($info) {
  if (!empty($info->old_type) && $info->type != $info->old_type) {
    db_update('taxonomy_vocabulary_node_type')
      ->fields(array(
        'type' => $info->type,
      ))
      ->condition('type', $info->old_type)
      ->execute();
  drupal_static_reset('taxonomy_term_count_nodes');
}

/**
 * Implement hook_node_type_delete().
 */
function taxonomy_node_type_delete($info) {
  db_delete('taxonomy_vocabulary_node_type')
    ->condition('type', $info->type)
    ->execute();

  drupal_static_reset('taxonomy_term_count_nodes');
Dries Buytaert's avatar
 
Dries Buytaert committed
/**
 * Find all parents of a given term ID.
 */
function taxonomy_get_parents($tid, $key = 'tid') {
Kjartan Mannes's avatar
Kjartan Mannes committed
  if ($tid) {
    $query = db_select('taxonomy_term_data', 't');
    $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
    $query->addTag('term_access');