12

I'm creating an age select menu in the admin, populated from a taxonomy of age. The taxonomy is hierarchical as follows:

  • 18-25 (parent, ID 183)
    • 18 (child)
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 26-30 (parent, ID 184)
    • 26
    • 27
    • 28
    • 29
    • 30

I would like to only list the children (18, 19 etc) and not the parents (18-25, 26-30) etc. Currently I am using get_terms with the parent argument, but it doesn't accept more than 1 parent ID. Here's what I have so far, which shows the children from 18-25.

    $ages = get_terms( 'age', array(
        'hide_empty' => 0,
        'parent' => '183',
    ));

Here's what I want it to do, but isn't supported. I have also tried it with an array but it doesn't work either.

    $ages = get_terms( 'age', array(
        'hide_empty' => 0,
        'parent' => '183,184',
    ));

I see there is a get_term_children function but I'm unsure of how to use this either as it looks like it only accepts one value also. Eg: In this example it would build an unordered list but I could modify for select menu.

<?php
    $termID = 183;
    $taxonomyName = "age";
    $termchildren = get_term_children( $termID, $taxonomyName );

    echo '<ul>';
    foreach ($termchildren as $child) {
    $term = get_term_by( 'id', $child, $taxonomyName );
    echo '<li><a href="' . get_term_link( $term->name, $taxonomyName ) . '">' . $term->name . '</a></li>';
    }
    echo '</ul>';
?> 
3
  • Call get_terms twice and merge the two arrays of results?
    – t31os
    Commented Jul 24, 2011 at 15:21
  • @Mark Thanks, I thought of doing it this way, but even if I merge the ID's into an array, I can't see how it would work because it would be the same as listing them manually - 183, 184, which doesn't work.
    – Andrew
    Commented Jul 24, 2011 at 23:43
  • Having read the accepted answer i now realise your question wasn't entirely clear, from the looks of things you wanted all terms, excluding top level ones.. (which you can do with a single get_terms call). Your question read as if you were wanting all children of 2 particular parent terms..
    – t31os
    Commented Jul 26, 2011 at 16:03

6 Answers 6

19

This should work for you:

$taxonomyName = "age";
//This gets top layer terms only.  This is done by setting parent to 0.
$parent_terms = get_terms(
  $taxonomyName,
  array(
    'parent' => 0,
    'orderby' => 'slug',
    'hide_empty' => false
  )
);

echo '<ul>';
foreach ( $parent_terms as $pterm ) {
  //Get the Child terms
  $terms = get_terms(
    $taxonomyName,
    array(
      'parent' => $pterm->term_id,
      'orderby' => 'slug',
      'hide_empty' => false
    )
  );
  foreach ( $terms as $term ) {
    echo '<li><a href="'. get_term_link($term) . '">'. $term->name .'</a></li>';
  }
}
echo '</ul>';
4
  • Thanks @Manny, worked a treat. I took what you provided and mashed it into the select menu I was after. Great stuff.
    – Andrew
    Commented Jul 25, 2011 at 4:57
  • No problem. Glad it worked out for you. Commented Jul 25, 2011 at 5:21
  • 2
    See karimhossenbux's answer below for a much more efficient answer.
    – dotancohen
    Commented Mar 15, 2016 at 16:47
  • $term->name is invalid in get_term_link(), it only accepts the term ID, slug or object Commented Mar 15, 2016 at 18:03
17

You could also do:

$terms = get_terms($taxonomyName);
foreach($terms as $term) {
    if ($term->parent != 0) { // avoid parent categories
        //your instructions here
    }
}

I've noted that parent have "parent" field equal to 0, and a child have his parent id in it.

