'. t('This module adds support for multilingual taxonomy. You can set up multilingual options for each vocabulary:') .'

'; $output .= ''; $output .= '

'. t('To search and translate strings, use the translation interface pages.', array('@translate-interface' => url('admin/build/translate'))) .'

'; $output .= '

'. t('For more information, see the online handbook entry for Internationalization module.', array('@i18n' => 'http://drupal.org/node/133977')) .'

'; return $output; case 'admin/settings/language/i18n': $output = ''; return $output; case 'admin/content/taxonomy/%': $vocabulary = taxonomy_vocabulary_load($arg[3]); switch (i18ntaxonomy_vocabulary($vocabulary->vid)) { case I18N_TAXONOMY_LOCALIZE: return '

'. t('%capital_name is a localizable vocabulary. You will be able to translate term names and descriptions using the translate interface pages.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name, '@translate-interface' => url('admin/build/translate'))) .'

'; case I18N_TAXONOMY_LANGUAGE: return '

'. 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'))) .'

'; case I18N_TAXONOMY_TRANSLATE: return '

'. 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))) .'

'; } } } /** * 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 > 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']; tt("taxonomy:term:$tid:name", $edit['name'], NULL, TRUE); tt("taxonomy:term:$tid:description", $edit['description'], NULL, TRUE); 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']); $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; 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'] .= ' ('. $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 translation refresh 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), ); 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'] .= ' '. t('This name will be localizable. You can translate it using the translate interface pages.', array('@translate-interface' => url('admin/build/translate'))) .''; $form['identification']['description']['#description'] .= ' '. t('This description will be localizable. You can translate it using the translate interface pages.', array('@translate-interface' => url('admin/build/translate'))) .''; 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']) && $form['type']['#value'] .'_node_form' == $form_id && ($node = $form['#node']) && isset($form['taxonomy']) ) { // Node form. Translate vocabularies. i18ntaxonomy_node_form($form); } } } /** * 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 = tt("taxonomy:vocabulary:$vid: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) . tt("taxonomy:term:$term->tid:name", $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) { if (i18ntaxonomy_vocabulary($vid) == 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; } } } /** * 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 : tt("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(check_plain(tt("taxonomy:vocabulary:$vid:name", $vocabulary->name)), $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) { 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(); if (is_array($taxonomy)) { 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; } /** * 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->$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; }