You are here

i18ntaxonomy.module in Internationalization 6

i18n taxonomy module

Internationalization (i18n) package.

This module groups together all existing i18n taxonomy functionality providing several options for taxonomy translation.

Translates taxonomy term for selected vocabularies running them through the localization system. It also translates terms for views filters and views results.

@author Jose A. Reyero, 2004

File

i18ntaxonomy/i18ntaxonomy.module
View source
<?php

/**
 * @file
 * i18n taxonomy module
 *
 * Internationalization (i18n) package.
 *
 * This module groups together all existing i18n taxonomy functionality
 * providing several options for taxonomy translation.
 *
 * Translates taxonomy term for selected vocabularies running them through the localization system.
 * It also translates terms for views filters and views results.
 *
 * @author Jose A. Reyero, 2004
 */

/**
 * Modes for multilingual vocabularies.
 */

// No multilingual options
define('I18N_TAXONOMY_NONE', 0);

// Localizable terms. Run through the localization system.
define('I18N_TAXONOMY_LOCALIZE', 1);

// Predefined language for a vocabulary and its terms.
define('I18N_TAXONOMY_LANGUAGE', 2);

// Per-language terms, translatable (referencing terms with different languages) but not localizable.
define('I18N_TAXONOMY_TRANSLATE', 3);

/**
 * Implementation of hook_help().
 */
function i18ntaxonomy_help($path, $arg) {
  switch ($path) {
    case 'admin/help#i18ntaxonomy':
      $output = '<p>' . t('This module adds support for multilingual taxonomy. You can set up multilingual options for each vocabulary:') . '</p>';
      $output .= '<ul>';
      $output .= '<li>' . t('A language can be assigned globaly for a vocabulary.') . '</li>';
      $output .= '<li>' . t('Different terms for each language with translation relationships.') . '</li>';
      $output .= '<li>' . t('Terms can be common to all languages, but may be localized.') . '</li>';
      $output .= '</ul>';
      $output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array(
        '@translate-interface' => url('admin/build/translate'),
      )) . '</p>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@i18n">Internationalization module</a>.', array(
        '@i18n' => 'http://drupal.org/node/133977',
      )) . '</p>';
      return $output;
    case 'admin/settings/language/i18n':
      $output = '<ul>';
      $output .= '<li>' . t('To set up multilingual options for vocabularies go to <a href="@configure_taxonomy">Taxonomy configuration page</a>.', array(
        '@configure_taxonomy' => url('admin/content/taxonomy'),
      )) . '</li>';
      $output .= '</ul>';
      return $output;
    case 'admin/content/taxonomy/%':
      $vocabulary = taxonomy_vocabulary_load($arg[3]);
      switch (i18ntaxonomy_vocabulary($vocabulary->vid)) {
        case I18N_TAXONOMY_LOCALIZE:
          return '<p>' . t('%capital_name is a localizable vocabulary. You will be able to translate term names and descriptions using the <a href="@translate-interface">translate interface</a> pages.', array(
            '%capital_name' => drupal_ucfirst($vocabulary->name),
            '%name' => $vocabulary->name,
            '@translate-interface' => url('admin/build/translate'),
          )) . '</p>';
        case I18N_TAXONOMY_LANGUAGE:
          return '<p>' . t('%capital_name is a vocabulary with a fixed language. All the terms in this vocabulary will have %language language.', array(
            '%capital_name' => drupal_ucfirst($vocabulary->name),
            '%name' => $vocabulary->name,
            '%language' => i18n_language_property($vocabulary->language, 'name'),
          )) . '</p>';
        case I18N_TAXONOMY_TRANSLATE:
          return '<p>' . t('%capital_name is a full multilingual vocabulary. You will be able to set a language for each term and create translation relationships.', array(
            '%capital_name' => drupal_ucfirst($vocabulary->name),
          )) . '</p>';
      }
  }
}

/**
 * Returns list of vocabulary modes.
 */
function _i18ntaxonomy_vocabulary_options() {
  return array(
    I18N_TAXONOMY_NONE => t('None. No multilingual options for this vocabulary.'),
    I18N_TAXONOMY_LOCALIZE => t('Localize terms. Terms are common for all languages, but their name and description may be localized.'),
    I18N_TAXONOMY_TRANSLATE => t('Per language terms. Different terms will be allowed for each language and they can be translated.'),
    I18N_TAXONOMY_LANGUAGE => t('Set language to vocabulary. The vocabulary will have a global language and it will only show up for pages in that language.'),
  );
}

/**
 * Implementation of hook_menu().
 */
