Systematic audit and implementation of missing Master Trainer functionality with comprehensive WordPress best practices and security implementation. ## Features Implemented - Master Events Overview (/master-trainer/events/) - KPI dashboard with filtering - Import/Export Data Management (/master-trainer/import-export/) - CSV operations - Communication Templates (/trainer/communication-templates/) - Professional templates - Enhanced Announcements (/master-trainer/announcements/) - Dynamic shortcode integration - Pending Approvals System (/master-trainer/pending-approvals/) - Workflow management ## Navigation & UX Improvements - Removed redundant Events link from top-level navigation menu - Reorganized administrative functions under Tools dropdown - Enhanced navigation clarity and professional appearance - Full responsive design with accessibility compliance ## Architecture & Security - 5 new singleton manager classes following WordPress patterns - Comprehensive role-based access control (hvac_master_trainer) - Complete security implementation (nonces, sanitization, escaping) - Performance optimizations with transient caching and conditional loading - Professional error handling and user feedback systems ## Files Added (16 new files) - 4 manager classes: Import/Export, Events Overview, Pending Approvals, Communication Templates - 4 CSS files with responsive design and accessibility features - 4 JavaScript files with AJAX functionality and error handling - 2 new templates: Import/Export, Pending Approvals - 2 enhanced templates: Events Overview, Communication Templates ## Files Modified (14 files) - Core system integration in Plugin, Page Manager, Scripts/Styles classes - Navigation system cleanup in Master Menu System - Enhanced access control and role management - Template updates for dynamic content integration ## Testing & Deployment - Comprehensive testing with Playwright automation - Successful staging deployment and verification - All 5 missing pages now fully functional - Navigation improvements verified working Resolves master trainer area audit requirements with production-ready implementation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
498 lines
No EOL
15 KiB
PHP
498 lines
No EOL
15 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Master Events Overview
|
|
*
|
|
* Provides comprehensive events overview for master trainers with read-only access
|
|
* to all events across all trainers with filtering, KPI tiles, and performance optimization.
|
|
*
|
|
* @package HVAC Community Events
|
|
* @subpackage Includes
|
|
* @author Ben Reed
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Master_Events_Overview
|
|
*
|
|
* Handles comprehensive read-only events overview for master trainers
|
|
*/
|
|
class HVAC_Master_Events_Overview {
|
|
|
|
/**
|
|
* Instance
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Master dashboard data instance
|
|
*/
|
|
private $dashboard_data = null;
|
|
|
|
/**
|
|
* Get instance (singleton pattern)
|
|
*/
|
|
public static function instance() {
|
|
if ( is_null( self::$instance ) ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
// Load dependencies
|
|
$this->load_dependencies();
|
|
|
|
// Initialize hooks
|
|
$this->init_hooks();
|
|
|
|
// Initialize dashboard data
|
|
if ( class_exists( 'HVAC_Master_Dashboard_Data' ) ) {
|
|
$this->dashboard_data = new HVAC_Master_Dashboard_Data();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load required dependencies
|
|
*/
|
|
private function load_dependencies() {
|
|
// Ensure master dashboard data is available
|
|
if ( ! class_exists( 'HVAC_Master_Dashboard_Data' ) ) {
|
|
require_once HVAC_PLUGIN_DIR . 'includes/class-hvac-master-dashboard-data.php';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// AJAX handlers for events overview
|
|
add_action( 'wp_ajax_hvac_master_events_filter', array( $this, 'ajax_filter_events' ) );
|
|
add_action( 'wp_ajax_hvac_master_events_kpis', array( $this, 'ajax_get_kpis' ) );
|
|
add_action( 'wp_ajax_hvac_master_events_calendar', array( $this, 'ajax_get_calendar_data' ) );
|
|
|
|
// Shortcode for embedding events overview
|
|
add_shortcode( 'hvac_master_events', array( $this, 'render_events_overview' ) );
|
|
|
|
// Add function for template integration
|
|
add_action( 'init', array( $this, 'register_template_functions' ) );
|
|
}
|
|
|
|
/**
|
|
* Register template functions
|
|
*/
|
|
public function register_template_functions() {
|
|
if ( ! function_exists( 'hvac_render_master_events' ) ) {
|
|
function hvac_render_master_events() {
|
|
$overview = HVAC_Master_Events_Overview::instance();
|
|
return $overview->render_events_overview();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Render the complete events overview
|
|
*/
|
|
public function render_events_overview( $atts = array() ) {
|
|
// Security check
|
|
if ( ! $this->can_view_events() ) {
|
|
return '<div class="hvac-notice hvac-notice-error">You do not have permission to view events overview.</div>';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="hvac-master-events-overview" id="hvac-master-events-overview">
|
|
|
|
<!-- KPI Tiles Section -->
|
|
<div class="hvac-events-kpi-section">
|
|
<div class="hvac-kpi-loading" id="hvac-kpi-loading">
|
|
<div class="hvac-spinner"></div>
|
|
<p>Loading event statistics...</p>
|
|
</div>
|
|
<div class="hvac-kpi-tiles" id="hvac-kpi-tiles" style="display: none;">
|
|
<!-- KPI tiles will be loaded via AJAX -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters Section -->
|
|
<div class="hvac-events-filters-section">
|
|
<form class="hvac-events-filters" id="hvac-events-filters">
|
|
<div class="hvac-filters-row">
|
|
|
|
<!-- Trainer Filter -->
|
|
<div class="hvac-filter-group">
|
|
<label for="filter-trainer">Trainer:</label>
|
|
<select id="filter-trainer" name="trainer_id">
|
|
<option value="">All Trainers</option>
|
|
<?php echo $this->get_trainers_options(); ?>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Date Range Filter -->
|
|
<div class="hvac-filter-group">
|
|
<label for="filter-date-from">From Date:</label>
|
|
<input type="date" id="filter-date-from" name="date_from" />
|
|
</div>
|
|
|
|
<div class="hvac-filter-group">
|
|
<label for="filter-date-to">To Date:</label>
|
|
<input type="date" id="filter-date-to" name="date_to" />
|
|
</div>
|
|
|
|
<!-- Status Filter -->
|
|
<div class="hvac-filter-group">
|
|
<label for="filter-status">Status:</label>
|
|
<select id="filter-status" name="status">
|
|
<option value="all">All Events</option>
|
|
<option value="upcoming">Upcoming Events</option>
|
|
<option value="past">Past Events</option>
|
|
<option value="publish">Published</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="pending">Pending Review</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Search Filter -->
|
|
<div class="hvac-filter-group hvac-filter-search">
|
|
<label for="filter-search">Search:</label>
|
|
<input type="text" id="filter-search" name="search" placeholder="Event title..." />
|
|
</div>
|
|
|
|
<!-- Filter Actions -->
|
|
<div class="hvac-filter-actions">
|
|
<button type="submit" class="hvac-btn hvac-btn-primary">Filter Events</button>
|
|
<button type="button" class="hvac-btn hvac-btn-secondary" id="clear-filters">Clear All</button>
|
|
</div>
|
|
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- View Toggle Section -->
|
|
<div class="hvac-events-view-toggle">
|
|
<div class="hvac-view-controls">
|
|
<button type="button" class="hvac-view-btn hvac-view-btn-active" data-view="table" id="view-table">
|
|
<span class="dashicons dashicons-list-view"></span>
|
|
Table View
|
|
</button>
|
|
<button type="button" class="hvac-view-btn" data-view="calendar" id="view-calendar">
|
|
<span class="dashicons dashicons-calendar-alt"></span>
|
|
Calendar View
|
|
</button>
|
|
</div>
|
|
|
|
<div class="hvac-view-info">
|
|
<span id="events-count-display">Loading...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Events Content Section -->
|
|
<div class="hvac-events-content">
|
|
|
|
<!-- Table View -->
|
|
<div class="hvac-events-table-view" id="hvac-events-table-view">
|
|
<div class="hvac-events-loading" id="hvac-events-loading">
|
|
<div class="hvac-spinner"></div>
|
|
<p>Loading events...</p>
|
|
</div>
|
|
|
|
<div class="hvac-events-table-container" id="hvac-events-table-container" style="display: none;">
|
|
<!-- Events table will be loaded via AJAX -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calendar View -->
|
|
<div class="hvac-events-calendar-view" id="hvac-events-calendar-view" style="display: none;">
|
|
<div class="hvac-calendar-container">
|
|
<div class="hvac-calendar-loading">
|
|
<div class="hvac-spinner"></div>
|
|
<p>Loading calendar...</p>
|
|
</div>
|
|
<div class="hvac-calendar-content" id="hvac-calendar-content">
|
|
<!-- Calendar will be loaded via AJAX -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Hidden nonce for AJAX -->
|
|
<?php wp_nonce_field( 'hvac_master_events_nonce', 'hvac_master_events_nonce', false ); ?>
|
|
|
|
<script type="text/javascript">
|
|
// Pass AJAX URL and nonce to JavaScript
|
|
var hvac_master_events_ajax = {
|
|
ajax_url: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
|
|
nonce: '<?php echo esc_js( wp_create_nonce( 'hvac_master_events_nonce' ) ); ?>'
|
|
};
|
|
</script>
|
|
<?php
|
|
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Get trainers options for filter dropdown
|
|
*/
|
|
private function get_trainers_options() {
|
|
if ( ! $this->dashboard_data ) {
|
|
return '';
|
|
}
|
|
|
|
$trainer_stats = $this->dashboard_data->get_trainer_statistics();
|
|
$options = '';
|
|
|
|
if ( ! empty( $trainer_stats['trainer_data'] ) ) {
|
|
foreach ( $trainer_stats['trainer_data'] as $trainer ) {
|
|
$options .= sprintf(
|
|
'<option value="%d">%s (%d events)</option>',
|
|
esc_attr( $trainer->trainer_id ),
|
|
esc_html( $trainer->display_name ),
|
|
esc_html( $trainer->total_events )
|
|
);
|
|
}
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for filtering events
|
|
*/
|
|
public function ajax_filter_events() {
|
|
// Verify nonce
|
|
if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_events_nonce' ) ) {
|
|
wp_send_json_error( array( 'message' => 'Security check failed' ) );
|
|
}
|
|
|
|
// Check permissions
|
|
if ( ! $this->can_view_events() ) {
|
|
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
|
|
}
|
|
|
|
// Get filter parameters
|
|
$args = array(
|
|
'trainer_id' => isset( $_POST['trainer_id'] ) ? sanitize_text_field( $_POST['trainer_id'] ) : '',
|
|
'date_from' => isset( $_POST['date_from'] ) ? sanitize_text_field( $_POST['date_from'] ) : '',
|
|
'date_to' => isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : '',
|
|
'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all',
|
|
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
|
|
'page' => isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 1,
|
|
'per_page' => isset( $_POST['per_page'] ) ? absint( $_POST['per_page'] ) : 20,
|
|
'orderby' => isset( $_POST['orderby'] ) ? sanitize_text_field( $_POST['orderby'] ) : 'date',
|
|
'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'DESC',
|
|
);
|
|
|
|
// Get events data
|
|
if ( $this->dashboard_data ) {
|
|
$events_data = $this->dashboard_data->get_events_table_data( $args );
|
|
|
|
// Format events for display
|
|
$formatted_events = $this->format_events_for_display( $events_data['events'] );
|
|
|
|
wp_send_json_success( array(
|
|
'events' => $formatted_events,
|
|
'pagination' => $events_data['pagination'],
|
|
'total_found' => $events_data['pagination']['total_items']
|
|
) );
|
|
}
|
|
|
|
wp_send_json_error( array( 'message' => 'Unable to load events data' ) );
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for getting KPI data
|
|
*/
|
|
public function ajax_get_kpis() {
|
|
// Verify nonce
|
|
if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_events_nonce' ) ) {
|
|
wp_send_json_error( array( 'message' => 'Security check failed' ) );
|
|
}
|
|
|
|
// Check permissions
|
|
if ( ! $this->can_view_events() ) {
|
|
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
|
|
}
|
|
|
|
if ( $this->dashboard_data ) {
|
|
$kpis = array(
|
|
'total_events' => $this->dashboard_data->get_total_events_count(),
|
|
'upcoming_events' => $this->dashboard_data->get_upcoming_events_count(),
|
|
'past_events' => $this->dashboard_data->get_past_events_count(),
|
|
'total_tickets' => $this->dashboard_data->get_total_tickets_sold(),
|
|
'total_revenue' => $this->dashboard_data->get_total_revenue(),
|
|
'active_trainers' => $this->dashboard_data->get_active_trainers_count()
|
|
);
|
|
|
|
wp_send_json_success( $kpis );
|
|
}
|
|
|
|
wp_send_json_error( array( 'message' => 'Unable to load KPI data' ) );
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for calendar data
|
|
*/
|
|
public function ajax_get_calendar_data() {
|
|
// Verify nonce
|
|
if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_events_nonce' ) ) {
|
|
wp_send_json_error( array( 'message' => 'Security check failed' ) );
|
|
}
|
|
|
|
// Check permissions
|
|
if ( ! $this->can_view_events() ) {
|
|
wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
|
|
}
|
|
|
|
// Get filter parameters for calendar
|
|
$args = array(
|
|
'trainer_id' => isset( $_POST['trainer_id'] ) ? sanitize_text_field( $_POST['trainer_id'] ) : '',
|
|
'date_from' => isset( $_POST['date_from'] ) ? sanitize_text_field( $_POST['date_from'] ) : '',
|
|
'date_to' => isset( $_POST['date_to'] ) ? sanitize_text_field( $_POST['date_to'] ) : '',
|
|
'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all',
|
|
'search' => isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '',
|
|
'per_page' => 999, // Get all events for calendar display
|
|
'orderby' => 'date',
|
|
'order' => 'ASC'
|
|
);
|
|
|
|
// Get events data
|
|
if ( $this->dashboard_data ) {
|
|
$events_data = $this->dashboard_data->get_events_table_data( $args );
|
|
|
|
// Format events for calendar
|
|
$calendar_events = $this->format_events_for_calendar( $events_data['events'] );
|
|
|
|
wp_send_json_success( array(
|
|
'events' => $calendar_events,
|
|
'total_found' => count( $calendar_events )
|
|
) );
|
|
}
|
|
|
|
wp_send_json_error( array( 'message' => 'Unable to load calendar data' ) );
|
|
}
|
|
|
|
/**
|
|
* Format events for table display
|
|
*/
|
|
private function format_events_for_display( $events ) {
|
|
$formatted = array();
|
|
|
|
foreach ( $events as $event ) {
|
|
$event_date = date( 'M j, Y', $event['start_date_ts'] );
|
|
$event_time = date( 'g:i A', $event['start_date_ts'] );
|
|
|
|
// Determine status badge class
|
|
$status_class = 'hvac-status-' . esc_attr( $event['status'] );
|
|
if ( $event['start_date_ts'] > time() ) {
|
|
$status_class .= ' hvac-status-upcoming';
|
|
} else {
|
|
$status_class .= ' hvac-status-past';
|
|
}
|
|
|
|
$formatted[] = array(
|
|
'id' => $event['id'],
|
|
'name' => $event['name'],
|
|
'trainer_name' => $event['trainer_name'],
|
|
'trainer_email' => $event['trainer_email'],
|
|
'date' => $event_date,
|
|
'time' => $event_time,
|
|
'status' => ucfirst( $event['status'] ),
|
|
'status_class' => $status_class,
|
|
'capacity' => $event['capacity'],
|
|
'sold' => $event['sold'],
|
|
'revenue' => '$' . number_format( $event['revenue'], 2 ),
|
|
'link' => $event['link'],
|
|
'trainer_edit_link' => home_url( '/trainer/events/edit/' . $event['id'] . '/' ),
|
|
'is_upcoming' => $event['start_date_ts'] > time()
|
|
);
|
|
}
|
|
|
|
return $formatted;
|
|
}
|
|
|
|
/**
|
|
* Format events for calendar display
|
|
*/
|
|
private function format_events_for_calendar( $events ) {
|
|
$formatted = array();
|
|
|
|
foreach ( $events as $event ) {
|
|
$formatted[] = array(
|
|
'id' => $event['id'],
|
|
'title' => $event['name'],
|
|
'trainer' => $event['trainer_name'],
|
|
'start' => date( 'Y-m-d', $event['start_date_ts'] ),
|
|
'url' => $event['link'],
|
|
'className' => 'hvac-calendar-event hvac-status-' . $event['status'],
|
|
'extendedProps' => array(
|
|
'trainer_name' => $event['trainer_name'],
|
|
'capacity' => $event['capacity'],
|
|
'sold' => $event['sold'],
|
|
'revenue' => $event['revenue'],
|
|
'status' => $event['status']
|
|
)
|
|
);
|
|
}
|
|
|
|
return $formatted;
|
|
}
|
|
|
|
/**
|
|
* Check if current user can view events
|
|
*/
|
|
private function can_view_events() {
|
|
$user = wp_get_current_user();
|
|
return in_array( 'hvac_master_trainer', $user->roles ) || current_user_can( 'manage_options' );
|
|
}
|
|
|
|
/**
|
|
* Get events summary for quick stats
|
|
*/
|
|
public function get_events_summary() {
|
|
if ( ! $this->dashboard_data ) {
|
|
return array();
|
|
}
|
|
|
|
return array(
|
|
'total_events' => $this->dashboard_data->get_total_events_count(),
|
|
'upcoming_events' => $this->dashboard_data->get_upcoming_events_count(),
|
|
'past_events' => $this->dashboard_data->get_past_events_count(),
|
|
'total_revenue' => $this->dashboard_data->get_total_revenue(),
|
|
'total_tickets' => $this->dashboard_data->get_total_tickets_sold()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Clear events cache
|
|
*/
|
|
public function clear_cache() {
|
|
if ( method_exists( 'HVAC_Master_Dashboard_Data', 'clear_cache' ) ) {
|
|
HVAC_Master_Dashboard_Data::clear_cache();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get filtered events count for display
|
|
*/
|
|
public function get_filtered_events_count( $args = array() ) {
|
|
if ( ! $this->dashboard_data ) {
|
|
return 0;
|
|
}
|
|
|
|
$events_data = $this->dashboard_data->get_events_table_data( array_merge( $args, array( 'per_page' => 1 ) ) );
|
|
return $events_data['pagination']['total_items'];
|
|
}
|
|
}
|
|
|
|
// Initialize the class
|
|
HVAC_Master_Events_Overview::instance();
|