Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions inc/Blocks/Calendar/Query/DateFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php
/**
* Date Filter — centralized "upcoming" vs "past" event definition.
*
* Single source of truth for the condition that determines whether an
* event is upcoming or past. Exposes the logic in two formats:
*
* - meta_query(): WP_Query meta_query arrays (for EventQueryBuilder)
* - sql(): Raw SQL joins + WHERE fragments (for Taxonomy_Helper,
* PageBoundary, calendar-stats, and any other raw query)
*
* Definition:
* upcoming = start >= $datetime OR end >= $datetime
* past = start < $datetime AND (end < $datetime OR end IS NULL)
*
* @package DataMachineEvents\Blocks\Calendar\Query
* @since 0.19.0
*/

namespace DataMachineEvents\Blocks\Calendar\Query;

use const DataMachineEvents\Core\EVENT_DATETIME_META_KEY;
use const DataMachineEvents\Core\EVENT_END_DATETIME_META_KEY;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

class DateFilter {

/**
* WP_Query meta_query for upcoming events.
*
* @param string $datetime MySQL datetime to compare against.
* @return array meta_query clause.
*/
public static function upcoming_meta_query( string $datetime ): array {
return array(
'relation' => 'OR',
array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
);
}

/**
* WP_Query meta_query for past events.
*
* @param string $datetime MySQL datetime to compare against.
* @return array meta_query clauses (two entries for AND nesting).
*/
public static function past_meta_query( string $datetime ): array {
return array(
'relation' => 'AND',
array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $datetime,
'compare' => '<',
'type' => 'DATETIME',
),
array(
'relation' => 'OR',
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $datetime,
'compare' => '<',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'compare' => 'NOT EXISTS',
),
),
);
}

/**
* Raw SQL fragments for upcoming events.
*
* Returns JOIN and WHERE clauses. The caller must supply `$wpdb`
* table references and call `$wpdb->prepare()` on the WHERE.
*
* The returned WHERE uses `%s` placeholders — pass `$datetime` twice
* as the corresponding prepare() values.
*
* @param string $postmeta_table Full postmeta table name (e.g. $wpdb->postmeta).
* @return array{joins: string, where: string, param_count: int}
*/
public static function upcoming_sql( string $postmeta_table ): array {
$start_key = esc_sql( EVENT_DATETIME_META_KEY );
$end_key = esc_sql( EVENT_END_DATETIME_META_KEY );

return array(
'joins' => "INNER JOIN {$postmeta_table} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '{$start_key}'"
. " LEFT JOIN {$postmeta_table} pm_end ON p.ID = pm_end.post_id AND pm_end.meta_key = '{$end_key}'",
'where' => '(pm_start.meta_value >= %s OR pm_end.meta_value >= %s)',
'param_count' => 2,
);
}

/**
* Raw SQL fragments for past events.
*
* @param string $postmeta_table Full postmeta table name.
* @return array{joins: string, where: string, param_count: int}
*/
public static function past_sql( string $postmeta_table ): array {
$start_key = esc_sql( EVENT_DATETIME_META_KEY );
$end_key = esc_sql( EVENT_END_DATETIME_META_KEY );

return array(
'joins' => "INNER JOIN {$postmeta_table} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '{$start_key}'"
. " LEFT JOIN {$postmeta_table} pm_end ON p.ID = pm_end.post_id AND pm_end.meta_key = '{$end_key}'",
'where' => '(pm_start.meta_value < %s AND (pm_end.meta_value < %s OR pm_end.meta_value IS NULL))',
'param_count' => 2,
);
}

/**
* Raw SQL fragments for a date range filter.
*
* Filters events whose start_datetime falls within the range.
* Also includes the standard start/end JOIN for consistency.
*
* @param string $postmeta_table Full postmeta table name.
* @return array{joins: string, where: string, param_count: int}
*/
public static function date_range_sql( string $postmeta_table ): array {
$start_key = esc_sql( EVENT_DATETIME_META_KEY );
$end_key = esc_sql( EVENT_END_DATETIME_META_KEY );

return array(
'joins' => "INNER JOIN {$postmeta_table} pm_start ON p.ID = pm_start.post_id AND pm_start.meta_key = '{$start_key}'"
. " LEFT JOIN {$postmeta_table} pm_end ON p.ID = pm_end.post_id AND pm_end.meta_key = '{$end_key}'",
'where' => '(pm_start.meta_value >= %s AND pm_start.meta_value <= %s)',
'param_count' => 2,
);
}
}
81 changes: 4 additions & 77 deletions inc/Blocks/Calendar/Query/EventQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,46 +82,10 @@ public static function build_query_args( array $params ): array {
$current_datetime = current_time( 'mysql' );
$has_date_range = ! empty( $params['date_start'] ) || ! empty( $params['date_end'] );

// Use start_datetime as primary with end_datetime OR fallback.
// Most events don't have end_datetime meta (no real end time provided).
// An event is "upcoming" if start >= now OR end >= now (still ongoing).
// An event is "past" if start < now AND (end < now OR no end meta).
if ( $params['show_past'] && ! $params['user_date_range'] ) {
$meta_query[] = array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '<',
'type' => 'DATETIME',
);
$meta_query[] = array(
'relation' => 'OR',
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '<',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'compare' => 'NOT EXISTS',
),
);
$meta_query[] = DateFilter::past_meta_query( $current_datetime );
} elseif ( ! $params['show_past'] && ! $params['user_date_range'] ) {
$meta_query[] = array(
'relation' => 'OR',
array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
);
$meta_query[] = DateFilter::upcoming_meta_query( $current_datetime );
}