function i18ntaxonomy_menu() {
  $items['admin/content/taxonomy/%taxonomy_vocabulary/translation'] = array(
    'title' => 'Translation',
    'page callback' => 'i18ntaxonomy_page_vocabulary',
    'page arguments' => array(
      3,
      5,
      6,
    ),
    'access callback' => '_i18ntaxonomy_translation_tab',
    'access arguments' => array(
      3,
    ),
    'type' => MENU_LOCAL_TASK,
    'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
    'file' => 'i18ntaxonomy.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_menu_alter().
 *
 * Take over the taxonomy pages
 */
function i18ntaxonomy_menu_alter(&$items) {

  // If ctool's page manager is active for the path skip this modules override.
  if (variable_get('page_manager_term_view_disabled', TRUE)) {

    // Taxonomy term page. Localize terms.
    $items['taxonomy/term/%']['module'] = 'i18ntaxonomy';
    $items['taxonomy/term/%']['page callback'] = 'i18ntaxonomy_term_page';
    $items['taxonomy/term/%']['file'] = 'i18ntaxonomy.pages.inc';
  }

  // Localize autocomplete
  $items['taxonomy/autocomplete']['module'] = 'i18ntaxonomy';
  $items['taxonomy/autocomplete']['page callback'] = 'i18ntaxonomy_autocomplete';
  $items['taxonomy/autocomplete']['file'] = 'i18ntaxonomy.pages.inc';
}

/**
 * Menu access callback. Show tab only for full multilingual vocabularies.
 */
function _i18ntaxonomy_translation_tab($vocabulary) {
  return i18ntaxonomy_vocabulary($vocabulary->vid) == I18N_TAXONOMY_TRANSLATE;
}

/**
 * Implementation of hook_locale().
 */
function i18ntaxonomy_locale($op = 'groups', $group = NULL) {
  switch ($op) {
    case 'groups':
      return array(
        'taxonomy' => t('Taxonomy'),
      );
    case 'info':
      $info['taxonomy']['refresh callback'] = 'i18ntaxonomy_locale_refresh';
      $info['taxonomy']['format'] = FALSE;
      return $info;
  }
}

/**
 * Refresh strings.
 */
function i18ntaxonomy_locale_refresh() {
  foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
    if (empty($vocabulary->language)) {
      i18nstrings_update("taxonomy:vocabulary:{$vid}:name", $vocabulary->name);
      if ($vocabulary->help) {
        i18nstrings_update("taxonomy:vocabulary:{$vid}:help", $vocabulary->help);
      }
    }
    if (i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE) {
      foreach (taxonomy_get_tree($vid, 0) as $term) {
        i18nstrings_update("taxonomy:term:{$term->tid}:name", $term->name);
        if ($term->description) {
          i18nstrings_update("taxonomy:term:{$term->tid}:description", $term->description);
        }
      }
    }
  }
  return TRUE;

  // Meaning it completed with no issues
}

/**
 * Implementation of hook_alter_translation_link().
 *
 * Replaces links with pointers to translated versions of the content.
 */
function i18ntaxonomy_translation_link_alter(&$links, $path) {
  if (preg_match("/^(taxonomy\\/term\\/)([^\\/]*)(.*)\$/", $path, $matches)) {

    //or at a taxonomy-listing?
    foreach ($links as $langcode => $link) {
      if ($str_tids = i18ntaxonomy_translation_tids($matches[2], $langcode)) {
        $links[$langcode]['href'] = "taxonomy/term/{$str_tids}" . $matches[3];
      }
    }
  }
}

/**
 * Implementation of hook_theme().
 */
function i18ntaxonomy_theme() {
  return array(
    'i18ntaxonomy_term_page' => array(
      'arguments' => array(
        'tids' => array(),
        'result' => NULL,
      ),
      'file' => 'i18ntaxonomy.pages.inc',
    ),
  );
}

/**
 * Translate term name
 * 
 * @param $tid
 *   Term id or term object
 * @param $name
 *   Filtered default(untranslated) name
 */
function i18ntaxonomy_translate_term_name($tid, $name = '', $langcode = NULL) {

  // If it is a term object we check for vocabulary options
  if (is_object($tid)) {
    return i18ntaxonomy_vocabulary($tid->vid) == I18N_TAXONOMY_LOCALIZE ? i18nstrings_string("taxonomy:term:{$tid->tid}:name", $tid->name, $langcode, TRUE) : check_plain($tid->name);
  }
  else {
    return i18nstrings_string("taxonomy:term:{$tid}:name", $name, $langcode);
  }
}

/**
 * Translate vocabulary name
 * 
 * @param $vid
 *   Vocabulary id or vocabulary object
 * @param $name
 *   Filtered default(untranslated) name
 */
function i18ntaxonomy_translate_vocabulary_name($vid, $name = '', $langcode = NULL) {
  return is_object($vid) ? i18nstrings_string("taxonomy:vocabulary:{$vid->vid}:name", $vid->name, $langcode, TRUE) : i18nstrings_string("taxonomy:vocabulary:{$vid}:name", $name, $langcode);
}

/**
 * Get translated term's tid.
 *
 * @param $tid
 *   Node nid to search for translation.
 * @param $language
 *   Language to search for the translation, defaults to current language.
 * @param $default
 *   Value that will be returned if no translation is found.
 * @return
 *   Translated term tid if exists, or $default.
 */
function i18ntaxonomy_translation_term_tid($tid, $language = NULL, $default = NULL) {
  $translation = db_result(db_query("SELECT t.tid FROM {term_data} t INNER JOIN {term_data} a ON t.trid = a.trid AND t.tid <> a.tid WHERE a.tid = %d AND t.language = '%s' AND t.trid > 0", $tid, $language ? $language : i18n_get_lang()));
  return $translation ? $translation : $default;
}

/**
 *  Returns an url for the translated taxonomy-page, if exists.
 */
function i18ntaxonomy_translation_tids($str_tids, $lang) {
  if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
    $separator = '+';

    // The '+' character in a query string may be parsed as ' '.
    $tids = preg_split('/[+ ]/', $str_tids);
  }
  elseif (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
    $separator = ',';
    $tids = explode(',', $str_tids);
  }
  else {
    return;
  }
  $translated_tids = array();
  foreach ($tids as $tid) {
    if ($translated_tid = i18ntaxonomy_translation_term_tid($tid, $lang)) {
      $translated_tids[] = $translated_tid;
    }
  }
  return implode($separator, $translated_tids);
}

/**
 * Implementation of hook_taxonomy().
 *
 * $edit parameter may be an array or an object !!
 */
