Get direct children of custom taxonomy

I’ve googled extensively and tried my best, but I just can’t get the first level children of the custom taxonomy in the code below to work! It only displays the parent level. What I am trying to achieve is the following:

Level 1  <--- at the moment, it only displays this level
  Level 2  <-- I want to display this level too
    Level 3  <-- but NOT this level

This is my code so far:

function my_dropdown_categories( $taxonomy, $current_selected = '', $include = null ) {
// Get all terms of the chosen taxonomy
$terms = get_terms($taxonomy, array('orderby' => 'name'));

// our content variable
$list_of_terms = '<select id="location" class="selectboxSingle" name="location">';

if ( ! is_wp_error( $terms ) ) foreach($terms as $term){

// If include array set, exclude unless in array.
if ( is_array( $include ) && ! in_array( $term->slug, $include ) ) continue;

$select = ($current_selected == $term->slug) ? "selected" : ""; // Note: ==

if ($term->parent == 0 ) {

    // get children of current parent.
    // $tchildren = get_term_children($term->term_id, $taxonomy); <- gets ALL children

    $uchildren =get_terms( $taxonomy, array('hide_empty' => 0, 'parent' => $term->term_id ));

    $children = array();
    foreach ($uchildren as $child) {
        $cterm = get_term_by( 'id', $child, $taxonomy );
        // If include array set, exclude unless in array.
        if ( is_array( $include ) && ! in_array( $cterm->slug, $include ) ) continue;
        $children[$cterm->name] = $cterm;
    }
    ksort($children);

    // PARENT TERM      
    if ($term->count > 0) {
      $list_of_terms .= '<option class ="group-result" value="'.$term->slug.'" '.$select.'>' . $term->name .' </option>';
    } else {
      $list_of_terms .= '<option value="'.$term->slug.'" '.$select.'>'. $term->name .' </option>';
    };

    // now the CHILDREN.
    foreach($children as $child) {
       $select = ($current_selected == $child->slug) ? "selected" : ""; // Note: child, not cterm
       $list_of_terms .= '<option class="result-sub" value="'.$child->slug.'" '.$select.'>'. $child->name.' </option>';
    } //end foreach
  }
}

$list_of_terms .= '</select>';

return $list_of_terms;
}

If someone can help me with this beast, you’ll be my freaken hero!

EDIT (more info based on Pieter’s answer):

This is what is being output (all parents):

Parent 1

–Parent 1

–Parent 2

–Parent 3

–Parent 4

Parent 2

–Parent 2

–Parent 1

–Parent 3

etc

Solutions Collecting From Web of "Get direct children of custom taxonomy"

You have two issues here, one is a performance issue, the other is big issue that should return a pile of errors if you enable debug

The performance issue here is your first instance where you are getting the terms. It seems that your goal here is to get only top level terms, yet you get all. I would just add 'parent' => 0 to the arguments to get only top level terms. You can then drop the condition where you check if ( $term->parent == 0 ) because all terms will be top level terms which will all have 0 as term parent, so this condition will always return true

Your big issue is with this code, and I don’t understand your logic here

    $uchildren =get_terms( $taxonomy, array('hide_empty' => 0, 'parent' => $term->term_id ));

    $children = array();
    foreach ($uchildren as $child) {
        $cterm = get_term_by( 'id', $child, $taxonomy );
        // If include array set, exclude unless in array.
        if ( is_array( $include ) && ! in_array( $cterm->slug, $include ) ) continue;
        $children[$cterm->name] = $cterm;
    }
    ksort($children);

You are correct in getting your direct term children, so your declaration of $uchildren is fine. From there everything goes haywire

  • Why are you using get_term_by() here. $child already holds the term properties as you have used get_terms() to retrieve the terms.

  • You are not just using get_term_by() wrongly here, but your arguments are invalid, and you are passing a complete object to it as well. You should consult the codex for proper use of this function. All in all, that section should be removed from your code

