1

There is the following hierarchical taxonomy structure of a specific post:

  • Term A
    • SubTerm A.1
    • SubTerm A.2
      • SubTerm A.2.1
  • Term B
    • SubTerm B.1
      • SubTerm B.1.1
      • SubTerm B.1.2

How to display

  1. all parent terms (result: A, B)
  2. all second level terms (result: A.1, A.2, B.1)
  3. all third level terms (result: A.2.1, B.1.1, B.1.2)
0

4 Answers 4

1

I love a good challenge!

The function here:

function get_post_categories_sorted ( $post_id ){

    $terms = wp_get_post_terms( $post_id, 'category' );

    $sorted_terms = [];

    foreach( $terms as $term ){

        $depth = count( get_ancestors( $term->term_id, 'category' ) );

        if( ! array_key_exists( $depth, $sorted_terms ) ){
            $sorted_terms[$depth] = [];
        }

        $sorted_terms[$depth][] = $term;

    }

    return $sorted_terms;

}

will give you an array with the structure in the following image. $sorted_terms[0] contains the top level terms, $sorted_terms[1] all the second level term and so on.

Sorted Terms for a given post

It would definitely be worth using a cache or the transients API to store the data as it could get expensive with a lot of terms to sort through as the call to get_ancestors involves a call back to the database for each level it has to traverse back up the chain.

EDIT

You can use this something like the following:

$sorted_terms = get_post_categories_sorted( get_the_ID() );

foreach( $sorted_terms as $key => $level_items ){

    $number = $key + 1;

    echo sprintf( '<p>Level %s Categories</p>', $number );

    echo '<ul>';

        foreach( $level_items as $term ){
            echo sprintf( '<li>%s</li>', $term->name );
        }

    echo '</ul>';

}

EDIT 2

To just use a specific level, you can simplify the code above like this:

$sorted_terms = get_post_categories_sorted( get_the_ID() );

echo '<p>Level 1 Categories</p>';

echo '<ul>';
    //Change the 0 here to whatever level you need, 
    //just remember is is 0 based; IE the first level is 0, 2nd level is 1 etc.
    //If the number doesn't exist, PHP will throw an undefined index error.

    foreach( $sorted_items[0] as $term ){ 
        echo sprintf( '<li>%s</li>', $term->name );
    }

echo '</ul>';
9
  • Hi Ben, thanks for reply. My questions above (1-3) are isolated questions. In your code I do not see a call of the term depth, therefore this is not what I need.
    – Traveler
    Commented Mar 9, 2017 at 12:50
  • @Traveler in my code the term depth is found by count( get_ancestors( $term->term_id, 'category' ) ); the top level terms (Question 1 in your question) would all be in $sorted_terms[0] Second level (2 in your question) in $sorted_terms[1] Third level in $sorted_terms[2] This would continue for as deep as the hierarchy goes. But would get very expensive for deep nesting.
    – Ben Casey
    Commented Mar 9, 2017 at 12:56
  • You are right! But I have problems to make your code running in a template file. Something like echo $sorted_terms[2] is not working.
    – Traveler
    Commented Mar 9, 2017 at 15:03
  • $sorted_items[2] has an array of term objects. You will have to loop over them to get a list or whatever HTML output you need them to be.
    – Ben Casey
    Commented Mar 9, 2017 at 16:41
  • Sorry, I tried a lot, could not make it run. You may have a code example which can be embedded in template file to display for each level?
    – Traveler
    Commented Mar 9, 2017 at 18:53
1

Found the easiest solution with the get_the_terms() | Function. I really do not have a clue why the figures of level 2 ($terms[2]) and 3 ($terms[1]) are not in the correct order, but at least it is realized with a Wordpress function.

Level 1:

<?php
$terms = get_the_terms( $post->ID, 'custom_taxonomy' );
echo $terms[0]->name;
?>  

Level 2:

<?php
$terms = get_the_terms( $post->ID, 'custom_taxonomy' );
echo $terms[2]->name;
?>  

Level3:

<?php
$terms = get_the_terms( $post->ID, 'custom_taxonomy' );
echo $terms[1]->name;
?>  
0

