7

I have a hierarchical custom taxonomy which I can display using print_r(get_the_terms( $post->ID, 'taxonomic_rank' ));:

Array
(
    [46] => stdClass Object
        (
            [term_id] => 46
            [name] => Aplocheilidae
            [slug] => aplocheilidae
            [term_group] => 0
            [term_taxonomy_id] => 53
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 39
            [count] => 1
            [object_id] => 443
        )

    [47] => stdClass Object
        (
            [term_id] => 47
            [name] => Aplocheilus
            [slug] => aplocheilus
            [term_group] => 0
            [term_taxonomy_id] => 54
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 46
            [count] => 1
            [object_id] => 443
        )

    [39] => stdClass Object
        (
            [term_id] => 39
            [name] => Cyprinodontiformes
            [slug] => cyprinodontiformes
            [term_group] => 0
            [term_taxonomy_id] => 52
            [taxonomy] => taxonomic_ranks
            [description] => 
            [parent] => 0
            [count] => 1
            [object_id] => 443
        )

)

This taxonomy will always take the following form: Order (parent) > Family (child of Order) > Sub-family (child of Family)

Is there a quick and easy way of displaying these taxonomies in the correct order, so that I could print out the following line? Order: <order>, Family: <family>, Sub-family: <sub-family>

Thanks in advance

  • 4
    Whoever down-marked me, would you please explain why? – dunc Dec 26 '11 at 13:09
6

There are probably some better ways to do this but you can always do a three simple foreach loops.

I wrote an example function that does the job well and should serve you as a good starting point:

function print_taxonomic_ranks( $terms = '' ){

    // check input
    if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
        return;

    // set id variables to 0 for easy check 
    $order_id = $family_id = $subfamily_id = 0;

    // get order
    foreach ( $terms as $term ) {
        if ( $order_id || $term->parent )
            continue;
        $order_id  = $term->term_id;
        $order     = $term->name;
    }

    // get family
    foreach ( $terms as $term ) { 
        if ( $family_id || $order_id != $term->parent )
            continue;
        $family_id = $term->term_id;
        $family    = $term->name;
    }

    // get subfamily
    foreach ( $terms as $term ) { 
        if ( $subfamily_id || $family_id != $term->parent ) 
            continue;
        $subfamily_id = $term->term_id;
        $subfamily    = $term->name;
    }

    // output
    echo "Order: $order, Family: $family, Sub-family: $subfamily";

}

Let it live in your functions.php file and use it in your templates like this:

print_taxonomy_ranks( get_the_terms( $post->ID, 'taxonomic_rank' ) );

NOTE: Looping the same array three times around sounds a bit stupid but on the other hand it's a quick and easy solution which is easy to read, extend and maintain.

  • I couldn't be further in your debt. That is a perfect answer, and I wish I could give you more reputation!! If anyone passes this post, please +1 it so that Maugly can be rewarded as much as I'd like :) – dunc Dec 27 '11 at 14:23
  • 1
    No worries. I'm glad I could help :) I've also updated the code in my answer and added a simple input check... – Michal Mau Dec 27 '11 at 15:10
  • Maugly, thanks for your answer, it's almost exactly what I needed- do you have any thoughts on how to use it with the archive links still attached to the terms? Thanks again – user12391 Jan 24 '12 at 3:55
  • @Adam Take a look on get_term_link() – Michal Mau Jan 24 '12 at 10:36
  • What if the post has two parent categories? I'm trying to fix it, but it's taking me too long to figure it out yet. – Lucas Bustamante Jun 2 '15 at 5:20
4

Though Maugly's approach seem a bit more readable but running a loop 3 times over the array doesn't seem right to me. So here is just another approach which might be less readable for some but works without running loop 3 times.

function print_taxonomy_ranks( $terms ) {
    // if terms is not array or its empty don't proceed
    if ( ! is_array( $terms ) || empty( $terms ) ) {
        return false;
    }

    foreach ( $terms as $term ) {
        // if the term have a parent, set the child term as attribute in parent term
        if ( $term->parent != 0 )  {
            $terms[$term->parent]->child = $term;   
        } else {
            // record the parent term
            $parent = $term;
        }
    }

    echo "Order: $parent->name, Family: {$parent->child->name}, Sub-Family: {$parent->child->child->name}";
}
  • Nice work! I knew that it is possible, I just couldn't wrap my head around it back then :) I like your solution! – Michal Mau Jan 24 '12 at 12:34
3

I had a situation where a post could be tagged with multiple category groups, and multiple children within parent categories, so I wanted my hierarchy to reflect that. I also just wanted a few lines of code:

$terms = get_the_terms($id, 'department_categories');
foreach($terms as $key => $term){
    if($term->parent != 0){
        $terms[$term->parent]->children[] = $term;
        unset($terms[$key]);
    }
}

Basically after it finds a category's parent, it moves it to the parent's obj as a child, and then removes it from it's original position in the array. I've tested this using multiple siblings, children, and different levels of categories.