4
  • 6
    The accepted answer runs N get_terms() calls and runs in polynomial time. This answer runs a single get_terms() call and runs in linear time. This is a much better answer.
    – dotancohen
    Commented Mar 15, 2016 at 16:46
  • @dotancohen You can do this without having to exclude top level terms on output, you can remove top level terms with the wpse_exclude_top parameter added to the query arguments ;-). I do however agree, this is faster than the accepted answer Commented Mar 15, 2016 at 18:05
  • 1
    @PieterGoosen: Thank you Pieter. I just grepped the 4.4.2 source code for the strings wpse_exclude_top and exclude_top but did not find them. Nor does google know about that. Where is it documented?
    – dotancohen
    Commented Mar 16, 2016 at 8:23
  • 1
    @dotancohen in my answer ;-) Commented Mar 16, 2016 at 8:24
6

We can exclude the top level parents by filtering them out by using the terms_clauses filter to alter the SQL query before it executes. This way we do not need to skip parents in the final foreach loop as they are not in the returned array of terms, this saves us unnecessary work and coding

You can try the following:

add_filter( 'terms_clauses', function (  $pieces, $taxonomies, $args )
{
    // Check if our custom arguments is set and set to 1, if not bail
    if (    !isset( $args['wpse_exclude_top'] ) 
         || 1 !== $args['wpse_exclude_top']
    )
        return $pieces;

    // Everything checks out, lets remove parents
    $pieces['where'] .= ' AND tt.parent > 0';

    return $pieces;
}, 10, 3 );

To exclude top level parents, we can now pass 'wpse_exclude_top' => 1 with our array of arguments. The new wpse_exclude_top parameter is supported by the filter above

$terms = get_terms( 'category', ['wpse_exclude_top' => 1] );
if (    $terms
     && !is_wp_error( $terms )
) {
    echo '<ul>';
        foreach ($terms as $term) {
            echo '<li><a href="' . get_term_link( $term ) . '">' . $term->name . '</a></li>';  
        }
    echo '</ul>';
}

Just a note, get_term_link() do no not accept the term name, only, slug, ID or the complete term object. For performance, always always pass the term object to get_term_link() if the term object is available (as in this case)

1
  • Hi, any idea how to solve this one? stackoverflow.com/q/70327174/9036446
    – evavienna
    Commented Dec 17, 2021 at 5:22
2

Why can't you just set the childless argument to true?

$ages = get_terms( array(
    'taxonomy' => 'age',
    'childless' => true
    )
);
3
  • setting the childless argument to true means you can only go 1 level deep, so this doesn't work for taxonomies with 3 or more levels.
    – GeckoSEO
    Commented Apr 23, 2018 at 15:58
  • This worked perfectly for my needs! Thank you!
    – jsmod
    Commented Oct 11, 2020 at 16:54
  • Also it will return parents without children instead of children without parents. Commented Jan 2, 2021 at 21:15
0
$taxonomy = 'age';
foreach( get_terms( [ 'taxonomy' => $taxonomy, 'parent' => 0 ] ) as $parent )
{
    foreach( get_terms[ 'taxonomy' => $taxonomy, 'parent' => $parent->term_id ] as $child )
    {
        // do stuff
        print_r( $child );
    }
}
-1

If you display multiple parent's child, you can try this. Display mention term ids child term.

$termIds = array(367, 366, 365, 364, 363, 362);
$taxonomyName = "age";

$args = array(
    'orderby'           => 'term_id', 
    'order'             => 'DESC',                      
    'hide_empty'        => false,                       
    'childless'         => false,
); 

$terms = get_terms( $taxonomyName, $args );

if ( ! empty( $terms ) && ! is_wp_error( $terms ) ){
$inc = 1;
foreach ( $terms as $term ) {                           
 if (in_array($term->parent, $termIds)) {
  echo '<option value="'.$term->term_id.'"><font><font>'.$term->name.'</font></font></option>';
    }
  }
}
2
  • 1
    Please explain why you think this code should work. Also, I am pretty sure that a hard-coded solution is not the best way.
    – s_ha_dum
    Commented Dec 29, 2015 at 14:23
  • Question mentions with ids, for this reason I have answered related think. Commented Dec 30, 2015 at 15:23

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.