Hi Patrick,
Sorry I’ve been very busy lately and could not find the time to answer.
You can’t query the database over every date of the repeating event because the dates are never actually stored in the database.
The solution I found was to set the first and last dates of the recurrence automatically when a post is saved so I can filter the posts later. Displaying all posts in an agenda requires some kind of pagination as you will never show the complete list of dates all at once. That could be hundreds or thousands of dates. What you will do is displaying all the events between two dates, for instance between today and next month.
The first step is to query all events having the first occurrence before today and the last one after the end date (a month after in our example)
Then you will have to loop through these events to get each individual date and create a new array that will contain every single occurrence sorted by dates.
Here is a simplified part of the code I wrote for my use case. I wrote some comments in it for you. I hope it will be clear enough to help you ??
Basically there are two functions:
- The first one returns a sorted array of all occurrences between two dates
- The second one is used to retrieve all dates for a specified event
<?php
/**
* Get all events between two dates.
*
* @param mixed $start
* @param mixed $end
* @return array
*/
function fbdm_get_events($start = null, $end = null, $data = [])
{
$events = [];
$has_more = false;
$index = 0;
if (! $start) {
// Defaults to today
$start = new DateTime();
} elseif (! $start instanceof DateTime) {
$start = DateTime::createFromFormat('Y-m-d', $start);
}
if (! $end) {
// Defaults to start date + 1 month
$end = clone $start;
$end->add(new DateInterval('P1M'));
} elseif (! $end instanceof DateTime) {
$end = DateTime::createFromFormat('Y-m-d', $end);
}
$start->setTime(0,0,0);
$end->setTime(0,0,0);
// Break the loop after 14 events
while (sizeof($events) <= 14) {
if ($index > 0) {
$start = clone $end;
$start->add(new DateInterval('P1D'));
$end = clone $start;
$end->add(new DateInterval('P1M'));
}
// Query all events which have not ended at start date
$args = [
'post_type' => 'event',
'posts_per_page' => -1,
'post_status' => 'publish',
'meta_query' => [
'relation' => 'AND',
[
'key' => 'end_date',
'compare' => '>=',
'value' => $start->format('Y-m-d'),
'type' => 'DATE',
],
],
];
if (isset($data['category'])) {
$args['tax_query'] = [
'relation' => 'AND',
[
'taxonomy' => 'event_category',
'terms' => $data['category'],
],
];
}
if (isset($data['free']) && $data['free'] == true) {
$args['meta_query'][] = [
'relation' => 'OR',
[
'key' => 'pricing',
'value' => 'free',
],
];
}
// Here you can add more filters if needed
$query = new WP_Query($args);
// Count posts from start date
if (! count($query->get_posts())) {
$has_more = false;
break;
}
// Add end date to query arguments
$args['meta_query'][] = [
'key' => 'start_date',
'compare' => '<=',
'value' => $end->format('Y-m-d'),
'type' => 'DATE',
];
$query = new WP_Query($args);
foreach ($query->get_posts() as $post) {
$post->dates = fbdm_get_event_dates($post->ID);
foreach ($post->dates as $date => $array) {
$datetime = new DateTime($date);
if ($datetime < $start) {
continue;
} elseif ($end && $datetime > $end) {
$has_more = true;
break;
}
// Push the event to the array of dates
if (! array_key_exists($date, $events)) {
$timestamp = $datetime->getTimestamp() + $datetime->getOffset();
$day = date_i18n("l j F", $timestamp);
$events[$date] = [
'day' => $day,
'month' => date_i18n("F Y", $timestamp),
'events' => [],
];
}
foreach ($array as $time) {
$time_full = $time['start_time'];
if ($time['end_time']) {
$time_full .= "-{$time['end_time']}";
}
// Update an existing row
if (array_key_exists($post->ID, $events[$date]['events'])) {
$events[$date]['events'][$post->ID]['time'][] = $time_full;
sort($events[$date]['events'][$post->ID]['time']);
}
// Create a new row
else {
$events[$date]['events'][$post->ID] = [
'post' => $post,
'start_time' => $time['start_time'],
'end_time' => $time['end_time'],
'time' => [$time_full],
];
}
}
// Sort events by start time
usort($events[$date]['events'], function($a, $b) {
return $a['start_time'] <=> $b['start_time'];
});
}
}
if ($index++ > 3) {
break;
}
}
// Sort events array by date (key)
ksort($events);
return [
'events' => $events,
'has_more' => $has_more
];
}
/**
* Get all dates for an event.
*
* @param int $post_id
* @return array
*/
function fbdm_get_event_dates( $post_id )
{
if (! $post_id || get_post_type($post_id) !== 'event') {
return;
}
$dates = [];
$recurrence = get_field('recurrence');
foreach ($recurrence['dates_collection'] as $date) {
if (! array_key_exists($date->format('Y-m-d'), $dates)) {
$dates[$date->format('Y-m-d')] = [];
}
// I created two ACF time fields along with the RRule field
// to set the start and end times of each occurrence.
// If you don't need to specify the times for your occurrences
// you can just push the date to the array instead of using key => value
$dates[$date->format('Y-m-d')][] = [
'start_time' => get_field('recurrence_start_time'),
'end_time' => get_field('recurrence_end_time'),
];
}
// Sort dates
ksort($dates);
return $dates;
}