0

So I have this category structure

- Recipes
   |-- Asian Food
        |-- Chinese Food
        |-- Japanese Food
        |-- Taiwanese Food
        |-- etc...
   |-- European Food
        |-- etc...
   |-- American Food
        |-- etc...
   |-- etc...
- Articles
   |-- News
        |-- etc...
   |-- Tutorial
        |-- etc...
   |-- Gossip
   |-- etc...

If I have category Recipes (slug: recipes), how to get its direct children [Asian Food, European Food, American Food]?

And if I have category Asian Food (slug: asian-food), how to get [Chinese Food, Japanese Food, Taiwanese Food]?

This is what I tried.

function getChild ($slug) {
   return get_terms('category', ['parent' => $slug]);
}

// $parentSlug = explode('/', trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/'))[0];
$parentSlug = 'recipes';
$children = getChild($parentSlug); // returns blank array

1 Answer 1

2

You may use get_term_children() to get children of a term but it return only ids so you could use get_term_by() to convert it to id

Approach 3

This method is based WP builtin on get_term_children() and _get_term_hierarchy() but converted to support slug. The whole set of functions are presented here for reference.

  • Like builtin version, return unlimited number of children
  • because it is based on builtin, cache system is also implemented
  • cache clean up logic is also added

Please feel free to adjust and test before production use. Approach 3 outputs unlimited of children. If find any bugs or insufficient, please feel free to comment.

// this is based on WP Core, return the immediate 1 level of children
function get_term_children_in_slug( $term_slug, $taxonomy ) {
    if ( ! taxonomy_exists( $taxonomy ) ) {
        return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    }

    $term_data = _get_term_hierarchy_in_slug( $taxonomy );
    $terms = $term_data['children'];
    $term_slug_ids = $term_data['term_slug_ids'];

    if ( ! isset( $term_slug_ids[ $term_slug ] ) ) {
        return array();
    }

    if( ! isset( $terms[ $term_slug_ids[ $term_slug ] ] ) ) {
        return array();
    }

    $children = $terms[ $term_slug_ids[ $term_slug ] ];

    // find children of children
    foreach ( (array) $terms[ $term_slug_ids[ $term_slug ] ] as $child_slug => $child_id ) {
        if ( $term_slug === $child_slug ) {
            continue;
        }

        if ( isset( $terms[ $term_slug_ids[ $child_slug ] ]) ) {
            $children = array_merge( $children, get_term_children_in_slug( $child_slug, $taxonomy ) );
        }
    }

    return $children;
}

// this is based on WP Core for get_term_children() but support slug instead
function _get_term_hierarchy_in_slug( $taxonomy ) {
    if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
        return array();
    }

    // WP Builtin with cache by saving to option, reduce performance hit
    $children = get_option( "{$taxonomy}_children_slug" );

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

    $children = array();
    $terms = get_terms( 'category', array(
        'hide_empty' => false,
        // 'orderby'                => 'id', // haven't used in test, optional
        'fields'                 => 'all_with_object_id',
    ) );

    $term_ids = []; // as cache to find out terms parent slug
    $compare = [];

    // all parents become level 1 id => level 2 id structure
    foreach ( $terms as $key => $term ) {
        $term_ids[ $term->slug ] = $term->term_id;
        if ( $term->parent > 0 ) {
            // $compare[ $term->slug ][] = $term->parent;
            $children[ $term->parent ][$term->slug] = $term->term_id;
        } 
    }

    update_option( "{$taxonomy}_children_slug", $children );

    // children for use, term ids for matching
    return array(
        'children' => $children,
        'term_slug_ids' => $term_ids
    );
}

// add clean up cache logic together with builtin clean up
add_action( 'clean_taxonomy_cache', 'custom_clean_up_term_cache' );
function custom_clean_up_term_cache( $taxonomy ) {
    delete_option( "{$taxonomy}_children_slug" );
}

Approach 2 (Recommended method since it is the most simplest)

Asker combine get_term_by() in the first answer with get_categories() This method

  • depends on args, could return either direct descendants or all children including grandchilren

Reference posts

// this method only return 1 level of immediate children
function get_term_children_in_slug_approach2( $term_slug ) {
    $parent = get_term_by( 'slug', $term_slug, 'category' );
    $term_slugs = (get_categories([
        'taxonomy' => 'category',
        'parent' => $parent->term_id,
        'hide_empty' => false, // in the test, have no posts
    ]));

    return $term_slugs;
}

// the following return children and grandchildren
function get_term_children_in_slug_approach2( $term_slug ) {
    $parent = get_term_by( 'slug', $term_slug, 'category' );
    $term_slugs = (get_categories([
        'taxonomy' => 'category',
        'child_of' => $parent->term_id,
        'hide_empty' => false, // in the test, have no posts
    ]));

    return $term_slugs;
}

Approach 1

Because the WP Builtin tools are IDs based, so using the builtin get_term_chilren() is not straightforward and need to do ID to slug searching.

  • return the first immediate level of children
function getChild ( $slug ) {
    $term = get_term_by( 'slug', $slug, 'category' );
    $term_children = get_term_children( $term->term_id, 'category' );

    // return a list of children id
    if( $term_children ) {
        return get_term_children( $term->term_id, 'category' );
    }

    return $term_children;
}

after you have got a list of children id, you can convert it back for a slug

// helper for retrieve slug from $term ID
function get_term_slug( $id ) {
    $term = get_term_by( 'id', $id, 'category' );
    // var_dump($term);
    if( $term ) {
        return $term->slug;
    }

    return $term;
}

// get a list of terms slug from term id
$term_slugs = [];
$term_children_ids = getChild( 'level1' ); // example

foreach ($term_children_ids as $key => $term_id) {
    $term_slugs[] = get_term_slug( $term_id );
}
8
  • isn't calling get_term_by and get_term_slug runs database query? if so, I don't think it is a good idea to call that inside a loop May 4, 2020 at 11:00
  • It uses WP_Term_Query. I think it should be getting from cache if using repeatedly, you may refer to wp-includes/class-wp-term-query.php around line 700 to have a look and confirm if you worry that it will be a performance hit. May 4, 2020 at 11:05
  • When I write the above code as a quick solutions. I have also considered another possible solutions is based on the get_term_children to write a version with slug output. May 4, 2020 at 11:11
  • I use your advice about get_term_by() to get the parent, and then get the children with get_categories(['parent' => $parent->term_id]). So, it just calls 2 query. Maybe you want to put it in your answer, and I'll accept it. May 4, 2020 at 11:37
  • 1
    After reading some posts, I think your approach is the most simplest and effective for your situation. Also, only changing the args 'parent' to 'child_of' could return also grandchildren. I have updated the answers and posts that I think is of good reference. Thanks again for your feedback. May 5, 2020 at 2:31

Your Answer

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

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