1

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

  • News

    • Australia
      • NSW
        • Sydney
  • Sports

    • Cycling
      • Road Cycling
      • BMX
    • Triathlon

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".

3

4 Answers 4

3

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.

0

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;
    }
}
4
  • Thanks for the help @fdsa. Your solution did indeed target the correct top-level category, but it pulls out the last child category (not necessarily the deepest). In the instance of my demo categories, "Sports" is category 2. And if I use your code of get_categories('child_of=2') I might have been wrong path when I said the code from StackOverflow was close, because Jun 8, 2012 at 6:52
  • (I always hit the "Enter" key by habit which send my reply before I've finished it). In the instance of my demo categories, "Sports" is category 2. And if I use your code of get_categories('child_of=2'), then the "Triathlon" child category is shown for all posts (even posts that aren't in that category). I think that's the difference between get_the_category and get_categories isn't it? That get_categories returns objects for each category assigned to the post. Jun 8, 2012 at 6:59
  • 1
    @Thao does the edit work for you?
    – fdsa
    Jun 12, 2012 at 5:30
  • This answer makes no attempt to explain how it answers the question. Can you explain the code and how it solves the problem, and what you did?
    – Tom J Nowell
    Jul 7, 2014 at 21:51
0

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.

-1

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];
}
1
  • get_the_category takes a post ID not a term ID
    – Tom J Nowell
    Jul 7, 2014 at 22:19

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.