Show certain terms from custom taxonomy but exclude ‘parent’ terms?

I have a custom post type “mycustom_products” (hierarchical) that has custom taxonomies called ‘categories’ and ‘tags’ (as many other custom post types do). My custom category is called “myprod_category” (also hierarchical) and my tag taxonomy is called “product_tag” (not hierarchical).

I want to show a list of Product Tags that correspond ONLY to certain Product Categories.

For example, with Product Categories like “soap” & “lotion”, there might be tags called “Blueberry” or “Lavender” (scents) but “jewelry” would have tags like “silver” or “gold”. I want to be able to have a listing of the “scent” only tags (which would be the ones that correspond only with the “soap” and “lotion” categories. I don’t want the tags for “jewelry” to be included. So any tags that have been used on products entered in those categories would be included, and any used for other categories wouldn’t be.

Obviously I could hard code everything into the theme, but if/when the client adds more tags, I would have to go in and re-code everything and I’d like to avoid doing that.

I tried using <?php wp_tag_cloud( array( 'taxonomy' => 'product_tag', format => 'list' ) ); ?> to list out all my tags, but I can’t seem to get the EXCLUDE argument to prevent whole “product categories” from showing. Seems like I can do it using the tag_ID but that would be tedious, and would put me back to hard coding them into the theme and having to update each time a new tag is added.

Is there a filter or something I can use to exclude a category? Or is there a better way to list tags in this fashion. Thanks!

2 Answers
2

If you have a lot of products, probably a custom sql query is a better option.

I’d probably recommend following that route either way. Custom queries may be ‘icky’, but it’ll lighten resources and be a darn slight quicker in most cases*

I’ve written a function that does most of the labour, and may well come in handy in other scenarios (I’ve used the word ‘soft’ to imply the relations are not explicit).

/**
 * Get terms that are indirectly associated with others through the posts they
 * are attached to.
 * 
 * @see http://codex.wordpress.org/Function_Reference/WP_Query#Taxonomy_Parameters
 * @link http://wordpress.stackexchange.com/questions/16393/
 * 
 * @param string|array $taxonomy The taxonomy(s) of the terms you wish to obtain.
 * @param array $tax_query A tax query for the posts you want to make the association with.
 * @return array Array of term IDs.
 */
function get_terms_soft_associated( $taxonomy, $tax_query  )
{
    global $wpdb;

    // so you can pass a single tax query rather than wasted nested array
    if ( isset( $tax_query['taxonomy'] ) )
        $tax_query = array( $tax_query );

    $tax = new WP_Tax_Query( $tax_query );
    extract( $tax->get_sql( $wpdb->posts, 'ID' ) );

    if ( empty( $join ) || ( !$posts = $wpdb->get_col( "SELECT $wpdb->posts.ID FROM $wpdb->posts $join WHERE 1=1 $where" ) ) )
        return array();

    $taxonomy = implode( "','", array_map( 'esc_sql', ( array ) $taxonomy ) );
    $posts = implode( ',', wp_parse_id_list( $posts ) );

    return $wpdb->get_col(
        "SELECT DISTINCT t.term_id FROM $wpdb->terms AS t " .
        "INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id " .
        "INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id " .
        "WHERE tt.taxonomy IN('$taxonomy') AND tr.object_id IN($posts)"
    );
}

You can also check out the codex on tax queries for more help with $tax_query. This is the query we’re using to filter posts, before we do a reverse lookup to gather all their terms for another taxonomy.

Now onwards to the solution;

// suggestion 1 - for a single post
$product_cats = get_the_terms( get_the_ID(), 'myprod_category' );

// suggestion 2 - for a product category archive
$product_cats = get_queried_object_id();

// suggestion 3 - for all posts on the current page of an archive
foreach( $wp_query->posts as $_post ) {
    $_product_cats = get_the_terms( $_post->ID, 'myprod_category' );
    foreach ( $_product_cats as $_product_cat )
        $product_cats[] = $_product_cat->term_id;
}

// now get the 'associated' tag IDs
$product_assoc_tags = get_term_soft_association( 'product_tag', array(
    'taxonomy' => 'myprod_category',
    'field'    => 'term_id',
    'terms'    => $product_cats
) );

wp_tag_cloud( array( 'taxonomy' => 'product_tag', 'include' => $product_assoc_tags ) );

Note the suggestions are examples of how to grab all the product categories to later query against, depending on your circumstance and which post(s) you want to affect the outcome (only use one of the suggestions, or your own!).

If you find it’s not working as you expected, chances are we just need to tweak the tax query for the second argument of the function.

*Footnote: Native post querying pulls in all post data, whilst we only need to work with IDs. Saving memory where you can always helps, and as @Daniel says, if we’re talking a lot of posts, we’re also talking a lot of memory.

I also say quicker as, under out-of-the-box conditions, we’re using far fewer database queries & processing than had we used functions like get_terms() and get_posts().

Leave a Comment