The Events Calendar logoWith the The Events Calendar Pro add-on installed (together with the free The Events Calendar plugin), you have access to recurring events, which may be the main reason to buy and install it.

However, TEC Pro uses a custom database table to store these recurring events. Also, it hijacks all queries that use the tribe_events post type, in order to use this custom table. Because posts in custom tables are not indexed, TEC Pro is fundamentally incompatible with FacetWP on pages with a query that retrieve only tribe_events posts. With TEC Pro installed, facets on these pages will not populate and function correctly.

Below we will describe the issues in detail, and describe a workaround to keep using TEC Pro with recurring events and FacetWP.

Recurring events in The Events Calendar Pro

Before TEC Pro version 6.0.0, the post_parent field was used to relate a recurring event with its child “occurrences”. A recurring event was a post representing the event’s first occurrence, and the subsequent occurrences were posts related to the first by their post_parent field.

In TEC Pro version 6.0.0, TEC introduced a new implementation called “CT1”, which stands for Custom Tables v1. In this new structure, events and their child occurrences are stored in a custom tec_occurrences database table. In this table, the main events and all child occurrences each have an individual occurrence_id, also called a “provisional post ID”. This way they can be treated as “normal” posts by TEC functions. Note that the main event is also still stored in the wp_posts table.

For a full explanation and all terminology in CT1, see this TEC documentation page.

Recurring events and FacetWP

FacetWP in general is incompatible with posts in custom tables: it only indexes posts in WP’s wp_posts table, so event occurrences are not indexed. Which leaves only the main/parent events in wp_posts to be indexed.

Adding to this problem is that TEC Pro hijacks every query that has its post_type argument set to tribe_events, and will insert occurrence IDs into it. It also adds 10000000 to the post ID count, presumably to prevent collisions with normal post IDs. You can see this happening if you type FWP.settings.debug.sql in your browser Console, on a page with a tribe_events query and facets:

The Events Calendar Pro hijacking the query, as seen in Debug Mode with FWP.settings.debug.sql.
The Events Calendar Pro hijacking the query, as seen in Debug Mode with FWP.settings.debug.sql.

Because occurrences are not indexed, this query injection will result in empty facets, without any options, and non-functional filtering.

The good news is that this query hijack only happens if the post_type argument is set to only tribe_events. When you add a second post type, the query hijack will not happen and the main events will be retrieved normally from the wp_posts table. This presents an opportunity for a workaround to get TEC Pro working with FacetWP:

A workaround to retrieve and filter recurring events with FacetWP

Prevent the TEC query hijack

The above-mentioned query hijacking will only happen if the post_type argument is set to only tribe_events. To prevent this from happening, you can add a second post type to the argument. This could be any post type, and it does not even have to be an existing post type. In a custom WP_Query, the following will work:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => [ 'tribe_events', 'x' // Add a non-existing post_type ], 'post_status' => [ 'publish' ], 'posts_per_page' => 10, 'orderby' => 'meta_value', 'order' => 'ASC', 'meta_key' => '_EventStartDate', 'meta_value' => date( 'Y-m-d H:i:s' ), // now 'meta_compare' => '>', 'facetwp' => true // needed in a custom WP_Query, not in Listing Builder listings (in Dev mode) ]; $events_query = new WP_Query( $args );

With the above query arguments in place, only main (parent) events are retrieved. There will be no recurring events (“occurrences”) in your listing anymore.

If you don’t use recurring events, adding a second (fake) post type is enough to get facets working again with TEC Pro installed. You can adjust all other query arguments to what you need.

In the above query example we filter for events with a start date in the future. However, if you do want to use recurring events (with the workaround described below), don’t do this. We need to retrieve all events, even if they are in the past, because there could be child occurrences with a start date still in the future:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

$args = [ 'post_type' => [ 'tribe_events', 'x' // Add a non-existing post_type ], 'post_status' => [ 'publish' ], 'posts_per_page' => 10, 'orderby' => 'meta_value', 'order' => 'ASC', 'meta_key' => '_EventStartDate', 'facetwp' => true // needed in a custom WP_Query, not in Listing Builder listings (in Dev mode) ]; $events_query = new WP_Query( $args );

Add occurrences back into the listing

If you want to add occurrences back into the listing, we can now get all child occurrences for each parent event in the loop:

How to use custom PHP code?

PHP code can be added to your (child) theme's functions.php file. Alternatively, you can use the Custom Hooks add-on, or a code snippets plugin. More info

if ( $events_query->have_posts() ) : while ( $events_query->have_posts() ) : $events_query->the_post(); // Get all occurrence IDs for a "parent" event. // This includes the parent event itself, which is also an occurrence. $occurrence_ids = tribe_events()->where( 'ID', get_the_ID() )->pluck( 'ID' ); foreach ( $occurrence_ids as $occurrence_id ) { $start_date = get_post_meta( $occurrence_id, '_EventStartDate', true ); $start_date_formatted = tribe_get_start_date( $occurrence_id, false, 'F j, Y - H:i' ); $end_date_formatted = tribe_get_end_date( $occurrence_id, false,'F j, Y - H:i' ); $title = get_post( $occurrence_id )->post_title; $permalink = get_the_permalink( $occurrence_id ); // Only show occurrence if its start date is after "now": if ( $start_date > date( 'Y-m-d H:i:s' ) ) { echo '<h2><a href="' . $permalink . '">' . $title . '</a></h2>'; echo '<p>From: ' . $start_date_formatted . ' to '. $end_date_formatted . '</p>'; } } endwhile; else : _e( 'Sorry, no posts matched your criteria.' ); endif; wp_reset_postdata();

In this loop, parent events and all their child occurrences are retrieved, as long as their start dates are in the future (after now). The parent events themselves will also be retrieved because TEC Pro treats them as an individual occurrence.

Notice that for the retrieval of the start- or end dates of the occurrences, you need to pass the occurrence_id, not the post_id. For the permalink, post title, thumbnail, or other features of the main event, you can use either the occurrence_id or the main event’s post ID (retrieved with get_the_ID()).

You could use the above loop template as a starting point to build several different layouts. Implemented as above, it displays all occurrences as individual events, each with its own heading, mimicking how TEC Pro listings would work without FacetWP. Another idea could be to show a list of occurrences (as items, dates, or links) under their parent event’s heading/description.

See The Events Calendar’s developer documentation for more functions to use with (recurring) events.

Limitations

A few limitations and things to keep in mind if you implement the above workaround:

  • Facets, including Sort facets, will filter (or order) the parent events, not the individual child occurrences.
  • If the query is ordering by _EventStartDate, be aware that this is the parent event’s start date, even if that parent event/occurence is not showing anymore because its start date is now in the past.
  • Occurrences will show up grouped by parent event.

See also