Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

WordPress 3.7 introduced 'date_query' argument for WP_Query.

That's pretty useful and allow very complex date-based queries, however something strange happen when using WP_Query conditional methods on query using that argument.

Example:

$args = array(

  'tax_query'=> array(
    'relation' => 'AND',
    array(
      'taxonomy' => 'category',
      'field'    => 'slug',
      'terms'    => 'uncategorized'
    ),
    array(
      'taxonomy' => 'post_tag',
      'field'    => 'slug',
      'terms'    => array('foo', 'bar')
    )
  ),

  'date_query' => array(
     array(
       'year'  => $today["year"],
       'month' => $today["mon"],
       'day'   => $today["mday"],
     )
  )

);

$query = new WP_Query( $args );

The query should returns posts posted today and having 'uncategorized' category or 'foo' and 'bar' tags. And it does, so far so good.

Now consider the following conditional methods:

$query->is_archive();  // returns true, as guessable
$query->is_category(); // returns true, as guessable
$query->is_tag();      // returns true, as guessable

but

$query->is_date();  // returns false
$query->is_day();   // returns false
$query->is_month(); // returns false
$query->is_year();  // returns false

I thought that the problem can be caused by the fact I mixed 'tax_query' and 'date_query', but that's not the problem, proof:

$args = array(
  'date_query' => array(
     array(
       'year'  => $today["year"],
       'month' => $today["mon"],
       'day'   => $today["mday"],
     )
  )
);

$query = new WP_Query( $args );

$query->is_date();    // returns false
$query->is_day();     // returns false
$query->is_month();   // returns false
$query->is_year();    // returns false
$query->is_archive(); // returns false this time

Is this a bug, or I'm missing something? (I've searched WP Trac, but found nothing related).

share|improve this question

3 Answers 3

The values retrieved by the is_date(), is_day(), etc. methods are set inside the parse_query method and they really seem to be intended for the main query, whether that is explicitly stated (intended) or not. The code parses the public query variables to determine those settings.

My interpretation is that these values aren't really meant for secondary queries, and really don't make much sense for secondary queries either. You've just run the query, you know that it is a date/tax/whatever query. That part doesn't need to be determined for you by parsing the query variables.

To did a bit deeper, I'd say the problem stems from WP_Query being a bit schizophrenic. It parsed the request to determine which page ought to load, rather than just being a tool to query the database for posts. It does two jobs, rather than one, which is poor design in my opinion. I think distinct code should parse the request and then pass arguments to WP_Query. Maybe that is just me.

Is it a bug? I'd call it that, at least "weird design". If WP_Query is going to do both jobs, it should at least set the class variables consistently, or split the class into two each with a single purpose. Of course, I don't get to canonically declare something a "bug" or not :)

share|improve this answer
1  
>*"WP_Query being a bit schizophrenic [...] It does two jobs"* To be honest I think that WP_Query does much more that 2 jobs. For example IMHO generation of sql is a seaparate job, that is done in WP in the same function that gets the post (get_posts) and personally when I look at source code of that function I feel like seasick. > "Is it a bug? I'd call it that, at least "weird design" I'm agree, but I'm afraid the the whole WP core has a "weird design" ;) –  G. M. Mar 24 at 0:22
1  
I guess there are really two points. 1) These were/are not meant for secondary queries. Where they do work for secondary queries is, I think, an accident. And 2) the need for those don't make sense to me. –  s_ha_dum Mar 24 at 0:24

Note: This is just an addition to @s_ha_dum answer (which by the upvotes probably be north of this one).

All the "Conditional Tags"/Query methods that determine state, for e.g.

  • is_date
  • is_day
  • is_hour
  • is_nanosecond

…are all old, so people tend to forget about their existance. And let's face it: They never have been very popular. How many hourly archives have you encountered in your digital journeys across the interwebs?

But what is interesting and often needed are date ranges, which can be interpreted as completely unrelated to date ARCHIVES - and that is what the is_ conditionals are. So I could imagine, that those who wrote the date_query parts saw exactly no connection between date or time based archives and the according queries.

Imagine the following situation:

