All I want is display ALL the taxonomy terms of a given vocabolary in a hierarchical tree with their descriptions.
I managed to get the nested array from http://api.drupal.org/api/function/taxonomy_get_tree
But how do I now traverse it to get (and print) the terms in a html list?

Comments

sylvaticus’s picture

he-he-he.. internet is small.. today I was looking for the same thing... and google showed me.. my own old request!

I think I understood http://api.drupal.org/api/function/taxonomy_get_tree doesn't even return a nested array.. so I am at the same point: How do I show (echo) a nested vocaboulary on a page using PHP filter?

sylvaticus’s picture

Thanks to http://api.drupal.org/api/drupal/modules--taxonomy--taxonomy.module/func... I managed to get this code:


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 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;
}

$tree= taxonomy_get_nested_tree(2);
$output=theme_taxonomy_nested_tree($tree);
echo $output;

However I display only the first level. I think that's because I'm not placing the funcion on a .tpl but simply on a page. So the last question is how can I recursivelly call theme_taxonomy_nested_tree() ??

sylvaticus’s picture

Finally I got it :-))))))

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 output_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 .= output_taxonomy_nested_tree($term->children);
            }
            $output .= '</li>';
        }
        $output .= '</ul>';
    }
    return $output;
}

$tree= taxonomy_get_nested_tree(2,10);
$output=output_taxonomy_nested_tree($tree);
echo $output;

Of course it can be easily changed to point each term to a view that take the $tid as argument...

jurgenr’s picture

This is indeed a nice way to list the terms.
I'm going to start with your functions to get my taxonomy terms in a select dropdown.
With some brainbreaking and hopefully not to many problems with ajax it's gonna turn out really nice.

Thnx for this nice script!

Jurgen.

Drupal backend developer since 2011.
Open Source Webdevelopment Coding Blog

hmdnawaz’s picture

But where to put your code. As I paste your code in the body field of a page with php filter but it did not work.

Ahmad Nawaz
Acquia Certified Developer
Email: hmdnawaz@gmail.com
Skype: hmdnawaz

j4’s picture

Ahmad,

In case you still want to know how..see the 2, 10 in the brackets? 2 is the vocabulary id, and i think 10 is the depth..so for me I used 1, 2 and got the beautifully indented listing of my taxonomy terms with only one sub level.

Jaya

jhyrith’s picture

Updated for Drupal 7:

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 output_taxonomy_nested_tree($tree) {
    if (count($tree)) {
        $output = '<ul class="taxonomy-tree">';
        foreach ($tree as $term) {
            $output .= '<li class="taxonomy-term">';
            $output .= $term->name;
            if ($term->children) {
                $output .= output_taxonomy_nested_tree($term->children);
            }
            $output .= '</li>';
        }
        $output .= '</ul>';
    }
    return $output;
}
$tree= taxonomy_get_nested_tree(2,10);
$output=output_taxonomy_nested_tree($tree);
echo $output;

priyanka.singh’s picture

Your code worked like charm. Thanks a lot.

jkamizato’s picture

Thank you @jhyrith it is very useful :)

rcodina’s picture

@jhyrith code works like a charm!

ripcurl’s picture

Update for drupal 8.
I simply had to change the line

$terms = taxonomy_get_tree($terms);

For

$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree("vocabulary_id", $parent = $terms, $max_depth = NULL, $load_entities = FALSE);

You'll have to replace "vocabulary_id" with your concerned vocabulary_id.
Works well for a single term but if 2 terms id are specified in parameter of the taxonomy_get_nested_tree() function only the first term id is covered (it does not matter to me I do not need to travel more than one term).

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); //Function for drupal 7
		$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree("vocabulary_id", $parent = $terms, $max_depth = NULL, $load_entities = FALSE);
	}

	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 output_taxonomy_nested_tree($tree) {
	if (count($tree)) {
		$output = '<ul class="taxonomy-tree">';
		foreach ($tree as $term) {
			$output .= '<li class="taxonomy-term">';
			$output .= $term->name;
			if ($term->children) {
				$output .= output_taxonomy_nested_tree($term->children);
			}
			$output .= '</li>';
		}
		$output .= '</ul>';
	}
	return $output;
}

//$tree= taxonomy_get_nested_tree(2,10);//In this case only the first term id 2 is covered
$tree= taxonomy_get_nested_tree(2);
$output=output_taxonomy_nested_tree($tree);
echo $output;

And adapted version to return an array instead of an html list. Compatible (for example) with the script bootstrap-treeview.js (https://github.com/jonmiles/bootstrap-treeview).
Idem, works well for a single term but if 2 terms id are specified in parameter of the taxonomy_get_nested_tree() function only the first term id is covered.


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); //Function for drupal 7
		$terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree("vocabulary_id", $parent = $terms, $max_depth = NULL, $load_entities = FALSE);
	}

	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 output_taxonomy_nested_tree_array($tree) {

	if (count($tree)) {

		foreach ($tree as $term) {

			$actual_data_array_to_add = array();
			$actual_data_array_to_add['text'] = $term->name;
			$actual_data_array_to_add['tid'] = $term->tid;

			if ($term->children) {

				$actual_data_array_to_add['nodes'] = output_taxonomy_nested_tree_array($term->children);
			}

			$array_to_return[] = $actual_data_array_to_add;

		}

	}

	return $array_to_return;
}

	//$tree= taxonomy_get_nested_tree(2,10);//In this case only the first term id 2 is covered
$tree= taxonomy_get_nested_tree(2);
$output=output_taxonomy_nested_tree_array($tree);
print_r($output);
miststudent2011’s picture

Thanks for your code but the menu elements are being print in a flat manner like the image attached(Flat Mannered) but they should actually print the hierarchical manner like the image attached(Hierarchy ) and should work as link references but they arent There is small correction and i made corrections to your code.