That section should look something like this

    $uchildren =get_terms( $taxonomy, array('hide_empty' => 0, 'parent' => $term->term_id ));

    $children = array();
    foreach ($uchildren as $child) {
        if ( is_array( $include ) && ! in_array( $child->slug, $include ) ) continue;
        $children[$child->name] = $child;
    }
    ksort($children);

EDIT

Here is a full working code with a couple of bugs fixed. Please see my comments in code for the updates

function my_dropdown_categories( $taxonomy, $current_selected = '', $include = null ) 
{
    /*
     * Declare your variable first. Without this, your code has a bug if no terms are found
     */
    $list_of_terms = '';

    /**
    * Get all parent terms. Note we use 'parent' => 0 to only get top level terms
    *
    * @see get_terms
    * @link http://codex.wordpress.org/Function_Reference/get_terms
    */
    $terms = get_terms( $taxonomy, array( 'orderby' => 'name', 'parent' => 0 ) );

    /*
    * Use curlies here to enclose your statement. Also, check whether or not you have terms
    */
    if ( $terms && ! is_wp_error( $terms ) ) {

        /*
        * Moved this section inside your if statement. We don't want to display anything on empty terms
        */
        $list_of_terms .= '<select id="location" class="selectboxSingle" name="location">';

        foreach ( $terms as $term ) {

            // If include array set, exclude unless in array.
            if ( is_array( $include ) && ! in_array( $term->slug, $include ) ) continue;

            $select = ($current_selected == $term->slug) ? "selected" : ""; // Note: ==

            /*
             * Use the parent term term id as parent to get direct children of the term
             * Use child_of if you need to get all descendants of a term
             */
            $uchildren = get_terms( $taxonomy, array('hide_empty' => 0, 'parent' => $term->term_id ));

            $children = array();
            foreach ($uchildren as $child) {
                // If include array set, exclude unless in array.
                if ( is_array( $include ) && ! in_array( $child->slug, $include ) ) continue;
                $children[$child->name] = $child;
            }
            ksort($children);

            // PARENT TERM      
            if ($term->count > 0) {
                $list_of_terms .= '<option class ="group-result" value="'.$term->slug.'" '.$select.'>' . $term->name .' </option>';
            } else {
                $list_of_terms .= '<option value="'.$term->slug.'" '.$select.'>'. $term->name .' </option>';
            };

            // now the CHILDREN.
            foreach($children as $child) {
                $select = ($current_selected == $child->slug) ? "selected" : ""; // Note: child, not cterm
                $list_of_terms .= '<option class="result-sub" value="'.$child->slug.'" '.$select.'>'. $child->name.' </option>';
            } //end foreach

        }

        /*
        * Moved this section inside your if statement. We don't want to display anything on empty terms
        */
        $list_of_terms .= '</select>';

    }
    return $list_of_terms;
}

Check this code

$uchildren =get_terms( 'category', array('hide_empty' => false, 'parent' => $term->term_id ));

$children = array();
foreach ($uchildren as $child) {
    $cterm = get_term_by( 'id', $child, $taxonomy );
    // If include array set, exclude unless in array.
    if ( is_array( $include ) && ! in_array( $cterm->slug, $include ) ) continue;
    $children[$cterm->name] = $cterm;
}

This line

$cterm = get_term_by( 'id', $child, $taxonomy ); 

assumes that $child will be an id (and an integer), whereas:

foreach ($uchildren as $child) {

gives us an object

This also is the reason why this doesn’t work as you want:

if ( is_array( $include ) && ! in_array( $cterm->slug, $include ) ) continue;

From here is junk that I had posted but good sense prevailed, till…

Let’s do the fun part:

var_dump($term):

Notice the type of each property:

object(stdClass)(9) {
    ...
    ["parent"]=>
    string(1) "0"
    ...
}

See the parent. It’s a string. I know, logically it should be an integer, but it’s a string. It sucks, right!

So, your if ($term->parent == 0 ) { condition is not getting satisfied. Try this instead:

if ( empty( $term->parent ) ) {

here