diff --git a/inc/Blocks/Calendar/Query/DateFilter.php b/inc/Blocks/Calendar/Query/DateFilter.php new file mode 100644 index 0000000..64a6518 --- /dev/null +++ b/inc/Blocks/Calendar/Query/DateFilter.php @@ -0,0 +1,148 @@ += $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, + ); + } +} diff --git a/inc/Blocks/Calendar/Query/EventQueryBuilder.php b/inc/Blocks/Calendar/Query/EventQueryBuilder.php index 351a7d4..8977ea5 100644 --- a/inc/Blocks/Calendar/Query/EventQueryBuilder.php +++ b/inc/Blocks/Calendar/Query/EventQueryBuilder.php @@ -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'] ) ) { @@ -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 ), ) ); diff --git a/inc/Blocks/Calendar/Taxonomy_Helper.php b/inc/Blocks/Calendar/Taxonomy_Helper.php index bee83db..5b9b110 100644 --- a/inc/Blocks/Calendar/Taxonomy_Helper.php +++ b/inc/Blocks/Calendar/Taxonomy_Helper.php @@ -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; @@ -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; }