Combine two taxonomies in a hierarchical tree

To get a list of all terms of taxonomy X whose posts are associated to terms from taxonomy Y too, we have to:

  1. Get all term IDs for both taxonomies
  2. Create a tax query to fetch all posts, because we don’t want to show empty term archives.
  3. Format the result in a hierarchical list.

Let’s go!

  1. Getting the term IDs is simple:

    get_terms( $taxonomy_name, array( 'fields' => 'ids' ) )
    

    We just have to do that two times (for each taxonomy once).

  2. The taxonomy query needs a relationship AND to make sure all posts we get are in both taxonomies:

        'tax_query'        => array (
            'relation' => 'AND',
            array(
                'taxonomy' => $first,
                'field'    => 'id',
                'terms'    => get_terms( $first, array( 'fields' => 'ids' ) )
            ),
            array(
                'taxonomy' => $second,
                'field'    => 'id',
                'terms'    => get_terms( $second, array( 'fields' => 'ids' ) )
            ),
        ),
    
  3. For formatting, we create an array where the terms of the first taxonomy are the keys and the terms of the second taxonomy are the values. Then we build a nested list with plain <ul> elements.

Now the function: I have used tags and categories as taxonomies, just because it was easier to test.

function double_term_tree(
    $post_types = array( 'post', 'page' ),
    $first="category",
    $second     = 'post_tag'
    )
{
    $query = new WP_Query(
        array (
            'numberposts'      => -1,
            'suppress_filters' => TRUE,
            'posts_per_page'   => -1,
            'post_type'        => $post_types,
            'tax_query'        => array (
                'relation' => 'AND',
                array(
                    'taxonomy' => $first,
                    'field'    => 'id',
                    'terms'    => get_terms( $first, array( 'fields' => 'ids' ) )
                ),
                array(
                    'taxonomy' => $second,
                    'field'    => 'id',
                    'terms'    => get_terms( $second, array( 'fields' => 'ids' ) )
                ),
            ),
        )
    );

    if ( empty ( $query->posts ) )
        return;

    $result_list = array();
    $output="<ul>";

    foreach ( $query->posts as $post )
    {
        $first_terms  = get_the_term_list( $post->ID, $first, '', '|' );
        $second_terms = get_the_term_list( $post->ID, $second, '', '|' );

        $f_term_array = explode( '|', $first_terms );
        $s_term_array = explode( '|', $second_terms );

        foreach ( $f_term_array as $f_term )
        {
            if ( ! isset ( $result_list[ $f_term ] ) )
                $result_list[ $f_term ] = array();

            $result_list[ $f_term ] = array_merge( $result_list[ $f_term ], $s_term_array );
        }
    }

    foreach ( $result_list as $k => $v )
    {
        $result_list[ $k ] = array_unique( $v );
        $output           .= "\n<li>$k\n\t<ul>\n\t\t<li>"
            . join( "</li>\n\t\t<li>", array_unique( $v ) )
            . "</li>\n\t</ul>\n</li>";
    }

    $output .= '</ul>';

    return $output;
}

You can call this function like this:

echo double_term_tree( 'product', 'brand', 'category' );

And then you get that tree.

Leave a Comment