When/why does ‘$query->get( ‘tax_query’ );’ return empty?

First the code:

function itsme_better_editions( $query ) {
    if ( $query->is_category() && $query->is_main_query() ) {
        $query->set( 'post_type', array( 'post' ) );

        // Get current tax query
        $tax_query = $query->get( 'tax_query' );

        $tax_query['relation'] = 'OR';

        $tax_query[] = array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => 'intl',
            'operator' => 'IN'
        );

        $query->set( 'tax_query', $tax_query );
    }
    return $query;
}
add_filter( 'pre_get_posts', 'itsme_better_editions' );

It all seems right to me, except $query->get( 'tax_query' ); seems to return empty or not an array, i.e. the following condition is returning true:

Read More
if( !empty($tax_query) || is_array($tax_query) ) {
   // whatever
}

This is breaking stuff, I later realized.

The right posts are being shown in the archive i.e. posts that belong to either the current category OR ‘International (intl)’ category are listed. Which is what I want.

But the term object is pointed to ‘intl’ category (the same happens in tag archives as well; instead of term object pointing to the current tag, it points to ‘intl’ category). For example, if I visit the ‘UK (uk)’ category archive, the displays ‘International’ instead of ‘UK’.

This is just one of the problems I’ve noticed so far; I don’t know what else is broken.

What’s wrong with the function?


PS: And because $query->get( 'tax_query' ); was returning empty or not an array, I had to manually fill it up like so:

/*
 * Show posts assigned to 'International (intl)' Edition
 * in all editions.
 */
function itsme_better_editions( $query ) {
    if( $query->is_category() && $query->is_main_query() ) {

        $query->set( 'post_type', array( 'post' ) );

        // NOT WORKING!!!
        //$tax_query = $query->get( 'tax_query' );

        // Equivalent of original `$tax_query` START.
        $get_original_category = get_query_var( 'category_name' );
        $original_category = get_term_by( 'slug', $get_original_category, 'category' );
        if( $original_category && !is_wp_error( $original_category ) ) {
            $itsme_original_category = $get_original_category;
        }

        $tax_query[] = array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => $original_category,
            'operator' => 'IN'
        );
        // Equivalent of original `$tax_query` END.

        $tax_query['relation'] = 'OR';

        $tax_query[] = array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => 'intl',
            'operator' => 'IN'
        );

        $query->set( 'tax_query', $tax_query );
    }
    return $query;
}
add_filter( 'pre_get_posts', 'itsme_better_editions' );

OR simply replace the query like so:

/*
 * Show posts assigned to 'International (intl)' Edition
 * in all editions.
 */
function itsme_better_editions( $query ) {
    if( $query->is_category() && $query->is_main_query() ) {

        // Get original/actual category of the category archive
        $get_original_category = get_query_var( 'category_name' );
        $original_category = get_term_by( 'slug', $get_original_category, 'category' );
        if( $original_category && !is_wp_error( $original_category ) ) {
            $itsme_original_category = $get_original_category;
        }

        if( isset($itsme_original_category) ) {

            $query->set( 'post_type', array( 'post' ) );

            $query->set( "category_name", "{$itsme_original_category}, intl" );

        }

    }
}
add_action( 'pre_get_posts', 'itsme_better_editions' );

This works, but why should I do it this way? Isn’t $tax_query = $query->get( 'tax_query' ); supposed to return the original tax_query?


How to reproduce the problem

1. Create two categories: UK (uk) and International (intl). Create 2 posts and assign them to ‘UK’ only; 1 under ‘International’ only.

Now, example.com/category/uk/ shows 2 posts; and example.com/category/intl/ shows 1.

2. Now add the first function (first code block) in your theme’s functions.php, and visit example.com/category/uk/. You’ll see that the category name of the page (<?php single_cat_title(); ?>) is shown as ‘International’. Why? If I am not wrong, because $query->get( ‘tax_query’ ); seems to return empty, not an array.

3. Replace the function in functions.php with the second or third functions above. Now everything should work as it’s supposed to.

PS: Yes, I did this test myself with the default theme (Twenty Thirteen) and it persists. So you’d be able to reproduce the problem just fine.

Related posts

2 comments

  1. AFAIK $query->get for main query works only with public query vars, i.e. vars that can be triggered via url, but nothing prevents to directly access directly to tax_query property of query, but notice that it is an object, instance of WP_Tax_Query and the current queried taxonomy arguments are in the queries property of that object.

    Accessing to that property you avoid to run another query with get_term_by inside your function. As a side effect, single_cat_title will print the correct title:

    function itsme_better_editions( $query ) {
    
      if ( $query->is_category() && $query->is_main_query() ) {
    
        $query->set( 'post_type', array( 'post' ) );
    
        $tax_query_obj = clone $query->tax_query;
    
        $tax_query_obj->queries[] = array(
          'taxonomy' => 'category',
          'field' => 'slug',
          'terms' => 'intl',
          'operator' => 'IN'
        );
    
        $tax_query = array('relation' => 'OR');
    
        foreach ( $tax_query_obj->queries as $q ) {
          $tax_query[] = $q;
        }
    
        $query->set('tax_query', $tax_query);
      }
    
    }
    
    add_action( 'pre_get_posts', 'itsme_better_editions' );
    

    Note that actually you are running the filter also on admin queries, if is not what you want add && ! is_admin() inside first if conditional in function.

    PS: a tip: when using 'pre_get_posts' you can use add_action instead of add_filter and not return anything, because the query is passed as reference.

  2. $tax_query = $query->get( 'tax_query' ); will return the original tax_query if one is passed into the query. For example:

    function itsme_better_editions( $query ) {
      $query->set( 'post_type', array( 'post' ) );
    
      // Get current tax query
      $tax_query = $query->get( 'tax_query' );
      var_dump($tax_query); die;
    }
    add_action( 'pre_get_posts', 'itsme_better_editions' );
    
    $t = new WP_Query(
      array(
        'post_type' => 'post',
        'tax_query' => array(
          array(
            'taxonomy' => 'category',
            'field' => 'slug',
            'terms' => 'intl',
            'operator' => 'IN'
          )
        )
      )
    );
    

    However, if you dump $wp_query on a category archive, or run a similar test on the main query, there is no tax_query as you’ve already discovered. That is because for some of the default queries, the WP_Query object, constructs that tax_query from other query parameters long after pre_get_posts runs. That is why $tax_query = $query->get( 'tax_query' ); is empty.

Comments are closed.