Wordpress - How to handle optional end date in compare clause in meta_query
There is no need to craft a custom SQL query in order to achieve this. Since version 4.1, WordPress's query classes have supported complex/nested meta queries. So you can craft a query like this:
$args['meta_query'] = array(
// Use an OR relationship between the query in this array and the one in
// the next array. (AND is the default.)
'relation' => 'OR',
// If an end_date exists, check that it is upcoming.
array(
'key' => 'end_date',
'compare' => '>=',
'value' => date( 'Ymd', $current_date ),
),
// OR!
array(
// A nested set of conditions for when the above condition is false.
array(
// We use another, nested set of conditions, for if the end_date
// value is empty, OR if it is null/not set at all.
'relation' => 'OR',
array(
'key' => 'end_date',
'compare' => '=',
'value' => '',
),
array(
'key' => 'end_date',
'compare' => 'NOT EXISTS',
),
),
// AND, if the start date is upcoming.
array(
'key' => 'start_date',
'compare' => '>=',
'value' => date( 'Ymd', $current_date ),
),
),
);
I have tested this, and it works perfectly. My PHPUnit testcase:
/**
* Tests something.
*/
class My_Plugin_Test extends WP_UnitTestCase {
public function test_wpse() {
$current_time = current_time( 'timestamp' );
$current_date = date( 'Ymd', $current_time );
$yesterday_date = date( 'Ymd', strtotime( 'yesterday' ) );
$post_ids = $this->factory->post->create_many( 6 );
$post_with_end_past = $post_ids[0];
$post_with_end_now = $post_ids[1];
$post_empty_end_past = $post_ids[2];
$post_empty_end_now = $post_ids[3];
$post_null_end_past = $post_ids[4];
$post_null_end_now = $post_ids[5];
// This post has an end date in the past.
update_post_meta( $post_with_end_past, 'start_date', $yesterday_date );
update_post_meta( $post_with_end_past, 'end_date', $yesterday_date );
// This post has an end date in the present.
update_post_meta( $post_with_end_now, 'start_date', $yesterday_date );
update_post_meta( $post_with_end_now, 'end_date', $current_date );
// This post has no end date, but a start date in the past.
update_post_meta( $post_empty_end_past, 'start_date', $yesterday_date );
update_post_meta( $post_empty_end_past, 'end_date', '' );
// This post has an empty end date, but the start date is now.
update_post_meta( $post_empty_end_now, 'start_date', $current_date );
update_post_meta( $post_empty_end_now, 'end_date', '' );
// This post has no end date set at all, and the start date is past.
update_post_meta( $post_null_end_past, 'start_date', $yesterday_date );
// This post has no end date set at all, but the start date is now.
update_post_meta( $post_null_end_now, 'start_date', $current_date );
$args = array();
$args['fields'] = 'ids';
$args['meta_query'] = array(
// Use an OR relationship between the query in this array and the one in
// the next array. (AND is the default.)
'relation' => 'OR',
// If an end_date exists, check that it is upcoming.
array(
'key' => 'end_date',
'compare' => '>=',
'value' => $current_date,
),
// OR!
array(
// If an end_date does not exist.
array(
// We use another, nested set of conditions, for if the end_date
// value is empty, OR if it is null/not set at all.
'relation' => 'OR',
array(
'key' => 'end_date',
'compare' => '=',
'value' => '',
),
array(
'key' => 'end_date',
'compare' => 'NOT EXISTS',
),
),
// AND, if the start date is upcoming.
array(
'key' => 'start_date',
'compare' => '>=',
'value' => $current_date,
),
),
);
$post_query = new WP_Query();
$posts_list = $post_query->query( $args );
// Only the "now" posts should be returned.
$this->assertSame(
array( $post_with_end_now, $post_empty_end_now, $post_null_end_now )
, $posts_list
);
}
}