function taxonomy_get_tree

You are here

7 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE)
4.6 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL)
4.7 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL)
5 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL)
6 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL)
8 taxonomy.module taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $load_entities = FALSE)

Create a hierarchical representation of a vocabulary.

Parameters

$vid: Which vocabulary to generate the tree for.

$parent: The term ID under which to generate the tree. If 0, generate the tree for the entire vocabulary.

$depth: Internal use only. Now deprecated and isn't used. It is left here only because of compatibility issues.

$max_depth: The number of levels of the tree to return. Leave NULL to return all levels.

Return value

An array of all term objects in the tree. Each term object is extended to have "depth" and "parents" attributes in addition to its normal ones. Results are statically cached.

10 calls to taxonomy_get_tree()
forum_get_forums in modules/forum/forum.module
Returns a list of all forums for a given taxonomy id
forum_nodeapi in modules/forum/forum.module
Implementation of hook_nodeapi().
taxonomy_check_vocabulary_hierarchy in modules/taxonomy/taxonomy.module
Dynamically check and update the hierarachy flag of a vocabulary. Checks and updates the hierarchy flag of a vocabulary.
taxonomy_form_all in modules/taxonomy/taxonomy.module
Generate a set of options for selecting a term from all vocabularies.
taxonomy_form_term in modules/taxonomy/taxonomy.admin.inc
Form function for the term edit form.

... See full list

File

modules/taxonomy/taxonomy.module, line 848
Enables the organization of content into categories.

Code

function taxonomy_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
  static $children, $parents, $terms;

  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (!isset($children[$vid])) {
    $children[$vid] = array();
    $parents[$vid] = array();
    $terms[$vid] = array();

    $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
    while ($term = db_fetch_object($result)) {
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
    }
  }

  $max_depth = (!isset($max_depth)) ? count($children[$vid]) : $max_depth;
  $tree = array();

  // Keeps track of the parents we have to process, the last entry is used
  // for the next processing step.
  $process_parents = array();
  $process_parents[] = $parent;

  // Loops over the parent terms and adds its children to the tree array.
  // Uses a loop instead of a recursion, because it's more efficient.
  while (count($process_parents)) {
    $parent = array_pop($process_parents);
    // The number of parents determines the current depth.
    $depth = count($process_parents);
    if ($max_depth > $depth && !empty($children[$vid][$parent])) {
      $has_children = FALSE;
      $child = current($children[$vid][$parent]);
      do {
        if (empty($child)) {
          break;
        }
        $term = $terms[$vid][$child];
        if (count($parents[$vid][$term->tid]) > 1) {
          // We have a term with multi parents here. Clone the term,
          // so that the depth attribute remains correct.
          $term = clone $term;
        }
        $term->depth = $depth;
        unset($term->parent);
        $term->parents = $parents[$vid][$term->tid];
        $tree[] = $term;
        if (!empty($children[$vid][$term->tid])) {
          $has_children = TRUE;

          // We have to continue with this parent later.
          $process_parents[] = $parent;
          // Use the current term as parent for the next iteration.
          $process_parents[] = $term->tid;

          // Reset pointers for child lists because we step in there more often
          // with multi parents.
          reset($children[$vid][$term->tid]);
          // Move pointer so that we get the correct term the next time.
          next($children[$vid][$parent]);
          break;
        }
      } while ($child = next($children[$vid][$parent]));

      if (!$has_children) {
        // We processed all terms in this hierarchy-level, reset pointer
        // so that this function works the next time it gets called.
        reset($children[$vid][$parent]);
      }
    }
  }

  return $tree;
}

Comments

Keep in mind that taxonomy_get_tree doesn't actually return a tree of nested arrays - it returns a flat array of terms, with terms having a "parents" attribute.

I was scratching my head over this one because what this function returns is not at all a hierarchical representation of the tree of terms -- unless you know that a parent term is followed by its children in the array you get back. Which they are, although it doesn't say so here in the documentation. So that means you don't have to recursively process this function's output in order to get a nested list, which you would need to do otherwise.