function i18ntaxonomy_taxonomy($op, $type, $edit = NULL) {
  global $language;
  $edit = (array) $edit;
  switch ("{$type}/{$op}") {
    case 'term/insert':
    case 'term/update':
      switch (i18ntaxonomy_vocabulary($edit['vid'])) {
        case I18N_TAXONOMY_LOCALIZE:

          // Update strings for localizable vocabulary.
          $tid = $edit['tid'];
          i18nstrings_update("taxonomy:term:{$tid}:name", $edit['name']);
          i18nstrings_update("taxonomy:term:{$tid}:description", $edit['description']);
          break;
        case I18N_TAXONOMY_LANGUAGE:

          // Predefined language for all terms
          if (empty($edit['language']) && ($voc = taxonomy_vocabulary_load($edit['vid']))) {
            _i18ntaxonomy_term_set_lang($edit['tid'], $voc->language);
          }
          break;
        case I18N_TAXONOMY_TRANSLATE:

          // Multilingual terms, translatable
          if (empty($edit['language'])) {
            if (!empty($edit['i18ntaxonomy_form'])) {

              // Only for the case the term has no language, it may need to be removed from translation set
              _i18ntaxonomy_term_set_lang($edit['tid'], NULL);
            }
            elseif ($lang = _i18n_get_context_lang()) {

              // The term may come from a node tags field, just if this is not a taxonomy form
              _i18ntaxonomy_term_set_lang($edit['tid'], $lang);
            }
            else {

              // Not from the taxonomy form nor node form, set current language
              _i18ntaxonomy_term_set_lang($edit['tid'], $language->language);
            }
          }
          break;
      }
      break;
    case 'vocabulary/insert':
    case 'vocabulary/update':
      $vid = $edit['vid'];

      // Update vocabulary settings.
      if (isset($edit['i18nmode'])) {
        i18ntaxonomy_vocabulary($vid, $edit['i18nmode']);
        $edit_lang = isset($edit['language']) ? $edit['language'] : '';
        db_query("UPDATE {vocabulary} SET language='%s' WHERE vid = %d", $edit_lang, $edit['vid']);
        if ($edit_lang && $op == 'update') {
          db_query("UPDATE {term_data} SET language='%s' WHERE vid = %d", $edit_lang, $edit['vid']);
          drupal_set_message(t('Reset language for all terms.'));
        }

        // Always add vocabulary translation if !$language.
        if (!$edit_lang) {
          i18nstrings_update("taxonomy:vocabulary:{$vid}:name", $edit['name']);
        }
      }
      break;
    case 'term/delete':
      $tid = $edit['tid'];
      i18nstrings_remove_string("taxonomy:term:{$tid}:name");
      i18nstrings_remove_string("taxonomy:term:{$tid}:description");
      break;
    case 'vocabulary/delete':
      $vid = $edit['vid'];
      i18nstrings_remove_string("taxonomy:vocabulary:{$vid}:name");
      break;
  }
}

/**
 * Implementation of hook_db_rewrite_sql().
 */
function i18ntaxonomy_db_rewrite_sql($query, $primary_table, $primary_key) {

  // No rewrite for administration pages or mode = off.
  $mode = i18n_selection_mode();
  if ($mode == 'off' || arg(0) == 'admin') {
    return;
  }
  switch ($primary_table) {
    case 't':
    case 'v':

      // Taxonomy queries.
      // When loading specific terms, vocabs, language conditions shouldn't apply.
      if (preg_match("/WHERE.* {$primary_table}\\.tid\\s*(=\\s*\\d|IN)/", $query)) {
        return;
      }

      // Taxonomy for specific node, or when using the term_node table.
      if (preg_match("/WHERE r\\.nid = \\%d/", $query)) {
        return;
      }
      if (preg_match("/{term_node}/", $query)) {
        return;
      }
      $result['where'] = i18n_db_rewrite_where($primary_table, 'taxonomy', $mode);
      return $result;
  }
}

/**
 * Implementation of hook_form_alter().
 *
 * This is the place to add language fields to all forms.
 *
 * @ TO DO The vocabulary form needs some javascript.
 */
