feature: Initial commit

This commit is contained in:
Keith Solomon
2025-08-30 14:22:44 -05:00
commit 63fc9f034d
19 changed files with 1633 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
<?php
/**
* Register ACF Fields.
*
* @package AA_Events
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register the ACF field group.
*/
function events_register_acf_fields() {
if ( function_exists( 'acf_add_local_field_group' ) ) :
acf_add_local_field_group(
array(
'key' => 'group_61b0c5f5a3e7e',
'title' => 'Event Details',
'fields' => array(
array(
'key' => 'field_61b0c609a3e7f',
'label' => 'Event Date & Time',
'name' => 'event_datetime',
'type' => 'date_time_picker',
'instructions' => '',
'required' => 1,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'display_format' => 'F j, Y g:i a',
'return_format' => 'Y-m-d H:i:s',
'first_day' => 1,
),
array(
'key' => 'field_61b0c6a5a3e82',
'label' => 'Event Cost',
'name' => 'event_cost',
'type' => 'text',
'instructions' => '',
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'default_value' => '',
'placeholder' => '',
'prepend' => '',
'append' => '',
'maxlength' => '',
),
array(
'key' => 'field_61b0c6cba3e83',
'label' => 'Online/In-Person',
'name' => 'event_location_type',
'type' => 'true_false',
'instructions' => '',
'required' => 0,
'conditional_logic' => 0,
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'message' => '',
'default_value' => 0,
'ui' => 1,
'ui_on_text' => 'Online',
'ui_off_text' => 'In-Person',
),
array(
'key' => 'field_61b0c702a3e84',
'label' => 'Event URL',
'name' => 'event_url',
'type' => 'url',
'instructions' => '',
'required' => 1,
'conditional_logic' => array(
array(
array(
'field' => 'field_61b0c6cba3e83',
'operator' => '==',
'value' => '1',
),
),
),
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'default_value' => '',
'placeholder' => '',
),
array(
'key' => 'field_61b0c72fa3e85',
'label' => 'Event Address',
'name' => 'event_address',
'type' => 'textarea',
'instructions' => '',
'required' => 1,
'conditional_logic' => array(
array(
array(
'field' => 'field_61b0c6cba3e83',
'operator' => '!=',
'value' => '1',
),
),
),
'wrapper' => array(
'width' => '',
'class' => '',
'id' => '',
),
'default_value' => '',
'placeholder' => '',
'maxlength' => '',
'rows' => '',
'new_lines' => '',
),
),
'location' => array(
array(
array(
'param' => 'post_type',
'operator' => '==',
'value' => 'event',
),
),
),
'menu_order' => 0,
'position' => 'normal',
'style' => 'default',
'label_placement' => 'top',
'instruction_placement' => 'label',
'hide_on_screen' => '',
'active' => true,
'description' => '',
)
);
endif;
}
add_action( 'acf/init', 'events_register_acf_fields' );
+128
View File
@@ -0,0 +1,128 @@
<?php
/**
* The core plugin class.
*
* @package AA_Events
*/
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* The core plugin class.
*/
final class AA_Events {
/**
* The single instance of the class.
*
* @var AA_Events
*/
protected static $instance = null;
/**
* Main AA_Events Instance.
*
* Ensures only one instance of AA_Events is loaded or can be loaded.
*
* @static
* @return AA_Events - Main instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* AA_Events Constructor.
*/
public function __construct() {
$this->includes();
$this->init_hooks();
}
/**
* Include required core files.
*/
public function includes() {
include_once AA_EVENTS_PLUGIN_DIR . 'includes/post-types.php';
include_once AA_EVENTS_PLUGIN_DIR . 'includes/acf-fields.php';
include_once AA_EVENTS_PLUGIN_DIR . 'includes/template-loader.php';
}
/**
* Hook into actions and filters.
*/
private function init_hooks() {
add_action( 'init', array( $this, 'load_plugin_textdomain' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) );
// Ensure the active theme supports thumbnails for the event post type.
add_action( 'after_setup_theme', array( $this, 'ensure_post_thumbnails_support' ) );
}
/**
* Enqueue styles.
*/
public function enqueue_styles() {
$theme_stylesheet = get_stylesheet_directory() . '/aa-events/aa-events.css';
if ( file_exists( $theme_stylesheet ) ) {
wp_enqueue_style(
'aa-events-theme',
get_stylesheet_directory_uri() . '/aa-events/aa-events.css',
array(),
filemtime( $theme_stylesheet )
);
} else {
wp_enqueue_style(
'aa-events',
AA_EVENTS_PLUGIN_URL . 'assets/css/aa-events.css',
array(),
AA_EVENTS_VERSION
);
}
}
/**
* Load Localisation files.
*/
public function load_plugin_textdomain() {
load_plugin_textdomain(
'aa-events',
false,
dirname( plugin_basename( __FILE__ ) ) . '/languages/'
);
}
/**
* Ensure the theme supports post thumbnails for events.
*
* Some themes do not declare `add_theme_support( 'post-thumbnails' )`,
* which hides the Featured Image panel. This enables thumbnails at least
* for the `event` post type without impacting other types.
*/
public function ensure_post_thumbnails_support() {
$support = get_theme_support( 'post-thumbnails' );
// If theme has no support at all, enable it for events only.
if ( false === $support ) {
add_theme_support( 'post-thumbnails', array( 'event' ) );
return;
}
// If theme supports specific post types, ensure 'event' is included.
if ( is_array( $support ) && isset( $support[0] ) && is_array( $support[0] ) ) {
$post_types = $support[0];
if ( ! in_array( 'event', $post_types, true ) ) {
$post_types[] = 'event';
add_theme_support( 'post-thumbnails', $post_types );
}
}
// If theme support is boolean true (all types), nothing to do.
}
}
+224
View File
@@ -0,0 +1,224 @@
<?php
/**
* Register Custom Post Types and Taxonomies.
*
* @package AA_Events
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Register a custom post type for events.
*/
function events_register_post_type() {
$labels = array(
'name' => _x( 'Events', 'post type general name', 'aa-events' ),
'singular_name' => _x( 'Event', 'post type singular name', 'aa-events' ),
'menu_name' => _x( 'Events', 'admin menu', 'aa-events' ),
'name_admin_bar' => _x( 'Event', 'add new on admin bar', 'aa-events' ),
'add_new' => _x( 'Add New', 'event', 'aa-events' ),
'add_new_item' => __( 'Add New Event', 'aa-events' ),
'new_item' => __( 'New Event', 'aa-events' ),
'edit_item' => __( 'Edit Event', 'aa-events' ),
'view_item' => __( 'View Event', 'aa-events' ),
'all_items' => __( 'All Events', 'aa-events' ),
'search_items' => __( 'Search Events', 'aa-events' ),
'parent_item_colon' => __( 'Parent Events:', 'aa-events' ),
'not_found' => __( 'No events found.', 'aa-events' ),
'not_found_in_trash' => __( 'No events found in Trash.', 'aa-events' ),
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'events' ),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'supports' => array( 'title', 'editor', 'author', 'thumbnail' ),
'menu_icon' => 'dashicons-calendar-alt',
);
register_post_type( 'event', $args );
}
add_action( 'init', 'events_register_post_type' );
/**
* Register a custom taxonomy for event types.
*/
function events_register_event_type_taxonomy() {
$labels = array(
'name' => 'Event Types',
'singular_name' => 'Event Type',
'search_items' => 'Search Event Types',
'all_items' => 'All Event Types',
'parent_item' => 'Parent Event Type',
'parent_item_colon' => 'Parent Event Type:',
'edit_item' => 'Edit Event Type',
'update_item' => 'Update Event Type',
'add_new_item' => 'Add New Event Type',
'new_item_name' => 'New Event Type Name',
'menu_name' => 'Event Types',
);
$args = array(
'hierarchical' => true,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'event-type' ),
);
register_taxonomy( 'event_type', array( 'event' ), $args );
}
add_action( 'init', 'events_register_event_type_taxonomy' );
/**
* Register a custom taxonomy for event tags.
*/
function events_register_event_tag_taxonomy() {
$labels = array(
'name' => _x( 'Event Tags', 'taxonomy general name', 'aa-events' ),
'singular_name' => _x( 'Event Tag', 'taxonomy singular name', 'aa-events' ),
'search_items' => __( 'Search Event Tags', 'aa-events' ),
'all_items' => __( 'All Event Tags', 'aa-events' ),
'parent_item' => null,
'parent_item_colon' => null,
'edit_item' => __( 'Edit Event Tag', 'aa-events' ),
'update_item' => __( 'Update Event Tag', 'aa-events' ),
'add_new_item' => __( 'Add New Event Tag', 'aa-events' ),
'new_item_name' => __( 'New Event Tag Name', 'aa-events' ),
'separate_items_with_commas' => __( 'Separate event tags with commas', 'aa-events' ),
'add_or_remove_items' => __( 'Add or remove event tags', 'aa-events' ),
'choose_from_most_used' => __( 'Choose from the most used event tags', 'aa-events' ),
'not_found' => __( 'No event tags found.', 'aa-events' ),
'menu_name' => __( 'Event Tags', 'aa-events' ),
);
$args = array(
'hierarchical' => false,
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'event-tag' ),
);
register_taxonomy( 'event_tag', array( 'event' ), $args );
}
add_action( 'init', 'events_register_event_tag_taxonomy' );
/**
* Add custom columns to the event list table.
*
* @param array $columns The existing columns.
* @return array The modified columns.
*/
function events_add_admin_columns( $columns ) {
$new_columns = array();
foreach ( $columns as $key => $value ) {
$new_columns[ $key ] = $value;
if ( 'title' === $key ) {
$new_columns['event_datetime'] = __( 'Event Date & Time', 'aa-events' );
}
}
return $new_columns;
}
add_filter( 'manage_edit-event_columns', 'events_add_admin_columns' );
/**
* Display the content for the custom columns.
*
* @param string $column The name of the column.
* @param int $post_id The ID of the post.
*/
function events_display_admin_column_content( $column, $post_id ) {
if ( 'event_datetime' === $column ) {
$datetime = get_field( 'event_datetime', $post_id );
if ( $datetime ) {
echo esc_html( gmdate( 'F j, Y g:i a', strtotime( $datetime ) ) );
}
}
}
add_action( 'manage_event_posts_custom_column', 'events_display_admin_column_content', 10, 2 );
/**
* Make the custom column sortable.
*
* @param array $columns The existing sortable columns.
* @return array The modified sortable columns.
*/
function events_make_admin_column_sortable( $columns ) {
$columns['event_datetime'] = 'event_datetime';
return $columns;
}
add_filter( 'manage_edit-event_sortable_columns', 'events_make_admin_column_sortable' );
/**
* Handle sorting by the custom column.
*
* @param WP_Query $query The WordPress query object.
*/
function events_sort_by_custom_column( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) {
return;
}
if ( 'event_datetime' === $query->get( 'orderby' ) ) {
$query->set( 'meta_key', 'event_datetime' );
$query->set( 'orderby', 'meta_value' );
}
}
add_action( 'pre_get_posts', 'events_sort_by_custom_column' );
/**
* Set default sort order for event post type by event_datetime ascending.
* Applies to both admin and frontend queries.
*
* @param WP_Query $query The WordPress query object.
*/
function events_set_default_sort_order( $query ) {
// Only modify main queries for event post type
if ( ! $query->is_main_query() ) {
return;
}
// Admin list table
if ( is_admin() && $query->get( 'post_type' ) === 'event' ) {
if ( ! $query->get( 'orderby' ) ) {
$query->set( 'meta_key', 'event_datetime' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'order', 'ASC' );
}
}
// Frontend: event archives and taxonomies
if ( ! is_admin() && $query->get( 'post_type' ) === 'event' ) {
// Main event archive: newest first (descending by date/time)
if ( $query->is_post_type_archive( 'event' ) ) {
if ( ! $query->get( 'orderby' ) ) {
$query->set( 'meta_key', 'event_datetime' );
$query->set( 'meta_type', 'DATETIME' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'order', 'DESC' );
}
}
// Taxonomy views: upcoming first (ascending)
if ( ( $query->is_tax( 'event_type' ) || $query->is_tax( 'event_tag' ) ) && ! $query->get( 'orderby' ) ) {
$query->set( 'meta_key', 'event_datetime' );
$query->set( 'meta_type', 'DATETIME' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'order', 'ASC' );
}
}
}
add_action( 'pre_get_posts', 'events_set_default_sort_order' );
+163
View File
@@ -0,0 +1,163 @@
<?php
/**
* Template Loader for AA Events.
*
* @package AA_Events
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Load a template.
*
* Handles template usage so that users can override the plugin's templates by using the theme.
*
* @param string $template_name Template name.
* @param array $args Arguments. (default: array).
* @param string $template_path Template path. (default: '').
* @param string $default_path Default path. (default: '').
*/
function events_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
if ( ! empty( $args ) && is_array( $args ) ) {
extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
}
$located = events_locate_template( $template_name, $template_path, $default_path );
if ( ! file_exists( $located ) ) {
_doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $located ) ), '1.0' );
return;
}
// Allow 3rd party plugin filter template file from theme.
$located = apply_filters( 'events_get_template', $located, $template_name, $args, $template_path, $default_path );
do_action( 'events_before_template_part', $template_name, $template_path, $located, $args );
include $located;
do_action( 'events_after_template_part', $template_name, $template_path, $located, $args );
}
/**
* Locate a template and return the path for inclusion.
*
* This is the load order:
*
* yourtheme/$template_path/$template_name
* yourtheme/$template_name
* $default_path/$template_name
*
* @param string $template_name Template name.
* @param string $template_path Template path. (default: '').
* @param string $default_path Default path. (default: '').
* @return string
*/
function events_locate_template( $template_name, $template_path = '', $default_path = '' ) {
if ( ! $template_path ) {
$template_path = 'aa-events/';
}
if ( ! $default_path ) {
$default_path = AA_EVENTS_PLUGIN_DIR . 'templates/';
}
// Look within passed path within the theme - this is priority.
$template = locate_template(
array(
trailingslashit( $template_path ) . $template_name,
$template_name,
)
);
// Get default template/
if ( ! $template ) {
$template = $default_path . $template_name;
}
// Return what we found.
return apply_filters( 'events_locate_template', $template, $template_name, $template_path );
}
/**
* Filter the single template.
*
* @param string $template Template.
* @return string
*/
function events_single_template( $template ) {
if ( get_post_type() === 'event' ) {
$template = events_locate_template( 'single-event.php' );
}
return $template;
}
add_filter( 'single_template', 'events_single_template' );
/**
* Filter the archive template.
*
* @param string $template Template.
* @return string
*/
function events_archive_template( $template ) {
if ( is_post_type_archive( 'event' ) ) {
$template = events_locate_template( 'archive-event.php' );
}
return $template;
}
add_filter( 'archive_template', 'events_archive_template' );
/**
* Register plugin-provided page templates so they appear in the editor.
*
* WordPress only scans themes for page templates by default. This filter
* injects our plugin template into the list shown in the Page Attributes
* (or Template) dropdown, without requiring the file to live in the theme.
*
* @param array $page_templates Array of page templates.
* @param WP_Theme $theme WP_Theme object.
* @param WP_Post|null $post The post being edited (or null).
* @param string $post_type Post type slug.
* @return array
*/
function events_register_page_templates( $page_templates, $theme, $post, $post_type ) {
if ( 'page' !== $post_type ) {
return $page_templates;
}
// Key can be any unique string. We'll look for this exact value when loading.
$page_templates['aa-events-template-calendar.php'] = __( 'Events Calendar', 'aa-events' );
return $page_templates;
}
add_filter( 'theme_page_templates', 'events_register_page_templates', 10, 4 );
/**
* Load the selected plugin template on the front end.
*
* When a page uses our registered template slug, point WordPress to the
* template file bundled inside the plugin.
*
* @param string $template Resolved template path.
* @return string
*/
function events_load_plugin_page_template( $template ) {
if ( is_singular( 'page' ) ) {
$post_id = get_queried_object_id();
if ( $post_id ) {
$selected = get_post_meta( $post_id, '_wp_page_template', true );
if ( 'aa-events-template-calendar.php' === $selected ) {
// Allow theme override via aa-events/template-calendar.php, then fall back to plugin file.
$resolved = events_locate_template( 'template-calendar.php' );
if ( $resolved && file_exists( $resolved ) ) {
return $resolved;
}
}
}
}
return $template;
}
add_filter( 'template_include', 'events_load_plugin_page_template', 20 );