This code is partially working, in this example displaying the terms of third level with 'depth' => 3. Unfortunately the parent terms are listed too, how is it possible to exclude the parent terms?

<?php function wpse244577_list_terms( $taxonomy, $separator = ', ' ) {        
    // Get the term IDs assigned to post.
    $post_terms = wp_get_object_terms( get_the_ID(), $taxonomy, [ 'fields' => 'ids' ] );
    if ( ! empty( $post_terms ) && ! is_wp_error( $post_terms ) ) {
        $terms = wp_list_categories( [
                'title_li'  => '',
                'style'     => 'none',
                'echo'      => false,
                'taxonomy'  => $taxonomy,
                'include'   => $post_terms,
                'separator' => $separator,
                'depth'  => 3,
        ] );
        $terms = rtrim( trim( str_replace( $separator, $separator, $terms ) ), ', ' );
        $terms = explode( $separator, $terms );
        $terms = array_reverse( $terms );
        $terms = implode( $separator, $terms );
        echo  $terms;
    }
} wpse244577_list_terms( 'custom_taxonomy' ); ?>
1
  • Can you show the code you tried using my function?
    – Ben Casey
    Commented Mar 9, 2017 at 23:15
0

Here is an alternative solution. The following function takes an array of arguments, with two possible keys:

  • taxonomy: A string. The name of the Taxonomy.
  • level: An int. Use 0 for the root elements, 1 for the children of the root elements, and so on.

It returns an array of WP_Term objects.

function wpse_259499_get_subterms( $args = array() ) {
    $args = wp_parse_args( $args, array( 'taxonomy' => 'category', 'level' => 0 ) );

    $subterms = get_transient( 'taxonomy-subterms-' . $args['taxonomy'] . '-' . $args['level'] );
    $terms_ids = array();

    if ( is_array( $subterms ) ) {
        return $subterms;
    }

    if ( 0 == $args['level'] ) {
        $terms = get_terms( array(
            'taxonomy' => $args['taxonomy'],
            'get' => 'all',
            'orderby' => 'id',
            'fields' => 'id=>parent',
        ) );

        foreach ( $terms as $term_id => $parent ) {
            if ( ! $parent ) {
                $terms_ids[] = $term_id;
            }
        }
    } else if ( $hierarchy = _get_term_hierarchy( $args['taxonomy'] ) ) {
        $subterms_args = array( 'taxonomy' => $args['taxonomy'], 'level' => $args['level'] - 1 );

        foreach ( wpse_259499_get_subterms( $subterms_args ) as $term ) {
            if ( isset( $hierarchy[ $term->term_id ] ) ) {
                $terms_ids = array_merge( $terms_ids, $hierarchy[ $term->term_id ] );
            }
        }
    }

    if ( $terms_ids ) {
        $subterms = get_terms( array(
            'taxonomy' => $args['taxonomy'],
            'get' => 'all',
            'include' => $terms_ids,
        ) );
    } else {
        $subterms = array();
    }

    set_transient( 'taxonomy-subterms-' . $args['taxonomy'] . '-' . $args['level'], $subterms, HOUR_IN_SECONDS );

    return $subterms;
}

Note 1: If you decide to rename the function, please note that the function is calling itself, so you need to change the name in the recursive call as well.

Note 2: The results are being stored in a transient for an hour. In order to get fresh results during development, you can add the following line at the beginning of the function:

delete_transient( 'taxonomy-subterms-' . $args['taxonomy'] . '-' . $args['level'] );

You may also need to delete those transients every time a category in the taxonomy of interest is added, deleted or modified, if you want to have accurate results all the time.

Example

The following code can be used in a template, to generate an unordered list of links to all terms in the selected level:

<ul>
<?php foreach ( wpse_259499_get_subterms( array( 'taxonomy' => 'category', 'level' => 0 ) ) as $term ): ?>
    <li><a href="<?php echo esc_attr( get_term_link( $term ) ); ?>"><?php echo esc_html( $term->name ); ?></a></li>
<?php endforeach; ?>
</ul>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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