WTF! Why the heck does my hourly archive template kick in, when all I wanted was posts, paginated by week on my front page?

Even if a lot of core often seems to be "design by accident", this one actually isn't. Take a look at the tax_query parts of WP_Query::get_posts():

$this->parse_tax_query( $qv );

foreach ( $this->tax_query->queries as $tax_query ) {
    if ( 'NOT IN' != $tax_query['operator'] ) {
        switch ( $tax_query['taxonomy'] ) {
            case 'category':
                $this->is_category = true;
                break;
            case 'post_tag':
                $this->is_tag = true;
                break;
            default:
                $this->is_tax = true;
        }
    }
}
unset( $tax_query );

Let me leave you with two questions:

  1. What does above snippet imply?
  2. Do you really want that?
share|improve this answer
    
IMO it's not consistent. Conditional tags always determine a template state. is_tax() asks "Is the query for an existing taxonomy archive page?". And if I let WP perform a tax_query as secondary query for a related posts widget that is mounted on a sidebar in a single.php template, then it simply is wrong. Conditional tags shouldn't be true for secondary queries. Or they shouldn't refer to WP_Query properties. It's mixed and messed up. –  kaiser Mar 24 at 1:28
    
I completely agree with you. The only difference is that I think that date_query is not as messed up as tax_query. –  kaiser Mar 24 at 1:49
    
ah, just a note, does not exists any hourly archive template in WordPress. Nor any other time-based archive template. The only date/time related template is the generic date.php ;) –  G. M. Mar 24 at 6:57
up vote 1 down vote accepted

I'll try to make some order, and focus all my thoughts in a answer instead of commenting the 2 good answers by @s_ha_dum and @kaiser (I upvoted both).

First we need to specify that in WordPress there are 2 types of queries, main and secondaries.

Both are instances of same class, WP_Query but first difference is that main query object is instanciated by WordPress and saved in the global $wp_query variable, all the secondary queries must be instantiated by custom code.

But there is another big difference: to get posts, WP_Query need to receive some arguments: in main query those arguments are taken from url in secondary queries arguments must explicitly passed to constructor or to get_posts method.

A direct consequence of previous is that, for main query, the query arguments -taken from url- can be only scalar variables: strings, integers and booleans: query arguments whose values are arrays can't be set via urls, infact, any non scalar variable is strip out from query arguments set via urls.

So we have also 2 types of query arguments, "scalar arguments" and "array arguments".

WordPress uses scalar arguments to setup template tags, infact WordPress workflow is:

  1. Setup query arguments using url arguments
  2. Setup conditional template properties (all the is_*) in global $wp_query object
  3. Get the posts based on query arguments
  4. Using those conditional template properties load a specific template

Worth noting that even if using url is not possible set non-scalar arguments, is possible to use 'pre_get_posts' action hook to make the main query use non-scalar variables like tax_query, meta_query, date_query and so on.

But 'pre_get_posts' runs between #3 ad #4 in workflow described above, so even it make possible to use array-based query arguments, the conditional template properties are already setup, so simply using a tax_query or any other non-scalar query argument inside pre_get_posts don't affect the template conditional tags (and so the template loaded) unless one explicitly set is_* properties:

add_action( 'pre_get_posts', function( $query ) {

  if ( $query->is_main_query() && ! is_admin() ) {

    $tax_query = array ( array(
      'taxonomy' => 'category', 'field' => 'slug', 'terms' => 'foo')
    );

    // this will affect query, but will NOT affect the template loaded
    $query->query_vars['tax_query'] = $tax_query; 
    // this will affect the template loaded, instead
    $query->is_category = TRUE; 
  }

} );

That said, seems logic and legit that non-scalar query arguments like 'date_query' are not used to set template tags, so the question becomes another: why WordPress parse tax_query arguments even if they can't affect templates?

Even if this question should be asked to core devs, I have an idea.

Notice: next 2 code snippets are bad, here just for proof of concept: don't do at home.

add_action( 'template_redirect', function() {

  $args = array( 'tax_query' => array(
    array( 'taxonomy' => 'category', 'terms' => array( 16, 17 )  )
  ) );

  $GLOBALS['wp_query'] = new WP_Query( $args );

} );