if ( ! empty( $params['date_start'] ) ) {
Expand Down Expand Up @@ -254,60 +218,23 @@ public static function get_event_counts(): array {
private static function compute_event_counts(): array {
$current_datetime = current_time( 'mysql' );

// Upcoming: start >= now OR end >= now (still ongoing).
$future_query = new WP_Query(
array(
'post_type' => Event_Post_Type::POST_TYPE,
'post_status' => 'publish',
'fields' => 'ids',
'posts_per_page' => 1,
'meta_query' => array(
'relation' => 'OR',
array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '>=',
'type' => 'DATETIME',
),
),
'meta_query' => DateFilter::upcoming_meta_query( $current_datetime ),
)
);

// Past: start < now AND (end < now OR no end meta).
$past_query = new WP_Query(
array(
'post_type' => Event_Post_Type::POST_TYPE,
'post_status' => 'publish',
'fields' => 'ids',
'posts_per_page' => 1,
'meta_query' => array(
'relation' => 'AND',
array(
'key' => EVENT_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '<',
'type' => 'DATETIME',
),
array(
'relation' => 'OR',
array(
'key' => EVENT_END_DATETIME_META_KEY,
'value' => $current_datetime,
'compare' => '<',
'type' => 'DATETIME',
),
array(
'key' => EVENT_END_DATETIME_META_KEY,
'compare' => 'NOT EXISTS',
),
),
),
'meta_query' => DateFilter::past_meta_query( $current_datetime ),
)
);

Expand Down
23 changes: 10 additions & 13 deletions inc/Blocks/Calendar/Taxonomy_Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
namespace DataMachineEvents\Blocks\Calendar;

use DataMachineEvents\Core\Event_Post_Type;
use const DataMachineEvents\Core\EVENT_DATETIME_META_KEY;
use const DataMachineEvents\Core\EVENT_END_DATETIME_META_KEY;
use DataMachineEvents\Blocks\Calendar\Query\DateFilter;

if ( ! defined( 'ABSPATH' ) ) {
exit;
Expand Down Expand Up @@ -141,30 +140,28 @@ public static function get_batch_term_counts( $taxonomy_slug, $date_context = ar
$where_clauses = '';
$params = array( $taxonomy_slug, $post_type );

// Date context filtering — uses start_datetime as primary with end_datetime
// OR fallback. Most events don't have end_datetime meta (no real end time).
if ( ! empty( $date_context ) ) {
$joins .= " INNER JOIN {$wpdb->postmeta} pm_start_date ON p.ID = pm_start_date.post_id AND pm_start_date.meta_key = '" . esc_sql( EVENT_DATETIME_META_KEY ) . "'";
$joins .= " LEFT JOIN {$wpdb->postmeta} pm_end_date ON p.ID = pm_end_date.post_id AND pm_end_date.meta_key = '" . esc_sql( EVENT_END_DATETIME_META_KEY ) . "'";

$date_start = $date_context['date_start'] ?? '';
$date_end = $date_context['date_end'] ?? '';
$show_past = ! empty( $date_context['past'] ) && '1' === $date_context['past'];
$current_datetime = current_time( 'mysql' );

if ( ! empty( $date_start ) && ! empty( $date_end ) ) {
// Explicit date range from date picker.
$where_clauses .= ' AND pm_start_date.meta_value >= %s AND pm_start_date.meta_value <= %s';
$filter = DateFilter::date_range_sql( $wpdb->postmeta );
$joins .= ' ' . $filter['joins'];
$where_clauses .= ' AND ' . $filter['where'];
$params[] = $date_start . ' 00:00:00';
$params[] = $date_end . ' 23:59:59';
} elseif ( $show_past ) {
// Past: start < now AND (end < now OR no end meta).
$where_clauses .= ' AND pm_start_date.meta_value < %s AND (pm_end_date.meta_value < %s OR pm_end_date.meta_value IS NULL)';
$filter = DateFilter::past_sql( $wpdb->postmeta );
$joins .= ' ' . $filter['joins'];
$where_clauses .= ' AND ' . $filter['where'];
$params[] = $current_datetime;
$params[] = $current_datetime;
} else {
// Upcoming: start >= now OR end >= now (still ongoing).
$where_clauses .= ' AND (pm_start_date.meta_value >= %s OR pm_end_date.meta_value >= %s)';
$filter = DateFilter::upcoming_sql( $wpdb->postmeta );
$joins .= ' ' . $filter['joins'];
$where_clauses .= ' AND ' . $filter['where'];
$params[] = $current_datetime;
$params[] = $current_datetime;
}
Expand Down
Loading