function i18ntaxonomy_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'taxonomy_overview_vocabularies':
      $vocabularies = taxonomy_get_vocabularies();
      $languages = locale_language_list('name');
      foreach ($vocabularies as $vocabulary) {
        if ($vocabulary->language) {
          $form[$vocabulary->vid]['types']['#value'] .= '&nbsp;(' . $languages[$vocabulary->language] . ')';
        }
      }
      break;
    case 'taxonomy_overview_terms':
      $mode = i18ntaxonomy_vocabulary($form['#vocabulary']['vid']);
      if ($mode == I18N_TAXONOMY_TRANSLATE) {
        $languages = locale_language_list('name');
        foreach (element_children($form) as $key) {
          if (isset($form[$key]['#term']) && ($lang = $form[$key]['#term']['language'])) {
            $form[$key]['view']['#value'] .= '&nbsp;(' . $languages[$lang] . ')';
          }
        }
      }
      break;
    case 'taxonomy_form_vocabulary':

      // Taxonomy vocabulary
      if (!empty($form['vid']['#value'])) {
        $vocabulary = taxonomy_vocabulary_load($form['vid']['#value']);
        $mode = i18ntaxonomy_vocabulary($vocabulary->vid);
      }
      else {
        $vocabulary = NULL;
        $mode = I18N_TAXONOMY_NONE;
      }
      drupal_add_js(drupal_get_path('module', 'i18ntaxonomy') . '/i18ntaxonomy.js');
      drupal_add_js(array(
        'i18ntaxonomy_vocabulary_form' => array(
          'I18N_TAXONOMY_LANGUAGE' => I18N_TAXONOMY_LANGUAGE,
        ),
      ), 'setting');
      $form['i18n'] = array(
        '#type' => 'fieldset',
        '#title' => t('Multilingual options'),
        '#collapsible' => TRUE,
        '#weight' => 0,
      );
      $form['i18n']['i18nmode'] = array(
        '#type' => 'radios',
        '#title' => t('Translation mode'),
        '#options' => _i18ntaxonomy_vocabulary_options(),
        '#default_value' => $mode,
        '#description' => t('For localizable vocabularies, to have all terms available for translation visit the <a href="@locale-refresh">translation refresh</a> page.', array(
          '@locale-refresh' => url('admin/build/translate/refresh'),
        )),
      );
      $form['i18n']['language'] = array(
        '#type' => 'select',
        '#title' => t('Language'),
        '#default_value' => $vocabulary && !empty($vocabulary->language) ? $vocabulary->language : '',
        '#options' => array(
          '' => '',
        ) + locale_language_list('name'),
        '#description' => t('Language for this vocabulary. If set, it will apply to all terms in this vocabulary.'),
        '#disabled' => $vocabulary && $mode != I18N_TAXONOMY_LANGUAGE,
      );
      $form['#validate'][] = 'i18ntaxonomy_form_vocabulary_validate';
      break;
    case 'taxonomy_form_term':

      // Taxonomy term
      // Check for confirmation forms
      if (isset($form_state['confirm_delete']) || isset($form_state['confirm_parents'])) {
        return;
      }
      $vocabulary = (object) $form['#vocabulary'];
      $term = (object) $form['#term'];

      // Mark form so we can know later when saving the term it came from a taxonomy form
      $form['i18ntaxonomy_form'] = array(
        '#type' => 'value',
        '#value' => 1,
      );

      // Add language field or not depending on taxonomy mode.
      switch (i18ntaxonomy_vocabulary($vocabulary->vid)) {
        case I18N_TAXONOMY_TRANSLATE:
          $form['identification']['language'] = array(
            '#type' => 'select',
            '#title' => t('Language'),
            '#default_value' => isset($term) && !empty($term->language) ? $term->language : '',
            '#options' => array(
              '' => '',
            ) + locale_language_list('name'),
            '#description' => t('This term belongs to a multilingual vocabulary. You can set a language for it.'),
          );
          break;
        case I18N_TAXONOMY_LANGUAGE:
          $form['language'] = array(
            '#type' => 'value',
            '#value' => $vocabulary->language,
          );
          $form['identification']['language_info'] = array(
            '#value' => t('All terms in this vocabulary have a fixed language: %language', array(
              '%language' => i18n_language_property($vocabulary->language, 'name'),
            )),
          );
          break;
        case I18N_TAXONOMY_LOCALIZE:
          $form['language'] = array(
            '#type' => 'value',
            '#value' => '',
          );
          $form['identification']['name']['#description'] .= ' <strong>' . t('This name will be localizable. You can translate it using the <a href="@translate-interface">translate interface</a> pages.', array(
            '@translate-interface' => url('admin/build/translate'),
          )) . '</strong>';
          $form['identification']['description']['#description'] .= ' <strong>' . t('This description will be localizable.  You can translate it using the <a href="@translate-interface">translate interface</a> pages.', array(
            '@translate-interface' => url('admin/build/translate'),
          )) . '</strong>';
          break;
        case I18N_TAXONOMY_NONE:
        default:
          $form['language'] = array(
            '#type' => 'value',
            '#value' => '',
          );
          break;
      }
      break;
    case 'search_form':

      // Localize category selector in advanced search form.
      if (!empty($form['advanced']) && !empty($form['advanced']['category'])) {
        i18ntaxonomy_form_all_localize($form['advanced']['category']);
      }
      break;
    default:
      if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] . '_node_form' == $form_id && ($node = $form['#node']) && isset($form['taxonomy']) && !variable_get('taxonomy_override_selector', FALSE)) {

        // Node form. Translate vocabularies.
        i18ntaxonomy_node_form($form);
      }
  }
}
function i18ntaxonomy_form_vocabulary_validate($form, &$form_state) {
  $language = !empty($form_state['values']['language']) ? $form_state['values']['language'] : '';
  $mode = $form_state['values']['i18nmode'];
  if ($mode != I18N_TAXONOMY_LANGUAGE && $language) {
    form_set_error('language', t('Setting a vocabulary language only makes sense in the "Set language to vocabulary" translation mode. Either change to this mode or do not select a language.'));
  }
  elseif ($mode == I18N_TAXONOMY_LANGUAGE && !$language) {
    form_set_error('language', t('If selecting "Set language to vocabulary" you need to set a language to this vocabulary. Either change the translation mode or select a language.'));
  }
}

/**
 * Localize a taxonomy_form_all() kind of control
 *
 * The options array is indexed by vocabulary name and then by term id, with tree structure
 * We just need to localize vocabulary name and localizable terms. Multilingual vocabularies
 * should have been taken care of by query rewriting.
 **/
function i18ntaxonomy_form_all_localize(&$item) {
  $options =& $item['#options'];
  foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
    if (!empty($options[$vocabulary->name])) {

      // Localize vocabulary name if translated
      $vname = i18ntaxonomy_translate_vocabulary_name($vocabulary->name);
      if ($vname != $vocabulary->name) {
        $options[$vname] = $options[$vocabulary->name];
        unset($options[$vocabulary->name]);
      }
      if (i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE) {
        $tree = taxonomy_get_tree($vid);
        if ($tree && count($tree) > 0) {
          foreach ($tree as $term) {
            if (isset($options[$vname][$term->tid])) {
              $options[$vname][$term->tid] = str_repeat('-', $term->depth) . i18ntaxonomy_translate_term_name($term->tid, $term->name);
            }
          }
        }
      }
    }
  }
}

