Display the deepest child category from Category X (in loop)

I have a number of top-level categories including “News” and “Sport”. They each have multiple levels of child categories as shown below.

In my loop I want to display a single category – what ever is the deepest child category of “Sport”.

I have found some code on StackOverflow that does returns the deepest child category, but not from a specific category. It returns the the deepest child category from all top-level categories. So if a post is in two categories (Sydney and Triathlon) the code shows Sydney as the deepest child category. I somehow need to specify that which top-level category I want the deepest child to come from. In this case that would be “Sport” and the deepest child cat would therefore be “Triathlon”.

Related posts

Leave a Reply

4 comments

  1. The code you referenced in your question is flawed. In most cases it will work but it relies on an assumption that newer child terms are created after their parents, and as a result, have higher IDs.

    To fool the code you posted, do this:

    • Create a new category
    • Edit an older child term
    • Change its parent to the new category
    • Make sure it has a different depth

    You also have the other problem of specificity. In your example, you specify that it should look inside sport, not all, but how would the code know to look specifically in sport? This sounds like you plan to hardcode the Sport category name or ID into your code, which I STRONGLY advise against. Instead a post meta value or metabox UI would be better but not ideal.

    Your question can be distilled to this:

    Given a post A, and a term B, what is the deepest child term of B on post A? Where B is of taxonomy ‘category’.

    This gives us 2 new questions:

    1. How do you determine if term A contains term B?
    2. How do you find out how many ancestors/parents a term has?

    The answers to those questions would implement these 2 functions:

    • get_term_depth( $term ) – returns the depth or number of parents a term has
    • is_term_ancestor( $term, $ancestor ) – is this term an ancestor of another term? Returns true or false

    These functions are not a part of WordPress core, and will need to be written.

    But if using these 2 functions we could then do this:

    function get_deepest_child_term( $terms, $specific_term ) {
        $deepest_term = $specific_term;
        $deepest_depth = get_term_depth( $specific_term );
        foreach ( $terms as $term ) {
            if ( is_term_ancestor( $term, $specific_term ) ) {
                $depth = get_term_depth( $term );
                if ( $depth > $deepest_depth ) {
                    $deepest_term = $term;
                    $deepest_depth = $depth;
                }
            }
        }
        return $deepest_depth;
    }
    

    Which you could then use like this:

    $terms = wp_get_object_terms( $post_id, 'category' );
    $sport_term = get_term_by( 'slug', 'sport', 'category' );
    $deepest_term = get_deepest_child_term( $terms, $sport_term );
    echo $deepest_term->name;
    

    You’ll note I went directly to the taxonomy term API rather than using the higher level middle men such as get_the_category. This way the code is just as valid for custom taxonomies. E.g. a product category.

    The implementation of get_term_depth and the implementation of is_term_ancestor I leave as a task to the reader, and encourage 2 new questions be asked, as they are each useful and interesting questions in their own right. It’s important to break down a task into smaller parts, repeating the process until each part is solvable or bite sized. Both functions would require an understanding of recursion to implement.

  2. OK – I originally misunderstood your question.

    function getLowestCategory()
    {
        $postCategories = get_the_category();
        for ($I = 0;$I<sizeof($postCategories);$I++)
        {
            //this is a top level category
            if ($postCategories[$I])
            {
                continue;
            }
            for ($J=0;J<sizeof($postCategories);$J++)
            {
                //if another category lists it as its parent, it cannot be the lowest category 
                if (strcmp($postCategories[$I]->name,$postCategories[$J]->category_parent)==0)
                break;
            }
            //at this point, no other cateogry says it's its parent, therefore it must be the lowest one
            return $postCategories[$I]->name;
        }
    }
    
  3. I have solved a similar problem based on above mentioned ideas and I have created a gist, That can be viewed here.

    1st I created a function that gets a term’s depth

    function inspiry_get_term_depth( $term_id, $term_taxonomy ) {
        $term_ancestors = get_ancestors( $term_id, $term_taxonomy );
        if ( $term_ancestors ) {
            return count( $term_ancestors );
        }
        return 0;
    }
    

    then I used it with in following code

    $the_terms = get_the_terms( $post_id, $breadcrumbs_taxonomy );
    
        if ( $the_terms && ! is_wp_error( $the_terms ) ) :
    
            $deepest_term = $the_terms[0];
            $deepest_depth = 0;
    
            // Find the deepest term
            foreach( $the_terms as $term ) {
                $current_term_depth = inspiry_get_term_depth( $term->term_id, $breadcrumbs_taxonomy );
                if ( $current_term_depth > $deepest_depth ) {
                    $deepest_depth = $current_term_depth;
                    $deepest_term = $term;
                }
            }
    
            // work on deepest term
            if ( $deepest_term ) {
    
                // Get ancestors if any and add them to breadcrumbs items
                $deepest_term_ancestors = get_ancestors( $deepest_term->term_id, $breadcrumbs_taxonomy );
                if ( $deepest_term_ancestors && ( 0 < count( $deepest_term_ancestors ) ) ) {
                    $deepest_term_ancestors = array_reverse( $deepest_term_ancestors ); // reversing the array is important
                    foreach ( $deepest_term_ancestors as $term_ancestor_id ) {
                        $ancestor_term = get_term_by( 'id', $term_ancestor_id, $breadcrumbs_taxonomy );
                        $inspiry_breadcrumbs_items[] = array(
                            'name' => $ancestor_term->name,
                            'url' => get_term_link( $ancestor_term, $breadcrumbs_taxonomy ),
                            'class' => ''
                        );
                    }
                }
    
                // add deepest term
                $inspiry_breadcrumbs_items[] = array(
                    'name' => $deepest_term->name,
                    'url' => get_term_link( $deepest_term, $breadcrumbs_taxonomy ),
                    'class' => ''
                );
    
            }
    
        endif;
    

    I hope it will help.

  4. I have found some code on StackOverflow that does returns the deepest
    child category, but not from a specific category

    So what you are missing is, to specify a category. Why dont you do it? It’s simple

    $catID = get_cat_ID( 'Sports' );
    $categories = get_the_category( $catID );
    if ( $categories ) {
        $deepChild = get_deep_child_category( $categories );
        ?>
            <a href="<?php echo get_category_link( $deepChild->term_id ); ?>" title="<?php echo sprintf( __( "View all posts in %s" ), $deepChild->name ); ?>"><?php echo $deepChild->name; ?></a>
        <?php 
    }
    
    function get_deep_child_category( $categories )
    {
        $maxId = 0;
        $maxKey = 0;
        foreach ( $categories as $key => $value )
        {
            if ( $value->parent > $maxId )
            {
                $maxId = $value->term_id;
                $maxKey = $key;
            }
        }
        return $categories[$maxKey];
    }