Updated Code for Drupal 7

			<?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;
	}

	function output_taxonomy_nested_tree($tree) {
		if (count($tree)) {
			$output = '<ul class="nav navbar-nav">';
			foreach ($tree as $term) {            
				 $output .= '<li class="taxonomy-term">' . l($term->name, "taxonomy/term/". $term->tid);
				 
				
				if ($term->children) {
					$output .= output_taxonomy_nested_tree($term->children);
				}
				$output .= '</li>';
			}
			$output .= '</ul>';
		}
		return $output;
	}
	$tree= taxonomy_get_nested_tree(2,10);
	$output=output_taxonomy_nested_tree($tree);
	echo $output;
baltazarz3’s picture

I've made some updates after some PHP warnings and the way to pass the $vid into the loadTree since it was failing in Drupal 8.5.3 with php 7.1, I hope it helps someone.

function taxonomy_get_nested_tree($terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {

if (is_int($terms)) {
$vid = 'YOUR_TAXONOMY_VOCABULARY';
$terms =\Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid);
}
$return = array();
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 output_taxonomy_nested_tree($tree) {
$output = '';

if (count($tree)) {
$output = '<ul class="taxonomy-tree">';
foreach ($tree as $term) {
$output .= '<li class="taxonomy-term">';
$output .= $term->name;
if (isset($term->children)) {
$output .= output_taxonomy_nested_tree($term->children);
}
$output .= '</li>';
}
$output .= '</ul>';
}

return $output;
}

$tree= taxonomy_get_nested_tree(2);
$output=output_taxonomy_nested_tree($tree);
return $output;

berdir’s picture

loadTree() already returns the whole tree, as a flat list with a depth argument. that can be used directly to show it, with a single foreach loop that opens and loses <ul> tags depending on depth changes. Additionally, loading terms as entities allows to make the link generation much, much simpler as well:

<?php

$tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree('channel', 1, NULL, TRUE);
    $output = '<ul class="taxonomy-tree">';
    /* @var \Drupal\taxonomy\TermInterface $term */
    foreach ($tree as $i => $term) {

      // If this isn't the first term and has a higher depth than the previous
      // one, open a new list.
      if ($i && $tree[$i- 1]->depth < $term->depth) {
        $output .= '<ul class="taxonomy-tree">';
      }

      // If this isn't the first term and has a lower depth than the previous
      // one, close as many lists as the depth is lower. This is currently not
      // needed as the current structure only has two levels.
      if ($i && $tree[$i- 1]->depth > $term->depth) {
        for ($j = $term->depth; $j < $tree[$i- 1]->depth; $j++) {
          $output .= '</ul>';
        }
      }

      $output .= '<li class="taxonomy-term">';
      $output .= $term->toLink()->toString();
      $output .= '</li>';
    }
    $output .= '</ul>';

?>
mvdve’s picture

The code above will render an incorrect nested html list. The sub list should be included into the list item element.

I agree that it is better to call loadTree() only once as it returns the full tree. It is also preferred to use a twig template for the rendering part of the taxonomy tree. I created a small wrapper class for loadTree with a simple template. The switch block could probably be improved/removed. In my case only a depth of tree is needed to it works for now.

class TermLoader {

  private $vocabulary;
  private $parent;
  private $maxDepth;
  private $loadEntities;

  function __construct($vocabulary = 'categories', $parent = 0, $max_depth = 3, $load_entities = true) {
    $this->vocabulary = $vocabulary;
    $this->parent = $parent;
    $this->maxDepth = $max_depth;
    $this->loadEntities = $load_entities;
  }

  /**
   * Load all taxonomy terms from a given vocabulary in a nested array.
   *
   * @return array
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getTree() {
    $taxonomy_term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
    $terms = $taxonomy_term_storage->loadTree($this->vocabulary, $this->parent, $this->maxDepth, $this->loadEntities);

    return $this->mapTree($terms);
  }

  /**
   * Map a flat array of taxonomy terms to a nested array.
   *
   * @param array $terms
   *
   * @return array
   */
  private function mapTree(array $terms) {
    $tree = [];
    $lower_indexes = [];

    foreach ($terms as $i => $term) {
      $depth = $term->depth;
      $depth_next = isset($terms[$i + 1]) ? $terms[$i + 1]->depth : 0;

      if (!$lower_indexes) {
        $tree[$term->id()]['term'] = $term;
      } else {
        $index_depth = count($lower_indexes);

        switch ($index_depth) {
          case 1:
            $tree[$lower_indexes[0]]['children'][$term->id()]['term'] = $term;
            break;
          case 2:
            $tree[$lower_indexes[0]]['children'][$lower_indexes[1]]['children'][$term->id()]['term'] = $term;
            break;
          case 3:
            $tree[$lower_indexes[0]]['children'][$lower_indexes[1]]['children'][$lower_indexes[2]]['children'][$term->id()]['term'] = $term;
            break;

        }
      }

      if ($depth_next > $depth) {
        $lower_indexes[] = $term->id();
      }

      if ($depth_next < $depth) {
        array_splice($lower_indexes, ($depth_next - $depth));
      }
    }

    return $tree;
  }
}
<ul class="{% if sub %}taxonomy-tree-sub{% else %}taxonomy-tree{% endif %}">
  {% for i, term in terms %}

    <li class="tree-item">
      <a href="{{ path('entity.taxonomy_term.canonical', {'taxonomy_term': term.term.id }) }}">{{ term.term.name.value }}</a>

      {% if term.children %}
        {% include '@taxonomy_tree/taxonomy-tree.html.twig' with { 'terms': term.children, 'sub': true } only %}
      {% endif %}
    </li>
  {% endfor %}
</ul>