/**
 * Handle node form taxonomy.
 */
function i18ntaxonomy_node_form(&$form) {
  $node = $form['#node'];
  if (!isset($node->taxonomy)) {
    $terms = taxonomy_node_get_terms($node);
  }
  else {
    $terms = $node->taxonomy;
  }

  // Regenerate the whole field for translatable vocabularies.
  foreach (element_children($form['taxonomy']) as $vid) {
    if ($vid == 'tags') {

      // Special treatment for tags, add some help texts
      foreach (element_children($form['taxonomy']['tags']) as $vid) {
        $type = i18ntaxonomy_vocabulary($vid);
        if ($type == I18N_TAXONOMY_LOCALIZE || $type == I18N_TAXONOMY_TRANSLATE) {
          $form['taxonomy']['tags'][$vid]['#title'] = i18ntaxonomy_translate_vocabulary_name($vid, $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'),
          ));
        }
      }
    }
    elseif (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'] = i18ntaxonomy_translate_vocabulary_name($vid, $vocabulary->name);
      $form['taxonomy'][$vid]['#description'] = i18nstrings("taxonomy:vocabulary:{$vid}:help", $vocabulary->help);
    }
  }
}

/**
 * Generate a form element for selecting terms from a vocabulary.
 * Translates all translatable strings.
 */
function i18ntaxonomy_vocabulary_form($vid, $value = 0, $help = NULL) {
  $vocabulary = taxonomy_vocabulary_load($vid);
  $help = $help ? $help : i18nstrings("taxonomy:vocabulary:{$vid}:help", $vocabulary->help);
  if (!$vocabulary->multiple) {
    $blank = $vocabulary->required ? t('- Please choose -') : t('- None selected -');
  }
  else {
    $blank = $vocabulary->required ? 0 : t('- None -');
  }
  $tree = i18ntaxonomy_localize_terms(taxonomy_get_tree($vid));
  return _i18ntaxonomy_term_select(i18ntaxonomy_translate_vocabulary_name($vocabulary), $value, $tree, $help, intval($vocabulary->multiple), $blank);
}

/**
 * Produces tree for taxonomy vocabularies.
 *
 * The difference with _taxonomy_term_select() is that this function is passed the term tree
 * that may be already localized or filtered by language
 */
function _i18ntaxonomy_term_select($title, $value, $tree, $description = '', $multiple = FALSE, $blank = '--', $exclude = array()) {
  $options = array();
  if ($blank) {
    $options[''] = $blank;
  }
  if ($tree) {
    foreach ($tree as $term) {
      if (!in_array($term->tid, $exclude)) {
        $choice = new stdClass();
        $choice->option = array(
          $term->tid => str_repeat('--', $term->depth) . $term->name,
        );
        $options[] = $choice;
      }
    }
  }
  return array(
    '#type' => 'select',
    '#title' => $title,
    '#default_value' => $value,
    '#options' => $options,
    '#description' => $description,
    '#multiple' => $multiple,
    '#size' => $multiple ? min(9, count($options)) : 0,
    '#weight' => -15,
    '#theme' => 'taxonomy_term_select',
  );
}

/**
 * Helper function for
 */

/**
 * Set language for a term. If no language set trid to 0 too.
 */
function _i18ntaxonomy_term_set_lang($tid, $langcode) {
  if ($langcode) {
    db_query("UPDATE {term_data} SET language='%s' WHERE tid = %d", $langcode, $tid);
  }
  else {
    db_query("UPDATE {term_data} SET language = '', trid = 0 WHERE tid = %d", $tid);
  }
}

/**
 * Multilingual Taxonomy.
 */

/**
 * Get term translations for multilingual terms. This works for multilingual vocabularies.
 *
 * @param $params
 *   Array of query conditions. I.e. array('tid' => xxx)
 * @param $getall
 *   Whether to get the original term too in the set or not.
 *
 * @return
 *   An array of the from lang => Term.
 */
function i18ntaxonomy_term_get_translations($params, $getall = TRUE) {
  foreach ($params as $field => $value) {
    $conds[] = "i.{$field} = '%s'";
    $values[] = $value;
  }
  if (!$getall) {

    // If not all, a parameter must be tid.
    $conds[] = "t.tid != %d";
    $values[] = $params['tid'];
  }
  $conds[] = "t.trid != 0";
  $sql = 'SELECT t.* FROM {term_data} t INNER JOIN {term_data} i ON t.trid = i.trid WHERE ' . implode(' AND ', $conds);
  $result = db_query($sql, $values);
  $items = array();
  while ($data = db_fetch_object($result)) {
    $items[$data->language] = $data;
  }
  return $items;
}

/**
 * Like nat_get_terms() but without caching.
 */
function i18ntaxonomy_nat_get_terms($nid) {
  $return = array();
  $result = db_query("SELECT td.* FROM {nat} n INNER JOIN {term_data} td USING (tid) WHERE n.nid = %d", $nid);
  while ($term = db_fetch_object($result)) {
    $return[$term->tid] = $term;
  }
  return $return;
}

/**
 * Implementation of hook_nodeapi().
 *
 * Prepare node for translation.
 */
