Calling all Drupal developers!
Help us get this on the first page of Digg. DIGG NOW!
Help us get this on the first page of Digg. DIGG NOW!
<?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);
// Run through the localization system
define('I18N_TAXONOMY_LOCALIZE', 1);
// Predefined language for all terms
define('I18N_TAXONOMY_LANGUAGE', 2);
// Multilingual terms, translatable
define('I18N_TAXONOMY_TRANSLATE', 3);
/**
* Implementation of hook_help().
*/
function i18ntaxonomy_help($section, $arg) {
switch ($section) {
case 'admin/help#i18ntaxonomy' :
$output = '<p>'.t('This module adds support for 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('For more information please read the <a href="@i18n">on-line help pages</a>.', array('@i18n' =>'http://drupal.org/node/31631')) .'</p>';
return $output;
case 'admin/settings/i18n':
$output = '<ul>';
$output .= '<li>'.t('To set up multilingual options for vocabularies go to !configure_taxonomy', array('!configure_taxonomy' => l(t('Taxonomy configuration page'), '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 localization interface.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'</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) {
// 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 'refresh':
if ($group == 'taxonomy') {
return i18ntaxonomy_locale_refresh();
}
}
}
/**
* Refresh strings
*/
function i18ntaxonomy_locale_refresh() {
foreach (taxonomy_get_vocabularies() as $vid => $vocabulary) {
if (i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE) {
tt("taxonomy:vocabulary:$vid:name", $vocabulary->name, NULL, TRUE);
if ($vocabulary->help) {
tt("taxonomy:vocabulary:$vid:help", $vocabulary->help, NULL, TRUE);
}
foreach (taxonomy_get_tree($vid, 0) as $term) {
tt("taxonomy:term:$term->tid:name", $term->name, NULL, TRUE);
if ($term->description) {
tt("taxonomy:term:$term->tid:description", $term->description, NULL, TRUE);
}
}
}
}
}
/**
* 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];
}
}
}
}
/**
* 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", $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);
}
else if (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) {
$edit = (array)$edit;
switch ("$type/$op") {
case 'term/insert':
case 'term/update':
$tid = $edit['tid'];
$language = isset($edit['language']) ? $edit['language'] : '';
db_query("UPDATE {term_data} SET language='%s' WHERE tid=%d", $language, $tid);
// From translation module
if (!$language && !empty($edit['trid'])) {
// Removed language, remove trid
db_query('UPDATE {term_data} SET trid = 0 WHERE tid = %d', $tid);
if(db_affected_rows()) drupal_set_message(t('Removed translation information from term'));
}
// Update strings for localizable vocabulary
if (i18ntaxonomy_vocabulary($edit['vid']) == I18N_TAXONOMY_LOCALIZE) {
tt("taxonomy:term:$tid:name", $edit['name'], NULL, TRUE);
tt("taxonomy:term:$tid:description", $edit['description'], NULL, TRUE);
}
break;
case 'vocabulary/insert':
case 'vocabulary/update':
$vid = $edit['vid'];
// Update vocabulary settings
if (isset($edit['i18nmode'])) {
i18ntaxonomy_vocabulary($vid, $edit['i18nmode']);
$language = isset($edit['language']) ? $edit['language'] : '';
db_query("UPDATE {vocabulary} SET language='%s' WHERE vid = %d", $language, $edit['vid']);
if ($language && $op == 'update') {
db_query("UPDATE {term_data} SET language='%s' WHERE vid = %d", $edit['language'], $edit['vid']);
drupal_set_message(t('Reset language for all terms.'));
}
// Always add vocabulary translation if !$language
if (!$language) {
tt("taxonomy:vocabulary:$vid:name", $edit['name'], NULL, TRUE);
}
}
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
if (preg_match("/WHERE r\.nid = \%d/", $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'] .= ' ('.$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'] .= ' ('.$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;
}
$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 !locale-refresh page', array('!locale-refresh' => l(t('translation refresh'), '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 vocbulary. If set, it will apply to all terms in this vocabulary.'),
'#disabled' => ($vocabulary && $mode != I18N_TAXONOMY_LANGUAGE),
);
break;
case 'taxonomy_form_term': // Taxonomy term
$vocabulary = (object)$form['#vocabulary'];
$term = (object)$form['#term'];
// 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 be localizable').'</strong>';
$form['identification']['description']['#description'] .= ' <strong>'.t('This description will be localizable').'</strong>';
break;
case I18N_TAXONOMY_NONE:
default:
$form['language'] = array('#type' => 'value', '#value' => '');
break;
}
break;
default:
if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id
&& ($node = $form['#node']) && isset($form['taxonomy']) ) {
// Node form. Translate vocabularies
i18ntaxonomy_node_form($form);
}
}
}
/**
* Handle node form taxonomy
*/
function i18ntaxonomy_node_form(&$form) {
$node = $form['#node'];
if (!isset($node->taxonomy)) {
if (!empty($node->nid)) {
$terms = taxonomy_node_get_terms($node->nid);
}
else {
$terms = array();
}
}
else {
$terms = $node->taxonomy;
}
// Regenerate the whole field for translatable vocabularies
foreach (element_children($form['taxonomy']) as $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;
}
}
}
/**
* Generate a form element for selecting terms from a vocabulary.
* Translates all translatable strings.
*/
function i18ntaxonomy_vocabulary_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') {
$vocabulary = taxonomy_vocabulary_load($vid);
$help = $vocabulary->help ? tt("taxonomy:vocabulary:$vid:help", $vocabulary->help) : '';
if ($vocabulary->required) {
$blank = 0;
}
else {
$blank = '<'. t('none') .'>';
}
return _i18ntaxonomy_term_select(check_plain(tt("taxonomy:vocabulary:$vid:name", $vocabulary->name)), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank);
}
// Produces translated tree
function _i18ntaxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
$tree = taxonomy_get_tree($vocabulary_id);
$options = array();
if ($blank) {
$options[0] = $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) . tt("taxonomy:term:$term->tid:name", $term->name));
$options[] = $choice;
}
}
if (!$blank && !$value) {
// required but without a predefined value, so set first as predefined
$value = $tree[0]->tid;
}
}
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',
);
}
/**
* Returns a list for terms for vocabulary, language
*/
function i18ntaxonomy_vocabulary_get_terms($vid, $lang, $status = 'all') {
switch($status){
case 'translated':
$andsql = ' AND trid > 0';
break;
case 'untranslated':
$andsql = ' AND trid = 0';
break;
default:
$andsql = '';
}
$result = db_query("SELECT * FROM {term_data} WHERE vid=%d AND language='%s' $andsql", $vid, $lang);
$list = array();
while ($term = db_fetch_object($result)) {
$list[$term->tid] = $term->name;
}
return $list;
}
/**
* Multilingual Taxonomy
*
*/
/**
* Get term translations
*
* @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) {
switch ($op) {
case 'view':
// This runs after taxonomy:nodeapi, so we just localize terms here
if ($op == 'view' && array_key_exists('taxonomy', $node)) {
$node->taxonomy = i18ntaxonomy_localize_terms($node->taxonomy);
}
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;
}
}
/**
* Translate an array of taxonomy terms
*
* Translates all terms with language, just passing over terms without it
*/
function i18ntaxonomy_translate_terms($taxonomy, $langcode) {
$translation = array();
foreach ($taxonomy as $index => $term) {
if ($term->language && $term->language != $langcode) {
$translated_terms = i18ntaxonomy_term_get_translations(array('tid' => $term->tid));
if ($translated_terms && $newterm = $translated_terms[$langcode]) {
$translation[$newterm->tid] = $newterm;
}
} else {
// Term has no language. Should be ok
$translation[$index] = $term;
}
}
return $translation;
}
/**
* Implementation of hook_views_pre_view().
*
* Translate table header for taxonomy fields
* //field[i][id] = term_node_1.name, translate table header
* and replace handler for that field
*/
function i18ntaxonomy_views_pre_view(&$view, &$items) {
//var_dump($view);
$translate = variable_get('i18ntaxonomy_vocabularies', array());
foreach($view->field as $index => $data) {
$matches = array();
if($data['id'] == 'term_node.name') {
// That's a full taxonomy box
$view->field[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms';
} elseif(preg_match("/term_node_(\d+)\.name/", $data['id'], $matches)) {
$vid = $matches[1];
if ($translate[$vid]) {
// Set new handler for this field
$view->field[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms';
}
}
}
}
/**
* Field handler for taxonomy term fields
*
* Remake of views_handler_field_allterms with term name translation
*/
function i18ntaxonomy_views_handler_field_allterms($fieldinfo, $fielddata, $value, $data) {
if ($fieldinfo['vocabulary']) {
$terms = taxonomy_node_get_terms_by_vocabulary($data->nid, $fieldinfo['vocabulary']);
}
else {
$terms = taxonomy_node_get_terms($data->nid);
}
// Translate all these terms
_i18ntaxonomy_translate_terms($terms);
if ($fielddata['options'] == 'nolink') {
foreach ($terms as $term) {
$links[] = check_plain($term->name);
}
$links = !empty($links) ? implode(' | ', $links) : '';
}
else {
$node = new stdClass();
$node->taxonomy = $terms;
$links = theme('links', taxonomy_link('taxonomy terms', $node));
}
return $links;
}
/**
* 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')) {
$localize = i18ntaxonomy_vocabulary(NULL, I18N_TAXONOMY_LOCALIZE);
foreach ($terms as $index => $term) {
if (in_array($term->vid, $localize)) {
foreach ($fields as $property) {
$terms[$index]->$property = tt("taxonomy:term:$term->tid:$property", $term->name);
}
}
}
return $terms;
}
// Get a list of vocabularies and terms
function _i18ntaxonomy_vocabulary_terms($vid = NULL, $fullname = TRUE) {
$tids = array();
if (is_numeric($vid)) {
$where = "WHERE td.vid = $vid";
} elseif(is_array($vid)) {
$where = "WHERE td.vid IN(".implode(',', $vid).')';
}
$result = db_query("SELECT DISTINCT(td.tid), td.name, td.weight, v.name as vocabname, v.weight FROM {term_data} td LEFT JOIN {vocabulary} v ON v.vid = td.vid $where ORDER BY v.weight, v.name, td.weight, td.name");
while ($obj = db_fetch_object($result)) {
$tids[$obj->tid] = $fullname ? t($obj->vocabname).': '.t($obj->name) : t($obj->name);
}
return $tids;
}
/**
* 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;
}
}