WordPress Event Components Implementation
1. Overview
This document describes the technical implementation of the event-info-plugin, which provides the custom post type, shortcodes, and templates for displaying event information on WordPress sites.
2. Plugin Structure
event-info-plugin/
├── event-info-plugin.php # Plugin bootstrap
├── includes/
│ ├── post-types/
│ │ └── class-event-post-type.php
│ ├── taxonomies/
│ │ └── class-event-taxonomies.php
│ ├── shortcodes/
│ │ ├── class-courses.php
│ │ ├── class-categories.php
│ │ ├── class-program.php
│ │ ├── class-races.php
│ │ ├── class-reg-button.php
│ │ └── class-featured-events.php
│ ├── metaboxes/
│ │ └── class-event-metabox.php
│ └── api/
│ └── class-api-client.php
├── templates/
│ ├── single-epa_event.php
│ └── archive-epa_event.php
└── assets/
├── css/
│ └── featured-events.css
└── js/
└── splide.min.js
3. Custom Post Type Registration
class Event_Post_Type {
public function register() {
register_post_type('epa_event', [
'labels' => [
'name' => 'Events',
'singular_name' => 'Event',
'add_new' => 'Add Event',
'add_new_item' => 'Add New Event',
'edit_item' => 'Edit Event',
],
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-calendar-alt',
'supports' => ['title', 'editor', 'thumbnail', 'excerpt'],
'rewrite' => ['slug' => 'event'],
'show_in_rest' => true, // Gutenberg support
]);
}
}
4. Taxonomy Registration
class Event_Taxonomies {
public function register() {
// Event Category
register_taxonomy('event_category', 'epa_event', [
'labels' => [
'name' => 'Event Categories',
'singular_name' => 'Event Category',
],
'hierarchical' => true,
'show_in_rest' => true,
'rewrite' => ['slug' => 'event-category'],
]);
// Event Venue
register_taxonomy('event_venue', 'epa_event', [
'labels' => [
'name' => 'Venues',
'singular_name' => 'Venue',
],
'hierarchical' => false,
'show_in_rest' => true,
'rewrite' => ['slug' => 'venue'],
]);
}
}
5. Shortcode Implementation Pattern
All shortcodes follow a consistent implementation pattern:
5.1. Registration
class Courses_Shortcode {
public function __construct() {
add_shortcode('Courses', [$this, 'render']);
add_action('wp_ajax_epa_get_courses', [$this, 'ajaxHandler']);
add_action('wp_ajax_nopriv_epa_get_courses', [$this, 'ajaxHandler']);
}
public function render($atts) {
wp_enqueue_script('jquery-ui-tabs');
wp_enqueue_style('datatables-css');
return '<div id="epa-courses-container" class="epa-loading">
<div class="epa-spinner"></div>
</div>';
}
}
5.2. AJAX Handler
public function ajaxHandler() {
check_ajax_referer('epa_nonce', 'nonce');
$postId = intval($_POST['post_id']);
$eventId = get_post_meta($postId, '_event_id', true);
if (!$eventId) {
wp_send_json_error('No event linked to this page');
}
$apiClient = new API_Client();
$courses = $apiClient->getCourses($eventId);
if (is_wp_error($courses)) {
wp_send_json_error($courses->get_error_message());
}
wp_send_json_success($this->formatCoursesHtml($courses));
}
5.3. JavaScript Initialisation
jQuery(document).ready(function($) {
const container = $('#epa-courses-container');
if (container.length === 0) return;
$.ajax({
url: epa_ajax.url,
type: 'POST',
data: {
action: 'epa_get_courses',
nonce: epa_ajax.nonce,
post_id: epa_ajax.post_id
},
success: function(response) {
if (response.success) {
container.html(response.data);
container.removeClass('epa-loading');
// Initialize jQuery UI tabs
container.find('.epa-tabs').tabs();
} else {
container.html('<p class="epa-error">' + response.data + '</p>');
}
}
});
});
6. Individual Shortcode Details
6.1. [Courses] - RideWithGPS Integration
Displays race courses via embedded iframe from RideWithGPS.
API Endpoint: GET /api/courses?eventId.equals={eventId}
Output:
<div class="epa-tabs">
<ul>
<li><a href="#course-1">50km Route</a></li>
<li><a href="#course-2">100km Route</a></li>
</ul>
<div id="course-1">
<iframe src="https://ridewithgps.com/embeds?type=route&id=12345"
width="100%" height="500" frameborder="0"></iframe>
</div>
<!-- ... -->
</div>
6.2. [EventCategories] - Age Categories
Displays age categories with birth year calculations.
API Endpoint: GET /api/event-categories?eventId.equals={eventId}&size=100
Calculation Logic:
$currentYear = date('Y');
$birthYearMin = $currentYear - $category->maxAge;
$birthYearMax = $currentYear - $category->minAge;
$birthYearRange = "$birthYearMin - $birthYearMax";
Output: DataTable with columns: Age Range, Year of Birth, Category Name
6.3. [Program] - Event Schedule
Displays the event program/timetable.
API Endpoint: GET /api/program-entries?eventId.equals={eventId}&size=100
Sorting:
usort($entries, function($a, $b) {
return strtotime($a->dateTime) - strtotime($b->dateTime);
});
Output: DataTable with columns: Event Name, Time (24-hour format)
6.4. [Races] - Race Listing
Displays all races in an event.
API Endpoint: GET /api/races?eventId.equals={eventId}&size=100
Output: DataTable with columns: Name, Start Date/Time, Course Name
6.5. [Registration] - Registration Button
Renders a button linking to the registration portal.
Logic:
$registrationEnd = get_post_meta($postId, '_registration_end', true);
$eventId = get_post_meta($postId, '_event_id', true);
if (strtotime($registrationEnd) < time()) {
return '<p class="epa-closed">Online registration is now closed</p>';
}
$url = sprintf(
'https://registration.myriadevents.co.za/register?orgId=%s&eventId=%s',
$orgId, $eventId
);
return sprintf(
'<a href="%s" class="epa-reg-button">%s</a>',
esc_url($url),
esc_html($buttonText)
);
7. Featured Events Implementation
7.1. Grid Layout
class Featured_Events_Shortcode {
public function render($atts) {
$atts = shortcode_atts([
'count' => 4,
'layout' => 'grid',
'category' => '',
'columns' => 4,
], $atts);
$query = new WP_Query([
'post_type' => 'epa_event',
'posts_per_page' => $atts['count'],
'meta_key' => '_event_start_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => [
[
'key' => '_event_start_date',
'value' => date('Y-m-d'),
'compare' => '>=',
'type' => 'DATE'
]
],
]);
// Apply category filter if specified
if (!empty($atts['category'])) {
$query->set('tax_query', [[
'taxonomy' => 'event_category',
'field' => 'slug',
'terms' => $atts['category'],
]]);
}
return $this->renderGrid($query, $atts);
}
}
7.2. Slider Variant (Splide.js)
private function renderSlider($query, $atts) {
wp_enqueue_style('splide-css',
plugins_url('assets/css/splide.min.css', __FILE__));
wp_enqueue_script('splide-js',
plugins_url('assets/js/splide.min.js', __FILE__),
[], '4.1.4', true);
ob_start();
?>
<div id="featured-events-slider" class="splide">
<div class="splide__track">
<ul class="splide__list">
<?php while ($query->have_posts()): $query->the_post(); ?>
<li class="splide__slide">
<?php $this->renderCard(get_the_ID()); ?>
</li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
new Splide('#featured-events-slider', {
type: 'loop',
perPage: <?php echo $atts['columns']; ?>,
gap: '1rem',
autoplay: true,
interval: 5000,
pauseOnHover: true,
breakpoints: {
1024: { perPage: 3 },
768: { perPage: 2 },
480: { perPage: 1 }
}
}).mount();
});
</script>
<?php
return ob_get_clean();
}
7.3. Event Card Template
private function renderCard($postId) {
$startDate = get_post_meta($postId, '_event_start_date', true);
$venue = get_post_meta($postId, '_venue_name', true);
$city = get_post_meta($postId, '_venue_city', true);
$categories = get_the_terms($postId, 'event_category');
?>
<article class="event-card">
<div class="event-card__image">
<?php if (has_post_thumbnail()): ?>
<?php the_post_thumbnail('medium_large'); ?>
<?php else: ?>
<div class="event-card__placeholder"></div>
<?php endif; ?>
<?php if ($categories): ?>
<span class="event-card__badge event-card__badge--<?php
echo esc_attr($categories[0]->slug); ?>">
<?php echo esc_html($categories[0]->name); ?>
</span>
<?php endif; ?>
</div>
<div class="event-card__content">
<h3 class="event-card__title"><?php the_title(); ?></h3>
<div class="event-card__meta">
<span class="event-card__date">
<?php echo date_i18n('D, j M Y', strtotime($startDate)); ?>
</span>
<span class="event-card__location">
<?php echo esc_html("$venue, $city"); ?>
</span>
</div>
<a href="<?php the_permalink(); ?>" class="event-card__cta">
Explore
</a>
</div>
</article>
<?php
}
8. DataTables Integration
All table-based shortcodes use DataTables for enhanced functionality:
$('.epa-datatable').DataTable({
paging: rowCount > 20,
searching: false,
ordering: false,
info: false,
responsive: true,
language: {
emptyTable: 'No data available'
}
});
9. Caching Strategy
9.1. Transient Caching
public function getCourses($eventId) {
$cacheKey = "epa_courses_{$eventId}";
$cached = get_transient($cacheKey);
if ($cached !== false) {
return $cached;
}
$response = $this->apiClient->get("/api/courses", [
'eventId.equals' => $eventId
]);
if (!is_wp_error($response)) {
set_transient($cacheKey, $response, DAY_IN_SECONDS);
}
return $response;
}
10. Responsive Design
10.1. CSS Grid Breakpoints
.events-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
}
@media (max-width: 1024px) {
.events-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 768px) {
.events-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.events-grid { grid-template-columns: 1fr; }
}
11. Related Documentation
-
Components Architecture - High-level design
-
Payment Implementation - Payment processing
-
WordPress Site Guide - User documentation