2

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:

        $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!

3

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'];
  • Thanks @engelen ! Using your suggestion I created a function with posts_where and when I print out the Query I can see my custom inclusion, but on the Front-end nothing displays. The page appears to stop loading other areas as well. function restrict_post_by_id( $where ) { global $wpdb; $where .= " AND ( db_term_relationships.term_taxonomy_id NOT IN (SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id=5)"; return $where; } add_filter( "posts_where" , "restrict_post_by_id" ); – Shane Mar 27 '14 at 16:56
  • First of all, the posts_where filter is applied to all WordPress post queries (WP_Query) that do not explicitly suppress filtering. There are two arguments passed to posts_where: $where, the WHERE-clause, and $query, the WP_Query instance for which the clause is being filtered. You will want to build a check to make sure the WHERE-clause is only altered for your specific query. It might be a good idea to output the actual query (print_r( new WP_Query( $args ) );) to gain some insight in the query generated. – engelen Mar 28 '14 at 11:10
  • How am I able to determine the current query, or pass the specific query to the filter. Basically every widget has a custom field to enter an ID to exclude. Each one of those widgets has a new WP_Query object (with a different name of course) and multiple widgets with will appear on the same page. For example, you might have one widget excluding Category ID 5, but another on the same page excluding Tag ID 32. I can't quite wrap my head around this. – Shane Mar 28 '14 at 14:12
  • You could add the filter right before starting the query and remove it after the query is executed, thus making sure it only applies to that specific query. I will update my answer to include example code for this. – engelen Mar 28 '14 at 14:20
  • 1
    I believe this answer is out of date. It looks like Wordpress Core can handle nested taxonomies. core.trac.wordpress.org/ticket/29738 – italiansoda Aug 1 '18 at 21:23
5

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

  • I am glad I scrolled down to read this. Thank you. – hsimah Nov 26 '18 at 8:22

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.