function i18ntaxonomy_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'view':

      // This runs after taxonomy:nodeapi, so we just localize terms here.
      if (!empty($node->taxonomy)) {
        $node->taxonomy = i18ntaxonomy_localize_terms($node->taxonomy);
      }
      if ($node->type == 'forum' && ($vid = variable_get('forum_nav_vocabulary', '')) && i18ntaxonomy_vocabulary($vid)) {
        if ($page && taxonomy_node_get_terms_by_vocabulary($node, $vid) && ($tree = taxonomy_get_tree($vid))) {

          // Breadcrumb navigation
          $vocabulary = taxonomy_vocabulary_load($vid);
          $breadcrumb[] = l(t('Home'), NULL);
          $breadcrumb[] = l(i18nstrings("taxonomy:vocabulary:{$vid}:name", $vocabulary->name), 'forum');

          // Translate node taxonomy terms. Sometimes there are no terms, like for search results...
          if (!empty($node->taxonomy)) {

            // Get the forum terms from the (cached) tree
            foreach ($tree as $term) {
              $forum_terms[] = $term->tid;
            }
            foreach ($node->taxonomy as $term_id => $term) {
              if (in_array($term_id, $forum_terms)) {
                $node->tid = $term_id;
              }
            }
            if ($parents = taxonomy_get_parents_all($node->tid)) {
              $parents = array_reverse($parents);
              foreach ($parents as $p) {
                $breadcrumb[] = l(i18nstrings("taxonomy:term:{$term->tid}:name", $p->name), 'forum/' . $p->tid);
              }
            }
          }
          drupal_set_breadcrumb($breadcrumb);
        }
      }
      break;
    case 'prepare translation':
      $source = $node->translation_source;

      // Taxonomy translation.
      if (is_array($source->taxonomy)) {

        // Set translated taxonomy terms.
        $node->taxonomy = i18ntaxonomy_translate_terms($source->taxonomy, $node->language);
      }
      break;
  }
}

/**
 * Find all terms associated with the given node, ordered by vocabulary and term weight.
 * 
 * Same as taxonomy_node_get_terms() but without static caching.
 */
function i18ntaxonomy_node_get_terms($node, $key = 'tid') {
  $result = db_query(db_rewrite_sql('SELECT t.* FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.vid = %d ORDER BY v.weight, t.weight, t.name', 't', 'tid'), $node->vid);
  $terms = array();
  while ($term = db_fetch_object($result)) {
    $terms[$term->{$key}] = $term;
  }
  return $terms;
}

/**
 * Translate an array of taxonomy terms.
 *
 * Translates all terms with language, just passing over terms without it.
 * Filter out terms with a different language
 * 
 * @param $taxonomy
 *   Array of term objects or tids or multiple arrays or terms indexed by vid
 * @param $langcode
 *   Language code of target language
 * @param $fullterms
 *   Whether to return full $term objects, returns tids otherwise
 * @return
 *   Array with translated terms: tid -> $term
 *   Array with vid and term array
 */
function i18ntaxonomy_translate_terms($taxonomy, $langcode, $fullterms = TRUE) {
  $translation = array();
  if (is_array($taxonomy) && $taxonomy) {
    foreach ($taxonomy as $index => $tdata) {
      if (is_array($tdata)) {

        // Case 1: Index is vid, $tdata is an array of terms
        $mode = i18ntaxonomy_vocabulary($index);

        // We translate just some vocabularies: translatable, fixed language
        // Fixed language ones may have terms translated, though the UI doesn't support it
        if ($mode == I18N_TAXONOMY_LANGUAGE || $mode == I18N_TAXONOMY_TRANSLATE) {
          $translation[$index] = i18ntaxonomy_translate_terms($tdata, $langcode, $filter, $fullterms);
        }
        elseif ($fullterms) {
          $translation[$index] = array_map('_i18ntaxonomy_filter_terms', $tdata);
        }
        else {
          $translation[$index] = array_map('_i18ntaxonomy_filter_tids', $tdata);
        }
        continue;
      }
      elseif (is_object($tdata)) {

        // Case 2: This is a term object
        $term = $tdata;
      }
      elseif (is_numeric($tdata) && ($tid = (int) $tdata)) {

        // Case 3: This is a term tid, load the full term
        $term = taxonomy_get_term($tid);
      }

      // Translate the term if we got it
      if (empty($term)) {

        // Couldn't identify term, pass through whatever it is
        $translation[$index] = $tdata;
      }
      elseif ($term->language && $term->language != $langcode) {
        $translated_terms = i18ntaxonomy_term_get_translations(array(
          'tid' => $term->tid,
        ));
        if ($translated_terms && !empty($translated_terms[$langcode])) {
          $newterm = $translated_terms[$langcode];
          $translation[$newterm->tid] = $fullterms ? $newterm : $newterm->tid;
        }
      }
      else {

        // Term has no language. Should be ok.
        $translation[$index] = $fullterms ? $term : $term->tid;
      }
    }
  }
  return $translation;
}

/**
 * Localize taxonomy terms for localizable vocabularies.
 *
 * @param $terms
 *   Array of term objects.
 * @param $fields
 *   Object properties to localize.
 * @return
 *   Array of terms with the right ones localized.
 */
function i18ntaxonomy_localize_terms($terms, $fields = array(
  'name',
  'description',
)) {
  $localize = i18ntaxonomy_vocabulary(NULL, I18N_TAXONOMY_LOCALIZE);
  foreach ($terms as $index => $term) {
    if (in_array($term->vid, $localize)) {

      // Clone objects just in case one of them is saved later
      $terms[$index] = clone $term;
      foreach ($fields as $property) {
        $terms[$index]->{$property} = i18nstrings("taxonomy:term:{$term->tid}:{$property}", $term->{$property});
      }
    }
  }
  return $terms;
}

/**
 * Taxonomy vocabulary settings.
 *
 * - If $vid and not $value, returns mode for vid.
 * - If $vid and $value, sets mode for vid.
 * - If !$vid and !$value returns all settings.
 * - If !$vid and $value returns all vids for this mode.
 *
 * @param $vid
 *   Vocabulary id.
 * @param $value
 *   Vocabulary mode.
 *
 */