Hope someone else finds this useful in case they're just looking for some logic guidance rather than a "plugin"!

  • Can you please explain how to use that? – Lucas Bustamante Jun 2 '15 at 5:22
  • @LucasB I'll try to recall :P We have a post, and we know it's $post->ID is $id and "department_categories" would be the taxonomy. It simply gets all terms associated to the post and restructures the array of results to make more hierarchical sense. Once restructured, you're free to output it as you see fit. – Kaitlyn McDonald Jun 2 '15 at 13:22
3

Bit old topic but still relevant I think as it's still a complete pain.

I'm using this recursive function that takes in two arrays as references. It will create an array with the structure of: [term_id] => term_object->children->child_terms_array->children->child_terms_array.

<?php
function sort_terms_hierarchically( array &$terms, array &$into, $parent_id = 0 ) {
    foreach ( $terms as $i => $term ) {
        if ( $term->parent == $parent_id ) {
            $into[$term->term_id] = $term;
            unset( $terms[ $i ] );
        }
    }

    foreach ( $into as $top_term ) {
        $top_term->children = array();
        $this->sort_terms_hierarchically( $terms, $top_term->children, $top_term->term_id );
    }

}

$terms = get_the_terms( 'taxslug', $post );
$sorted_terms = array();
sort_terms_hierarchically( $terms, $sorted_terms );

// Will log the nested arrays of term objects.
error_log( print_r( $sorted_terms, true ) );

This is the only solution I've ever found that maintain the term objects and works with any amount of nesting.

1

Thanks Maugly,

Here's my modified version of your code that includes the term permalinks if anyone needs it

function print_show_location( $terms = '' ){

// check input
if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
    return;

// set id variables to 0 for easy check 
$country_id = $state_id = $city_id = 0;

// get country
foreach ( $terms as $term ) {
    if ( $country_id || $term->parent )
        continue;
    $country_id  = $term->term_id;
    $country_slug = $term->slug;
    $country = '<a href="'.get_term_link($country_slug, 'location').'">'.$term->name.'</a>';
}

// get state
foreach ( $terms as $term ) { 
    if ( $state_id || $country_id != $term->parent )
        continue;
    $state_id = $term->term_id;
    $state_slug = $term->slug;
    $state = '<a href="'.get_term_link($state_slug, 'location').'">'.$term->name.'</a>';
}

// get city
foreach ( $terms as $term ) { 
    if ( $city_id || $state_id != $term->parent ) 
        continue;
    $city_id = $term->term_id;
    $city_slug = $term->slug;
    $city = '<a href="'.get_term_link($city_slug, 'location').'">'.$term->name.'</a>';
}

// output
echo "$city, $state - $country";

}
0

There are a couple of recursive functions I use depending on my needs. Both need the list of terms, a starting parent id and a starting (generally empty) array where to put the resulting list.

FIRST VERSION

$terms = [a list of terms objects, use get_terms of whatever]
$ordered_terms = array(); // here you'll find your ordered terms, from root to final child

function list_terms_by_parent($parent_id = 0, &$terms, &$ordered_terms){
  $root_parent = $parent_id;

  foreach($terms as $index => $term){
    if($term->parent == (int) $parent_id){
      $ordered_terms[$term->term_id] = $term;
      $root_parent = $term->term_id;
      unset($terms[$index]);
    }
  }

  if(!empty($terms)) list_terms_by_parent($root_parent, $terms, $ordered_terms);
}

SECOND VERSION

$term_ids = [should be a list of ids in the form of child_id => parent_id]

// a fast way to get that kind of list is to use WP_Term_query, example:
//$terms_query = new WP_Term_Query(array(
//  'taxonomy' => 'product_categories'
//  ,'object_ids' => $post->ID
//  ,'hide_empty' => false
//  ,'fields' => 'id=>parent'
//));


$ordered_terms = array(); // here you'll find your ordered terms, from root to final child

function list_term_ids_by_parent($parent_id = 0, &$term_ids, &$ordered_terms){
  $child_id = array_search($parent_id, $term_ids);

  if($child_id){
    $ordered_terms[] = $child_id;
    unset($term_ids[$child_id]);
  }

  if(!empty($term_ids)) order_terms($child_id, $term_ids, $ordered_terms);
}

THIRD VERSION

This is not mine, I found it somewhere, probably here or in stackoverflow. This is a little different in output, since it'll generate a cascading terms list, with only one root, which has a children property, which will contain the next term with its children property and so on..

// $ordered_terms is supposed to be an empty array as above

function sort_terms_hierarchically(&$terms, &$ordered_terms, $parentId = 0){
  foreach($cats as $i => $cat){
    if($cat->parent == $parentId){
      $into[$cat->term_id] = $cat;
      unset($cats[$i]);
    }
  }

  foreach ($into as $topCat) {
    $topCat->children = array();
    sort_terms_hierarchically($cats, $topCat->children, $topCat->term_id);
  }
}

protected by Community Dec 22 '14 at 9:46

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.