In previous code I replaced the global wp_query with another query that makes use of a tax query. It can appear strange, but it works as expected, and the template loaded is category.php because WordPress set template tags for 'tax_query' (in facts and $GLOBALS['wp_query']->is_category() will be true).

If I do same thing using a 'date_query':

add_action( 'template_redirect', function() {

  $args = array( 'date_query' => array(
    array( 'month' => 3, 'year' => 2014  )
  ) );

  $GLOBALS['wp_query'] = new WP_Query( $args );

} );

posts will be retrieved as expected (from March 2014), but template loaded will be not date.php and $GLOBALS['wp_query']->is_date() will be false.

So my guess - and is only a guess- is that in older code, core devs thought about the possibility to override the global $wp_query object with one using non-scalar arguments and so they concerned to make template hierarchy working in that case. But in more modern code (and 'date_query' appear only on WP 3.7) core devs don't worried about that because they probably thought there is no reason to be worried about bad practises...

Essentially, for how core works, isn't strange that date_query doesn't set template conditional properties, but is strange that tax_query does.

Just two additional notes.

Looking at template hierarchy, some conditional template tags has no effect on it, e.g. is_time and much others have no effect on template choosing.

So, which is the usefulness of conditional template tags that doesn't affect template choosing?

If we answer: "none", than we have to admit that they are there just for some historical or misterious reason, otherwise if a conditional tag is useful even if doesn't affect template choosing, then we have to admit that will be useful if WP would set conditional properties for all the non-scalar argumets and not only for 'tax_query'.

Moreover, sometimes in my code I use to create my own class that extends WP_Query (and I'm not the only one). Even if most cases the usage of custom WP_Query classes is for secondary queries, is possible that one want to use it also for the main query (far from impossible because is just matter of overwrite a global variable).

Sure is a very bad practice overwrite $wp_query after it already queried posts, but if one uses an early hook, like 'setup_theme', there, global $wp_query is just an empty object and can be override without any issue. But customizing main WP_Query object, one can make possible to use non-scalar arguments for main query and allow to set them before the conditional template properties are set. In that case wouldn't be useful that WP_Query would parse all the non-scalar arguments and properly set conditional template properties accordingly?

In my opinion, yes, it would be useful, but untill the core will be strongly based on core WP_Query class (and thats is closely forever) it probably makes no sense having that in core.


Bonus

If someone, for any reason, want to parse 'date_query' and set conditional template tags, then following code can be used:

add_action( 'parse_query', function ( \WP_Query $q ) {
  $cond = array(
    'hour' => 'is_time', 'minute' => 'is_time', 'second' => 'is_time',
    'year' => 'is_year', 'month' => 'is_month', 'day' => 'is_day',
    'dayofweek' => 'is_day',
    'week' => 'is_date', 'before' => 'is_date', 'after' => 'is_date'
  );
  $date_query = isset( $q->query_vars['date_query'] )
    ? $q->query_vars['date_query']
    : FALSE;
  if ( empty( $date_query ) ) return;
  $found = 0;
  foreach ( $date_query as $i => $query ) {
    if ( ! is_array( $query ) ) continue;
    foreach ( $query as $key => $val) {
      if ( is_numeric($key) || ! isset( $cond[$key] ) ) continue;
      if ( in_array( $key, array( 'before', 'after' ) ) ) {
        $q->is_time = $q->is_year = $q->is_month = $q->is_day = FALSE;
        $q->is_date = $q->is_archive = TRUE;
        return;
      }
      $found++;
      $q->$cond[$key] = TRUE;
    }
  }
  if ( $found <= 0 ) return;
  // a query can't be is_time, is_year, is_month and is_day in same time
  $q->is_year = $q->is_year && ! $q->is_time && ! $q->is_month && ! $q->is_day;
  $q->is_month = $q->is_month && ! $q->is_time && ! $q->is_day;
  $q->is_day = $q->is_day && ! $q->is_time;
  $q->is_date = $q->is_archive = TRUE;
  $q->is_home = FALSE;
});
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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