function i18ntaxonomy_vocabulary($vid = NULL, $mode = NULL) {
  $options = variable_get('i18ntaxonomy_vocabulary', array());
  if ($vid && !is_null($mode)) {
    $options[$vid] = $mode;
    variable_set('i18ntaxonomy_vocabulary', $options);
  }
  elseif ($vid) {
    return array_key_exists($vid, $options) ? $options[$vid] : I18N_TAXONOMY_NONE;
  }
  elseif (!is_null($mode)) {
    return array_keys($options, $mode);
  }
  else {
    return $options;
  }
}

/**
 * Returns a list for terms for vocabulary, language.
 *
 * @param $vid
 *   Vocabulary id
 * @param $lang
 *   Language code
 * @param $status
 *   'all' (default), 'translated', 'untranslated'
 */
function i18ntaxonomy_vocabulary_get_terms($vid, $lang, $status = 'all') {
  switch ($status) {
    case 'translated':
      $result = db_query("SELECT * FROM {term_data} WHERE vid = %d AND language = '%s' AND trid > 0", $vid, $lang);
      break;
    case 'untranslated':
      $result = db_query("SELECT * FROM {term_data} WHERE vid = %d AND language = '%s' AND trid = 0", $vid, $lang);
      break;
    default:
      $result = db_query("SELECT * FROM {term_data} WHERE vid = %d AND language = '%s'", $vid, $lang);
      break;
  }
  $list = array();
  while ($term = db_fetch_object($result)) {
    $list[$term->tid] = $term->name;
  }
  return $list;
}

/**
 * Get taxonomy tree for a given language
 *
 * @param $vid
 *   Vocabulary id
 * @param $lang
 *   Language code
 * @param $parent
 *   Parent term id for the tree
 */
function i18ntaxonomy_get_tree($vid, $lang, $parent = 0, $depth = -1, $max_depth = NULL) {
  static $children, $parents, $terms;
  $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][$lang])) {
    $children[$vid][$lang] = 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 AND t.language = '%s' ORDER BY weight, name", 't', 'tid'), $vid, $lang);
    while ($term = db_fetch_object($result)) {
      $children[$vid][$lang][$term->parent][] = $term->tid;
      $parents[$vid][$lang][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
    }
  }
  $max_depth = is_null($max_depth) ? count($children[$vid][$lang]) : $max_depth;
  $tree = array();
  if (!empty($children[$vid][$lang][$parent])) {
    foreach ($children[$vid][$lang][$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][$lang][$child];
        $tree[] = $term;
        if (!empty($children[$vid][$lang][$child])) {
          $tree = array_merge($tree, i18ntaxonomy_get_tree($vid, $lang, $child, $depth, $max_depth));
        }
      }
    }
  }
  return $tree;
}

/**
 * Implementation of hook_token_values().
 */
function i18ntaxonomy_token_values($type, $object = NULL, $options = array()) {
  $values = array();
  switch ($type) {
    case 'taxonomy':
      $term = $object;
      $values['i18n-term-raw'] = i18nstrings("taxonomy:term:{$term->tid}:name", $term->name);
      $values['i18n-term'] = check_plain(i18nstrings("taxonomy:term:{$term->tid}:name", $term->name));
      break;
    case 'node':
      $node = $object;

      // This code is copied from the token module which i adapting
      // pathauto's handling code; it's intended for compatability with it.
      if (!empty($node->taxonomy) && is_array($node->taxonomy)) {
        foreach ($node->taxonomy as $term) {
          $original_term = $term;
          if ((object) $term) {

            // With freetagging it's somewhat hard to get the tid, vid, name values
            // Rather than duplicating taxonomy.module code here you should make sure your calling module
            // has a weight of at least 1 which will run after taxonomy has saved the data which allows us to
            // pull it out of the db here.
            if (!isset($term->name) || !isset($term->tid)) {
              $vid = db_result(db_query_range("SELECT t.vid FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.nid = %d ORDER BY v.weight, t.weight, t.name", $object->nid, 0, 1));
              if (!$vid) {
                continue;
              }
              $term = db_fetch_object(db_query_range("SELECT t.tid, t.name FROM {term_data} t INNER JOIN {term_node} r ON r.tid = t.tid WHERE t.vid = %d AND r.nid = %d ORDER BY weight", $vid, $object->nid, 0, 1));
              $term->vid = $vid;
            }

            // Ok, if we still don't have a term name maybe this is a pre-taxonomy submit node
            // So if it's a number we can get data from it
            if (!isset($term->name) && is_array($original_term)) {
              $tid = array_shift($original_term);
              if (is_numeric($tid)) {
                $term = taxonomy_get_term($tid);
              }
            }
            $vid = $term->vid;

            // If term names are localizable, we translate them to the node's
            // content language, not to the interface language' in which the
            // current user is viewing the site. (Creation of node tokens should
            // not depend on 'unpredictable' conditions like these.)
            // If node is language neutral, language is set to NULL.
            if (i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE && $node->language) {
              $values['i18n-term-raw'] = i18nstrings("taxonomy:term:{$term->tid}:name", $term->name, $node->language);
              $values['i18n-term'] = check_plain(i18nstrings("taxonomy:term:{$term->tid}:name", $term->name, $node->language));
            }
            else {
              $values['i18n-term-raw'] = $term->name;
              $values['i18n-term'] = check_plain($term->name);
            }
            break;
          }
        }
      }

      // It's possible to leave that block and still not have good data.
      // So, we test for these and if not set, set them.
      if (!isset($values['i18n-term'])) {
        $values['i18n-term-raw'] = '';
        $values['i18n-term'] = '';
      }
      break;
  }
  return $values;
}

/**
 * Implementation of hook_token_list().
 */
