Your current meta query embodies the logic
IF event_end IN range
OR event_start IN range
You need to account for the conditions of event_end
's presence separately:
IF event_end IN range
OR ( event_end NOT EXISTS AND event_start IN range )
Immediately adjacent nested clauses with the same AND/OR relation
can be combined, which might make things a bit more readable.
a AND ( b AND ( c OR d ) ) === a AND b AND ( c OR d )
a OR ( b OR ( c AND d ) ) === a OR b OR ( c AND d )
You might also be able to name your clauses in order to order the results by which was used - but I haven't tested this.
All of the above in practice:
$past_args = array(
'post_type' => 'event',
'meta_query' => array(
'relation' => 'AND',
array(
'relation' => 'OR',
// check to see if end date has been set and is in range
'end_clause' => array(
'key' => 'end_date',
'compare' => 'BETWEEN',
'type' => 'NUMERIC',
'value' => array($past, $now),
),
// if no end date has been set use event/start date
array(
'relation' => 'AND',
array(
'key' => 'end_date',
'compare' => 'NOT EXISTS',
),
'start_clause' => array(
'key' => 'event_date',
'compare' => 'BETWEEN',
'type' => 'NUMERIC',
'value' => array($past, $now),
),
),
),
array(
'key' => 'event_type',
'value' => array('Singlar', 'Multi-Day', 'Series'),
),
array(
'key' => 'include_in_past_events',
'value' => '1',
)
),
// 'meta_key' => 'event_date',// not needed when ordering by clause name.
'orderby' => 'end_clause start_clause',
'order' => 'DESC',
'nopaging' => false,
'posts_per_page'=> '8',
);
You might also explore naming your clauses to use in the orderby