- non recursive
- one pass
- resistant for non in order positions of terms
- safe for more than one parent
... function:

<?php
function _taxonomy_get_real_tree($vid){
 
$result_tree = array();
 
$terms = array();
  foreach(
taxonomy_get_tree($vid) as $term){
    if(isset(
$terms[$term->tid])){
     
$term->children = $terms[$term->tid]->children;
     
$terms[$term->tid] = $term;
    }else{
     
$terms[$term->tid] = $term;
    }

    if(

$term->depth === 0){
     
$result_tree[$term->tid] = &$terms[$term->tid];
      continue;
    }

    foreach(

$term->parents as $tid){
      if(
$tid){
        if(!isset(
$terms[$tid])){
         
$terms[$tid] = new stdClass();
        }
       
$terms[$tid]->children[$term->tid] = &$terms[$term->tid];
      }
    }
  }
  return
$result_tree;
}
?>

Work for me.

In my company, we make a function to get an more useful tree, with "nesteds" terms in an array that you can access with the $term->children.

Note: This function don't works as expected if you want to use it with unlimited depth. See the comment from mbmasuda below to get the correct function.

<?php
function taxonomy_get_nested_tree($terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {
  if (
is_int($terms)) {
   
$terms = taxonomy_get_tree($terms);
  }

  foreach(

$terms as $term) {
    foreach(
$term->parents as $term_parent) {
      if (
$term_parent == $parent) {
       
$return[$term->tid] = $term;
      }
      else {
       
$parents_index[$term_parent][$term->tid] = $term;
      }
    }
  }

  foreach(

$return as &$term) {
    if (isset(
$parents_index[$term->tid]) && !is_null($max_depth) && $depth < $max_depth) {
     
$term->children = taxonomy_get_nested_tree($parents_index[$term->tid], $max_depth, $term->tid, $parents_index, $depth + 1);
    }
  }

  return

$return;
}
?>

Use them with:

<?php
$tree
= taxonomy_get_nested_tree(7); // Where 7 is your vocabulary id (vid)
?>

Or with a depth limit (starting from 0), as:

<?php
$tree
= taxonomy_get_nested_tree(7, 1); // Where 1 is the depth limit
?>

Thank you for posting your taxonomy_get_nested_tree function. Unfortunately it did not work out of the box when given only one argument—in that case it only returned the top level items and no children. I fixed it so now it works as described when $max_depth is null.

<?php
function taxonomy_get_nested_tree($terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {
  if (
is_int($terms)) {
   
$terms = taxonomy_get_tree($terms);
  }

  foreach(

$terms as $term) {
    foreach(
$term->parents as $term_parent) {
      if (
$term_parent == $parent) {
       
$return[$term->tid] = $term;
      }
      else {
       
$parents_index[$term_parent][$term->tid] = $term;
      }
    }
  }

  foreach(

$return as &$term) {
    if (isset(
$parents_index[$term->tid]) && (is_null($max_depth) || $depth < $max_depth)) {
     
$term->children = taxonomy_get_nested_tree($parents_index[$term->tid], $max_depth, $term->tid, $parents_index, $depth + 1);
    }
  }

  return

$return;
}
?>

Here's a function that tries to mimic the default menu output. $tree parameter uses output from mbmasuda's taxonomy_get_nested_tree() above.

<?php
function taxonomy_nested_tree_output($tree) {

  if (!empty(

$tree)) {
   
$output = '';
   
$q = $_GET['q'];
   
$num_items = count($tree);
   
$i = 0;

    foreach (

$tree as $tid => $term) {
     
$extra_classes = array();
     
$in_active_trail = FALSE;
    
     
$data = array(
       
'title' => $term->name,
       
'href' => 'taxonomy/term/'. $tid,
      );
    
      if (
$data['href'] == $q) {
       
$data['localized_options'] = array('attributes' => array('class' => 'active'));
       
$in_active_trail = TRUE;
       
$extra_classes[] = 'active';
      }
    
      if (
$i == 0) {
       
$extra_class[] = 'first';
      }
      if (
$i == $num_items - 1) {
       
$extra_classes[] = 'last';
      }
    
     
$link = theme('menu_item_link', $data);
    
     
$has_children = $term->children ? TRUE : FALSE;
    
      if (
$has_children) {
        foreach(
$term->children as $child_tid => $child) {
          if (
'taxonomy/term/'. $child_tid == $q) {
           
$in_active_trail = TRUE;
          }
        }
      
       
$output .= theme('menu_item', $link, $has_children, taxonomy_nested_tree_output($term->children), $in_active_trail, implode(' ', $extra_classes));
      }
      else {
       
$output .= theme('menu_item', $link, $has_children, '', $in_active_trail, implode(' ', $extra_classes));
      }
    
     
$i++;
    }
  }

  return

theme('menu_tree', $output);
}
?>

I used the array that taxonomy_get_nested_tree() returns, and themed it into a menu. I did this because I couldn't figure out how to use the taxonomy_menu module to create a dynamic unordered hierarchal menu, each item linking to the current path with a custom query string. My custom code gives links with the path of /[current_path]?category=[tid]+[tid]+....+[tid]

Here's the code I used:

$parents = taxonomy_get_nested_tree(1);

$path = $_GET['q'];
$menu_tree = '<ul class="menu">';
  for($i = 1; $i <= count($parents); $i++) {
//create a list item, assign it a class of "first" or "last" and give it the proper path and query string
    $menu_tree.='<li'.($i == 1 ? ' class="first"' : '').($i == count($parents) ? ' class="last"':'').'>'.l($parents[$i]->name,$path,array('query'=>'category='.$parents[$i]->tid));
   
    if($parents[$i]->children){
//if it has children, build a nested list in this <li>
      $menu_tree.='<ul>';
      $ii = 1;
      foreach($parents[$i]->children as $level_1 ) {
        $menu_tree .= '<li'.($ii == 1 ? ' class="first"' : '').($ii == count($parents[$i]->children) ? ' class="last"':'').'>'.l($level_1->name,$path,array('query'=>'category='.$parents[$i]->tid.'+'.$level_1->tid));
        if($level_1->children){
          $menu_tree .= '<ul>';
          $iii = 1;
          foreach($level_1->children as $level_2) {
            $menu_tree .= '<li'.($iii == 1 ? ' class="first"' : '').($iii == count($level_1->children) ? ' class="last"':'').'>'.l($level_2->name,$path,array('query'=>'category='.$parents[$i]->tid.'+'.$level_1->tid.'+'.$level_2->tid)).'</li>';
            $iii++;
          }
          $menu_tree .='</ul>';
        }
        $menu_tree .= '</li>';
        $ii ++;
      }
        $menu_tree .= '</ul>';

    }
  }
return $menu_tree;

I only built this out for a vocabulary with 3 tiers, and I'm sure there would be a way of recursively processing it so you don't need a new foreach statement on each successive level... but this works for me.

<?php
function theme_taxonomy_nested_tree($tree) {
    if (
count($tree)) {
       
$output = '<ul class="taxonomy-tree">';
        foreach (
$tree as $term) {
           
$output .= '<li class="taxonomy-term">';
           
$output .= l($term->name, taxonomy_term_path($term));
            if (
$term->children) {
               
$output .= theme('illogica_category_tree', $term->children);
            }
           
$output .= '</li>';
        }
       
$output .= '</ul>';
    }
    return
$output;
}
?>

Someone filed an issue reporting that this doesn't work:
http://drupal.org/node/1046542
Just posting a link in case someone is interested.

Does anyone know of a way in which it might possible to use this snippet in combination with Views (or an argument in the page.tpl.php) so that I can return a taxonomy hierarchy for items sharing the same tag or a child tag of the current node?

I would like to output this on the bottom of each of my nodes as "also in this section" links.

I have them listed with views at the moment, but they are not nested which I need for accessibility and to style the children & parents differently.

Thanks

Is there a way to get the next or the previous term of the current term of a node?

Thanks

Worth noting.

If a term has two parents then it is actually output twice. The list of terms output seems to match exactly the list produced under admin/content/taxonomy/[vid].

With a twist - the value of $depth is calculated by the taxonomy_get_tree() function based on how far down the tree it needs to recurse. So if a term has two parents which are at different depths then the two term records output will differ in the value of the $depth attribute.

This might help someone to understand this function's behaviour.

'Results are statically cached' and there is no parameter to turn this off.

This means that it's important to be careful when calling this function as once it's been called the first time the results are effectively 'cached'.

It looks like that in D7 this has been added - so I doubt this will be added to this version.

This may be the basis for an updated function which has a reset value.

function taxonomy_get_tree_with_reset($vid, $parent = 0, $depth = -1, $max_depth = NULL, $reset = FALSE) {
  static $children, $parents, $terms;

  if ($reset) {
    unset($children[$vid]);
    unset($parents[$vid]);
    unset($terms[$vid]);
  }

  $depth++;

  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (!isset($children[$vid])) {
    $children[$vid] = array();

    $result = db_query(db_rewrite_sql('SELECT t.tid, t.*, parent FROM {term_data} t INNER JOIN {term_hierarchy} h ON t.tid = h.tid WHERE t.vid = %d ORDER BY weight, name', 't', 'tid'), $vid);
    while ($term = db_fetch_object($result)) {
      $children[$vid][$term->parent][] = $term->tid;
      $parents[$vid][$term->tid][] = $term->parent;
      $terms[$vid][$term->tid] = $term;
    }
  }

  $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
  $tree = array();
  if ($max_depth > $depth && !empty($children[$vid][$parent])) {
    foreach ($children[$vid][$parent] as $child) {
      $term = drupal_clone($terms[$vid][$child]);
      $term->depth = $depth;
      // The "parent" attribute is not useful, as it would show one parent only.
      unset($term->parent);
      $term->parents = $parents[$vid][$child];
      $tree[] = $term;
      if (!empty($children[$vid][$child])) {
        $tree = array_merge($tree, taxonomy_get_tree_with_reset($vid, $child, $depth, $max_depth));
      }
    }
  }

  return $tree;
}

This gives you a nested taxonomy list. Every level has it's own class!

<?php
function list(){
 
$tree = taxonomy_get_nested_tree(4);
  return
theme_taxonomy_nested_tree($tree);

}

function

taxonomy_get_nested_tree($terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {
  if (
is_int($terms)) {
   
$terms = taxonomy_get_tree($terms);
  }

  foreach(

$terms as $term) {
    foreach(
$term->parents as $term_parent) {
      if (
$term_parent == $parent) {
       
$return[$term->tid] = $term;
      }
      else {
       
$parents_index[$term_parent][$term->tid] = $term;
      }
    }
  }

  foreach(

$return as &$term) {
    if (isset(
$parents_index[$term->tid]) && (is_null($max_depth) || $depth < $max_depth)) {
     
$term->children = taxonomy_get_nested_tree($parents_index[$term->tid], $max_depth, $term->tid, $parents_index, $depth + 1);
    }
  }

  return

$return;
}

function

theme_taxonomy_nested_tree($tree) {
  if (
count($tree)) {
   
$output = '<ul>';
    foreach (
$tree as $term) {
     
$output .= '<li class="taxonomy-term">';
     
$output .= l($term->name, taxonomy_term_path($term),array('html'=>true,'attributes'=>array('class'=>'level-'.$term->depth)));
      if (!empty(
$term->children)){
       
$output .= as_get_children($term->children);
      }
       
     
$output .= '</li>';
    }
   
$output .= '</ul>';
  }
  return
$output;
}

function

get_children($term){
 
$output = '<ul>';
  foreach(
$term as $child){
   
$output .= "<li>";
   
$output .= l($child->name,taxonomy_term_path($child),array('html'=>true,'attributes'=>array('class'=>'level-'.$child->depth)));
   
$output .= "</li>";
    if(!empty(
$child->children)){
     
$output .=get_children($child->children);
    }
  }
 
$output .= '</ul>';

  return

$output;
}
?>