function i18ntaxonomy_token_list($type = 'all') {
  if ($type == 'node' || $type == 'all' || $type == 'taxonomy') {
    $tokens['i18ntaxonomy']['i18n-term-raw'] = t("Unescaped term name translated using i18n");
    $tokens['i18ntaxonomy']['i18n-term'] = t("Escaped term name translated using i18n");
    return $tokens;
  }
}

/**
 * Translate forums list.
 */
function i18ntaxonomy_preprocess_forum_list(&$variables) {
  $vid = variable_get('forum_nav_vocabulary', '');
  if (i18ntaxonomy_vocabulary($vid)) {
    foreach ($variables['forums'] as $id => $forum) {
      $variables['forums'][$id]->description = i18nstrings('taxonomy:term:' . $forum->tid . ':description', $forum->description);
      $variables['forums'][$id]->name = i18nstrings('taxonomy:term:' . $forum->tid . ':name', $forum->name);
    }
  }
}

/**
 * Translate forum page.
 */
function i18ntaxonomy_preprocess_forums(&$variables) {
  $vid = variable_get('forum_nav_vocabulary', '');
  if (i18ntaxonomy_vocabulary($vid)) {
    if (isset($variables['links']['forum'])) {
      $variables['links']['forum']['title'] = i18nstrings('nodetype:type:forum:post_button', 'Post new Forum topic');
    }

    // This one is from advanced forum, http://drupal.org/project/advanced_forum
    if ($variables['forum_description']) {
      $variables['forum_description'] = i18nstrings('taxonomy:term:' . $variables['tid'] . ':description', $variables['forum_description']);
    }
    $vocabulary = taxonomy_vocabulary_load($vid);

    // Translate the page title.
    $title = !empty($vocabulary->name) ? i18ntaxonomy_translate_vocabulary_name($vocabulary) : '';
    drupal_set_title($title);

    // Translate the breadcrumb.
    $breadcrumb = array();
    $breadcrumb[] = l(t('Home'), NULL);
    $breadcrumb[] = l($title, 'forum');
    drupal_set_breadcrumb($breadcrumb);
  }
}

/**
 * Recursive array filtering, convert all terms to 'tid'.
 */
function _i18ntaxonomy_filter_tids($tid) {
  if (is_array($tid)) {
    return array_map('_i18n_taxonomy_filter_tids', $tid);
  }
  else {
    return is_object($tid) ? $tid->tid : (int) $tid;
  }
}

/**
 * Recursive array filtering, convert all terms to 'term object'
 */
function _i18ntaxonomy_filter_terms($term) {
  if (is_array($term)) {
    return array_map('_i18n_taxonomy_filter_terms', $term);
  }
  else {
    return is_object($term) ? $term : taxonomy_get_term($term);
  }
}

Functions

Namesort descending Description
i18ntaxonomy_db_rewrite_sql Implementation of hook_db_rewrite_sql().
i18ntaxonomy_form_all_localize Localize a taxonomy_form_all() kind of control
i18ntaxonomy_form_alter Implementation of hook_form_alter().
i18ntaxonomy_form_vocabulary_validate
i18ntaxonomy_get_tree Get taxonomy tree for a given language
i18ntaxonomy_help Implementation of hook_help().
i18ntaxonomy_locale Implementation of hook_locale().
i18ntaxonomy_locale_refresh Refresh strings.
i18ntaxonomy_localize_terms Localize taxonomy terms for localizable vocabularies.
i18ntaxonomy_menu Implementation of hook_menu().
i18ntaxonomy_menu_alter Implementation of hook_menu_alter().
i18ntaxonomy_nat_get_terms Like nat_get_terms() but without caching.
i18ntaxonomy_nodeapi Implementation of hook_nodeapi().
i18ntaxonomy_node_form Handle node form taxonomy.
i18ntaxonomy_node_get_terms Find all terms associated with the given node, ordered by vocabulary and term weight.
i18ntaxonomy_preprocess_forums Translate forum page.
i18ntaxonomy_preprocess_forum_list Translate forums list.
i18ntaxonomy_taxonomy Implementation of hook_taxonomy().
i18ntaxonomy_term_get_translations Get term translations for multilingual terms. This works for multilingual vocabularies.
i18ntaxonomy_theme Implementation of hook_theme().
i18ntaxonomy_token_list Implementation of hook_token_list().
i18ntaxonomy_token_values Implementation of hook_token_values().
i18ntaxonomy_translate_terms Translate an array of taxonomy terms.
i18ntaxonomy_translate_term_name Translate term name
i18ntaxonomy_translate_vocabulary_name Translate vocabulary name
i18ntaxonomy_translation_link_alter Implementation of hook_alter_translation_link().
i18ntaxonomy_translation_term_tid Get translated term's tid.
i18ntaxonomy_translation_tids Returns an url for the translated taxonomy-page, if exists.
i18ntaxonomy_vocabulary Taxonomy vocabulary settings.
i18ntaxonomy_vocabulary_form Generate a form element for selecting terms from a vocabulary. Translates all translatable strings.
i18ntaxonomy_vocabulary_get_terms Returns a list for terms for vocabulary, language.
_i18ntaxonomy_filter_terms Recursive array filtering, convert all terms to 'term object'
_i18ntaxonomy_filter_tids Recursive array filtering, convert all terms to 'tid'.
_i18ntaxonomy_term_select Produces tree for taxonomy vocabularies.
_i18ntaxonomy_term_set_lang Set language for a term. If no language set trid to 0 too.
_i18ntaxonomy_translation_tab Menu access callback. Show tab only for full multilingual vocabularies.
_i18ntaxonomy_vocabulary_options Returns list of vocabulary modes.

Constants