Multiple, nested tax_query relation

I have been stuck on a particular problem for a while now and have yet to find a way around it. I have custom widgets and for each widget I have added the ability to filter posts based on category AND/OR tags (this functionality works). I am now trying to expand on that by including in each widget a custom field where you can enter the ID of a category you wish to exclude (the custom field is already in the widget and stores user input).

Let me show you what I have so far to help understand:

Read More
        $args = array(
            'post_type'         => 'post',
            'post_status'       => 'publish',
            'posts_per_page'    => $num_posts,
            'cat'               => -5,
            'tax_query'         => array(
                'relation'      => 'OR',
                array(
                    'taxonomy'  => 'category',
                    'field'     => 'id',
                    'terms'     => $category,
                ),
                array(
                    'taxonomy'  => 'post_tag',
                    'field'     => 'id',
                    'terms'     => $tags
                ),
            ),
            'offset'            => 1,
        );

As you can see I am taking in two arrays of input for both $category and $tags (this is working fine), but what does not seem to be working is:

'cat'               => -5,

If I remove the tax_query array completely, the exclude category works perfectly fine, so it appears that tax_query is overriding the exclude?

If someone could point me in the right direct, that would be great 🙂

Thanks!

Related posts

Leave a Reply

2 comments

  1. The cat-parameter is not overwritten by tax_query. Instead, it is added to tax_query. Thus, using 'cat' => -5, the following array is appended to tax_query:

    (
        [taxonomy] => category
        [terms] => Array
            (
                [0] => 5
            )
    
        [include_children] => 1
        [field] => term_id
        [operator] => NOT IN
    )
    

    The resulting taxonomy query is built up of three different clauses with the OR-operator. Thus, in your case, adding 'cat' => -5 simply adds the array described above to tax_query, resulting in the query returning that posts that are either

    • not in category 5,
    • in any of the categories in $category, or
    • in any of the tags in $tags

    Nested tax_query

    There is no way to use nested conditionals in tax_query. To achieve the conditional you are looking for (posts in a category from $category or having a tag from tags, but in all cases not being in category 5), you would have to hook into posts_where and possibly posts_join.

    For anyone interested, the same goes for meta_query: this does not support nested queries either.

    EDIT

    Using posts_where

    In response to your comment: to achieve this using posts_where, you will want to apply the filter to just the one query for which you want to exclude a category. To do so, add the filter before creating your query, and remove it afterwards:

    add_filter( 'posts_where', 'myplugin_posts_where' );
    $query = new WP_Query( $args );
    remove_filter( 'posts_where', 'myplugin_posts_where' );
    

    EDIT

    Constructing the additional WHERE-clause

    Using get_tax_sql to construct the additional WHERE-clause is useful as you do not have to construct your SQL manually, and the term IDs are automatically converted to term-taxonomy IDs.

    $clauses = get_tax_sql( array(
        array(
            'taxonomy' => 'category',
            'field' => 'id',
            'terms' => 2,
            'operator' => 'NOT IN'
        )
    ), $wpdb->posts, 'ID' );
    
    $where .= $clauses['where'];
    
  2. Just providing an updated answer about Nested Taxonomies because they are indeed supported. The selected answer above is out of date / a little misleading. WordPress Core has supported Nested Taxonomies since v4.1

    Taken from the WordPress Codex

    The ‘tax_query’ clauses can be nested, to create more complex queries.
    Example: Display posts that are in the quotes category OR both have
    the quote post format AND are in the wisdom category:

    $args = array(
        'post_type' => 'post',
        'tax_query' => array(
            'relation' => 'OR',
            array(
                'taxonomy' => 'category',
                'field'    => 'slug',
                'terms'    => array( 'quotes' ),
            ),
            array(
                            'relation' => 'AND',
                            array(
                        'taxonomy' => 'post_format',
                        'field'    => 'slug',
                        'terms'    => array( 'post-format-quote' ),
                            ),
                            array(
                                    'taxonomy' => 'category',
                                    'field'    => 'slug',
                                    'terms'    => array( 'wisdom' ),
                            ),
            ),
        ),
    );
    $query = new WP_Query( $args );
    

    https://codex.wordpress.org/Class_Reference/WP_Query#Taxonomy_Parameters