Developer Guide
Developer Guide#
Extend and customize the plugin using hooks, filters, templates, and the API. This guide covers common customization scenarios.
Architecture overview#
File structure#
evenzo-events-manager/
├── assets/
│ ├── css/ # Stylesheets
│ └── js/ # JavaScript files
├── includes/
│ ├── admin/ # Admin screens and settings
│ ├── api/ # REST API controllers and webhooks
│ ├── blocks/ # Gutenberg blocks
│ ├── core/ # Event, attendee, post types, QR code, recurrence
│ ├── emails/ # WooCommerce email classes
│ ├── export/ # CSV and iCal export
│ ├── frontend/ # Templates, shortcodes, calendar, widgets
│ ├── import/ # CSV and iCal import
│ ├── pdf/ # Printable tickets
│ ├── seating/ # Seating charts
│ ├── speakers/ # Speaker post type
│ ├── waitlist/ # Waitlist and pricing rules
│ └── woocommerce/ # Product and order integration
├── languages/ # Translation files
└── templates/ # Template files
Class naming#
All classes use the EMWC_ prefix. The main plugin class is EMWC (use class_exists( 'EMWC' ) for dependency checks).
EMWC_Event- Event model (get_start_date(),get_venue_id(),is_upcoming(),save())EMWC_Attendee- Attendee model (check_in(),check_out(),get_by_ticket_code(),get_for_event())EMWC_Logger- Logging through the WooCommerce logger
Database tables#
Custom tables created on activation:
| Table | Purpose |
|---|---|
wp_emwc_attendees | Attendee and ticket records |
wp_emwc_checkins | Check-in and check-out history |
wp_emwc_waitlist | Waitlist entries |
wp_emwc_pricing_rules | Created but not used by the current pricing code (rules are stored in product meta) |
Hooks reference#
Action hooks#
Tickets and attendees
// Attendee record created while generating tickets for an order
do_action( 'emwc_attendee_created', $attendee_id, $attendee_data, $event_id, $order_id );
// All tickets for an order generated
do_action( 'emwc_tickets_generated', $order_id, $order );
// All tickets for an order cancelled (order cancelled or refunded)
do_action( 'emwc_tickets_cancelled', $order_id );
// Single ticket cancelled or reactivated from the Attendees screen
do_action( 'emwc_ticket_cancelled', $attendee_id, $attendee );
do_action( 'emwc_ticket_reactivated', $attendee_id, $attendee );
// Ticket email resent from the Attendees screen
do_action( 'emwc_ticket_email_resent', $attendee_id, $order_id );
// Attendee deleted
do_action( 'emwc_attendee_deleted', $attendee_id );
// An attendee's name, email, or custom field answers changed, from the
// admin attendee editor or from the buyer's Edit attendee details page
do_action( 'emwc_attendee_updated', $attendee_id, $diff );
$attendee is an EMWC_Attendee object. $attendee_data is an array with first_name, last_name, email, and ticket_code. $diff is an array of the changed fields, each with old and new values.
Check-in
// Fired from the admin check-in screen (second argument is an EMWC_Attendee)
// and from the REST check-in endpoint (second argument is the attendee row as an array)
do_action( 'emwc_attendee_checked_in', $attendee_id, $attendee );
do_action( 'emwc_attendee_checked_out', $attendee_id, $attendee );
Waitlist
// Someone joined the waitlist
do_action( 'emwc_waitlist_joined', $entry_id, $product_id, $email );
// Notification email sent to a waitlist entry
do_action( 'emwc_waitlist_notification_sent', $entry, $product );
REST API
do_action( 'emwc_rest_event_created', $post_id, $request );
do_action( 'emwc_rest_event_updated', $post_id, $request );
do_action( 'emwc_rest_event_deleted', $post_id, $force );
do_action( 'emwc_rest_venue_created', $post_id, $request );
do_action( 'emwc_rest_venue_updated', $post_id, $request );
do_action( 'emwc_rest_venue_deleted', $post_id, $force );
do_action( 'emwc_rest_organizer_created', $post_id, $request );
do_action( 'emwc_rest_organizer_updated', $post_id, $request );
do_action( 'emwc_rest_organizer_deleted', $post_id, $force );
do_action( 'emwc_rest_attendee_updated', $attendee_id, $request );
Templates
// Inside the single event template, after the event details block
do_action( 'emwc_single_event_after_details', $event_id );
Filter hooks#
REST responses
add_filter( 'emwc_rest_prepare_event', function( $data, $post, $request ) {
$data['custom_field'] = get_post_meta( $post->ID, 'custom_field', true );
return $data;
}, 10, 3 );
Also available: emwc_rest_prepare_venue and emwc_rest_prepare_organizer ($data, $post, $request), and emwc_rest_prepare_attendee ($data, $attendee array, $request).
Structured data
// Schema.org Event data printed on single event pages
add_filter( 'emwc_event_schema', function( $schema, $event_id ) {
$schema['eventAttendanceMode'] = 'https://schema.org/MixedEventAttendanceMode';
return $schema;
}, 10, 2 );
Tickets
// Accent color on the printable ticket (default #2563eb)
add_filter( 'emwc_ticket_primary_color', function( $color ) {
return '#111111';
} );
Attendee export
// Add, remove, or reorder columns in the attendees CSV.
// $columns is key => header, in file order. $args are the query
// args the export is running with (from EMWC_Attendee_Query::from_request()).
add_filter( 'emwc_attendee_export_columns', function( $columns, $args ) {
$columns['loyalty_tier'] = __( 'Loyalty Tier', 'my-plugin' );
return $columns;
}, 10, 2 );
// Change a row's values before the cells are escaped. $row is key => value
// for one attendee, keyed the same as $columns above.
add_filter( 'emwc_attendee_export_row', function( $row, $attendee, $columns ) {
if ( isset( $columns['loyalty_tier'] ) ) {
$row['loyalty_tier'] = my_plugin_get_loyalty_tier( $attendee->get( 'email' ) );
}
return $row;
}, 10, 3 );
// Rows fetched per batch while streaming the export. Default 500, minimum 50.
add_filter( 'emwc_attendee_export_batch_size', function( $size ) {
return 1000;
} );
A custom column’s key becomes cf_{field key} if it collides with a standard column key such as event, seat, series, or currency. Escaping for the injection-protection prefix always runs after these filters, so a filter cannot turn it off.
Edit attendee details page
// Seconds a guest must wait between two saves of the same order's
// edit page, to slow down repeated submits. Default 10.
add_filter( 'emwc_attendee_details_rate_limit', function( $seconds ) {
return 30;
} );
Template overrides#
Page templates#
Copy a template from:
plugins/evenzo-events-manager/templates/
To either of these locations in your theme:
your-theme/single-emwc_event.php
your-theme/evenzo-events-manager/single-emwc_event.php
| Template | Purpose |
|---|---|
single-emwc_event.php | Single event page |
archive-emwc_event.php | Event archive and category pages |
single-emwc_speaker.php | Single speaker page |
archive-emwc_speaker.php | Speaker archive |
attendee-details.php | The buyer’s Edit attendee details page |
Copy attendee-details.php only to your-theme/evenzo-events-manager/attendee-details.php. It does not have a theme-root override path.
Email templates#
Emails are WooCommerce emails and follow the WooCommerce override path:
your-theme/woocommerce/emails/registration-confirmation.php
your-theme/woocommerce/emails/plain/registration-confirmation.php
Available files: admin-new-registration.php, event-cancellation.php, event-reminder.php, event-update.php, registration-confirmation.php, ticket-delivery.php, each with a plain/ version.
Headless checkout (Store API)#
The classic and block checkouts both collect attendee details through the WooCommerce Store API’s extension data, under the key evenzo-events-manager.
- Cart, read only:
attendee_slots. One entry per attendee a ticket in the cart needs details for, with the attendee’s number, the cart item key, the ticket name, the event name and date, and which custom fields to ask. - Checkout, write:
attendees. One entry per attendee, matching the slots from the cart response, withfirst_name,last_name,email, and the custom field answers.
A headless or custom checkout client must read attendee_slots from the cart response and send attendees on the checkout request whenever the cart has tickets that collect attendee info. If the cart needs attendee details and the checkout request does not send them, or a required field is missing, the order fails with the error code emwc_attendee_details_invalid and a message naming the missing field.
The plugin’s own block checkout fields (assets/js/emwc-block-checkout.js) work the same way, so they double as a reference implementation.
Custom post type integration#
Post types: emwc_event, emwc_venue, emwc_organizer, emwc_speaker. Taxonomy: emwc_event_category.
Event meta keys#
| Key | Description |
|---|---|
_emwc_start_date | Start date (Y-m-d) |
_emwc_end_date | End date |
_emwc_start_time | Start time (H:i) |
_emwc_end_time | End time |
_emwc_all_day | All day event (yes/no) |
_emwc_timezone | Event timezone |
_emwc_event_status | scheduled, postponed, cancelled, sold-out |
_emwc_event_type | in-person, virtual, hybrid |
_emwc_venue_id | Venue post ID |
_emwc_organizer_id | Organizer post ID |
_emwc_capacity | Maximum attendees |
_emwc_registration_deadline | Registration deadline |
_emwc_featured | Featured event (yes/no) |
_emwc_virtual_url | Virtual event URL |
_emwc_external_url | External URL (meta only, no admin field) |
The REST API and calendar endpoints read and write venue and organizer under _emwc_venue and _emwc_organizer instead. Events created in the admin report venue_id: 0 through the API until the two key names match.
Querying events#
$events = new WP_Query( array(
'post_type' => 'emwc_event',
'meta_key' => '_emwc_start_date',
'orderby' => 'meta_value',
'order' => 'ASC',
'meta_query' => array(
array(
'key' => '_emwc_start_date',
'value' => current_time( 'Y-m-d' ),
'compare' => '>=',
'type' => 'DATE',
),
),
) );
Database operations#
Attendee table#
Prefer the model over raw SQL:
$attendees = EMWC_Attendee::get_for_event( $event_id, array( 'status' => 'active' ) );
$attendee = EMWC_Attendee::get_by_ticket_code( 'EMWC-ABC123DEF' );
$attendee->check_in( get_current_user_id(), 'front desk', 'VIP' );
Attendee statuses: active, checked_in, checked_out, cancelled.
Check-in table#
Columns: checkin_id, attendee_id, event_id, check_type (in or out), checked_by, checked_at, device_info, notes. Rows are written by EMWC_Attendee::check_in() and check_out().
Creating extensions#
Plugin header#
<?php
/**
* Plugin Name: EMWC Custom Extension
* Description: Custom features for Evenzo Events Manager
* Version: 1.0.0
* Requires Plugins: woocommerce
*/
Dependency check#
add_action( 'plugins_loaded', function() {
if ( ! class_exists( 'EMWC' ) ) {
add_action( 'admin_notices', function() {
echo '<div class="error"><p>This extension requires Evenzo Events Manager.</p></div>';
} );
return;
}
// Initialize extension
}, 20 );
Adding admin pages#
The top-level menu slug is emwc-dashboard and uses the manage_woocommerce capability.
add_action( 'admin_menu', function() {
add_submenu_page(
'emwc-dashboard',
'Custom Page',
'Custom Page',
'manage_woocommerce',
'emwc-custom',
'render_custom_page'
);
}, 20 );
Debugging#
Enable Debug Mode under Events > Settings > Advanced (option emwc_enable_debug_mode). Debug, info, notice, and warning messages are then written to the WooCommerce log with source emwc. Error level messages and above are always written.
EMWC_Logger::debug( 'Custom message', 'my-context', array( 'key' => 'value' ) );
EMWC_Logger::error( 'Something failed', 'my-context' );
View logs under WooCommerce > Status > Logs.
Recommended approach#
Performance#
- Cache expensive queries
- Use transients for repeated data
- Load assets only where needed
Security#
- Sanitize all input
- Escape all output
- Verify nonces
- Check capabilities
Compatibility#
- Test with the latest WordPress and WooCommerce
- Use hooks instead of modifying plugin files