5

For example, I have WooCommerce products that have taxonomies:

  • brand (e.g. Converse, adidas, D&G) (taxonomy id: product_brand)
  • category (e.g. Boots, Sneakers, Sandals, Heels) (taxonomy id: product_cat)

Of course, not every brand includes all categories.

I need to list the categories by brand. (something similar to what Zappos does. E.g.:

Converse
- Sandals (2)
- Sneakers (6)

D&G
- Boots (10)
- Heels (4)
- Sandals (7)

Is there any light way to do this except looping through all products with specific brand and checking for its category?

  • Categories of a term does not make sense. Category is a taxonomy, Brand is also a taxonomy, they are relational groupings for posts(or post types) they have no direct relation to one another(aside from the fact they could both be associated with the same post). – t31os May 1 '13 at 15:06
  • Thank you for reply. I understand that they have no any built-in relation, but is there really no way to list them? I've just found an article - wprecipes.com/… but can't seem to adapt it for my needs. – Marvin3 May 1 '13 at 15:13
  • As toscho posted, but naturally you'll need two calls(one for each taxonomy). – t31os May 1 '13 at 15:26
8

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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