Probably the most “WordPress-y” approach to such a problem is to store the date of your 'event'
custom post-type in the post’s metadata. Enabling users to set meta-data on a post is usually accomplished by using the 'register_meta_box_cb'
argument to the register_post_type()
function to add an extra “meta box” interface to the pages for creating or editing a post on the dashboard. Alternately, you can enable custom fields support for the post-type, then check the box for “Custom Fields” in the screen options when creating or editing an 'event'
, after which you can manually enter the key ('event_date'
would be a good meta-key) and value in the “Custom Fields” section of the editor.
The actual meta-value itself should detail dates in YYYY-MM-DD
format in order to best make use of WordPress’s meta-query functionality.
In order to create permalinks to custom queries, you use a custom rewrite rule to populate a custom query var from the desired URL, then alter the main WP_Query
with a 'pre_get_posts'
hook to retrieve the content indicated with your query var. All together, it looks something like this:
// Allow WP to pass around an "event_year" variable as part of URL querystrings
function wpse_224309_add_event_year_query_var( $vars )
{
array_push( $vars, 'event_year' );
return $vars;
}
add_filter( 'query_vars', 'wpse_224309_add_event_date_query_var' );
// Populate the "event_year" query var when visiting "/seasons/{year}" URLs
function wpse_224309_event_archive_year_rewrite() {
add_rewrite_rule( 'seasons/([0-9]{4})/?', 'index.php?post_type=event&event_year=$matches[1]', 'top' );
}
add_action( 'init', 'wpse_224309_event_archive_year_rewrite' );
// If the "event_year" query var exists when in an event archive query, set up
// a meta-query to retrieve events with appropriate "event_date" metadata
function wpse_224309_event_archive_query_meta_date( $query ) {
// Don't modify queries that aren't for primary content
if( ! $query->is_main_query() )
return;
// Don't modify queries for anything but the 'events' post-type archive
if( ! is_post_type_archive( 'events' ) )
return;
$year = $query->get( 'event_year' ); // Get the target year from the qv
// If the "event_year" query var wasn't set, don't alter the query
if( ! isset( $year ) )
return;
$year = absint( $year ); // Make sure $year is a non-negative integer
// Look for events with an "event_date" meta-value between the end of the
// previous year and before the start of the next year.
$meta_query = array(
'key' => 'event_date',
'type' => 'DATE',
'compare' => 'BETWEEN'
'value' => array(
($year - 1) . '-12-31',
($year + 1) . '-01-01'
);
// Overwrite the meta-query to retrieve events with an "event_date" in the
// specified year, ordered by ascending date.
$query->set( 'meta_query', array( $meta_query ) );
$query->set( 'orderby', 'meta_value_date' );
$query->set( 'meta_key', 'event_date' );
$query->set( 'order', 'ASC' );
);
add_action( 'pre_get_posts', 'wpse_224309_event_archive_query_meta_date' );
If you need to filter events for other meta-data as well, however, you’ll need to compose a more detailed meta-query instead of completely overwriting it as I’ve done (see the Codex entries on WP_Query
and WP_Meta_Query
for details).
For the front-end, create an archive template for your “event” post type (likely archive-events.php
, in order to permit WordPress to load it automatically via template hierarchy conventions). You’ll need to create a list of links to your custom year archive manually. In order to do so, you’ll need to retrieve an array of unique years in for the 'event_date'
metadata. There are various ways to do this, but I believe that the most efficient is to directly query the database (WPDB->get_col()
example courtesy of WPSE 9394):
function wpse_224309_get_event_years( $order ) {
global $wpdb;
if( ! $order )
$order="DESC";
return $wpdb->get_col( $wpdb->prepare( "
SELECT DISTINCT YEAR( pm.meta_value ) FROM {$wpdb->postmeta} pm
LEFT JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = 'event_date'
AND p.post_status="publish"
AND p.post_type="events"
ORDER BY pm.meta_value %s
", $order ) );
}
function wpse_224309_event_year_archive_links( $args="" ) {
$defaults = array(
'format' => 'html',
'before' => '',
'after' => '',
'echo' => 1,
'order' => 'DESC'
);
$r = wp_parse_args( $args, $defaults );
$years = wpse_224309_get_event_years( $r['order'] );
if( ! is_array( $years ) || 0 === count( $years ) )
$return;
$output="";
for( $i = 0; $i < count( $years ); $i++ )
$output .= get_archive_link( get_home_url( null, 'seasons/' . $years[ $i ] ), $years[ $i ], $r['format'], $r['before'], $r['after'] );
if( $r['echo'] )
echo $output;
return $output;
}
Alternate Solutions
A simpler solution might be to simply use just a 'pre_get_posts'
hook to modify queries on the front-end for year-based event archives (i.e. ! is_admin() && is_post_type_archive( 'events' ) && is_year()
), unsetting all date-related query vars and redirecting the value of the year
query var to the meta-query instead. This has the advantage of enabling you to simply use the wp_get_archives()
function to print a list of year-based archive links. However, the approach is “hacky” in that it foils expected core behaviors, and may break other functionality as a result.
Similarly, the 'get_archives_link'
filter could be used to replace the links produced by wp_get_archives()
on the fly.
There are many different approaches to this problem – but it’s worth noting that filtering “events” by a date stored in metadata is something that’s no doubt come up time and time again – I’m confident that implemented solutions are out there if you dig deep enough.