## Major Enhancements ### 🏗️ Architecture & Infrastructure - Implement comprehensive Docker testing infrastructure with hermetic environment - Add Forgejo Actions CI/CD pipeline for automated deployments - Create Page Object Model (POM) testing architecture reducing test duplication by 90% - Establish security-first development patterns with input validation and output escaping ### 🧪 Testing Framework Modernization - Migrate 146+ tests from 80 duplicate files to centralized architecture - Add comprehensive E2E test suites for all user roles and workflows - Implement WordPress error detection with automatic site health monitoring - Create robust browser lifecycle management with proper cleanup ### 📚 Documentation & Guides - Add comprehensive development best practices guide - Create detailed administrator setup documentation - Establish user guides for trainers and master trainers - Document security incident reports and migration guides ### 🔧 Core Plugin Features - Enhance trainer profile management with certification system - Improve find trainer functionality with advanced filtering - Strengthen master trainer area with content management - Add comprehensive venue and organizer management ### 🛡️ Security & Reliability - Implement security-first patterns throughout codebase - Add comprehensive input validation and output escaping - Create secure credential management system - Establish proper WordPress role-based access control ### 🎯 WordPress Integration - Strengthen singleton pattern implementation across all classes - Enhance template hierarchy with proper WordPress integration - Improve page manager with hierarchical URL structure - Add comprehensive shortcode and menu system ### 🔍 Developer Experience - Add extensive debugging and troubleshooting tools - Create comprehensive test data seeding scripts - Implement proper error handling and logging - Establish consistent code patterns and standards ### 📊 Performance & Optimization - Optimize database queries and caching strategies - Improve asset loading and script management - Enhance template rendering performance - Streamline user experience across all interfaces 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			391 lines
		
	
	
		
			No EOL
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
		
			No EOL
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Master Trainers Overview
 | |
|  *
 | |
|  * Provides comprehensive trainers overview for master trainers with read-only access
 | |
|  * to all trainer profiles with filtering and statistics.
 | |
|  *
 | |
|  * @package    HVAC Community Events
 | |
|  * @subpackage Includes
 | |
|  * @author     Ben Reed
 | |
|  * @version    1.0.0
 | |
|  */
 | |
| 
 | |
| // Exit if accessed directly.
 | |
| if ( ! defined( 'ABSPATH' ) ) {
 | |
| 	exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Class HVAC_Master_Trainers_Overview
 | |
|  *
 | |
|  * Handles comprehensive read-only trainers overview for master trainers
 | |
|  */
 | |
| class HVAC_Master_Trainers_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 trainers overview
 | |
| 		add_action( 'wp_ajax_hvac_master_trainers_filter', array( $this, 'ajax_filter_trainers' ) );
 | |
| 		add_action( 'wp_ajax_hvac_master_trainers_stats', array( $this, 'ajax_get_stats' ) );
 | |
| 		
 | |
| 		// Shortcode for embedding trainers overview
 | |
| 		add_shortcode( 'hvac_master_trainers', array( $this, 'render_trainers_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_trainers' ) ) {
 | |
| 			function hvac_render_master_trainers() {
 | |
| 				$overview = HVAC_Master_Trainers_Overview::instance();
 | |
| 				return $overview->render_trainers_overview();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Render the complete trainers overview
 | |
| 	 */
 | |
| 	public function render_trainers_overview( $atts = array() ) {
 | |
| 		// Security check
 | |
| 		if ( ! $this->can_view_trainers() ) {
 | |
| 			return '<div class="hvac-notice hvac-notice-error">You do not have permission to view trainers overview.</div>';
 | |
| 		}
 | |
| 
 | |
| 		ob_start();
 | |
| 		?>
 | |
| 		<div class="hvac-master-trainers-overview" id="hvac-master-trainers-overview">
 | |
| 			
 | |
| 			<!-- Stats Section -->
 | |
| 			<div class="hvac-trainers-stats-section">
 | |
| 				<div class="hvac-stats-loading" id="hvac-stats-loading">
 | |
| 					<div class="hvac-spinner"></div>
 | |
| 					<p>Loading trainer statistics...</p>
 | |
| 				</div>
 | |
| 				<div class="hvac-stats-tiles" id="hvac-stats-tiles" style="display: none;">
 | |
| 					<!-- Stats tiles will be loaded via AJAX -->
 | |
| 				</div>
 | |
| 			</div>
 | |
| 
 | |
| 			<!-- Filters Section -->
 | |
| 			<div class="hvac-trainers-filters-section">
 | |
| 				<form class="hvac-trainers-filters" id="hvac-trainers-filters">
 | |
| 					<div class="hvac-filters-row">
 | |
| 						
 | |
| 						<!-- Status Filter -->
 | |
| 						<div class="hvac-filter-group">
 | |
| 							<label for="filter-status">Status:</label>
 | |
| 							<select id="filter-status" name="status">
 | |
| 								<option value="all">All Trainers</option>
 | |
| 								<option value="active">Active</option>
 | |
| 								<option value="inactive">Inactive</option>
 | |
| 								<option value="pending">Pending</option>
 | |
| 								<option value="disabled">Disabled</option>
 | |
| 							</select>
 | |
| 						</div>
 | |
| 
 | |
| 						<!-- Region Filter -->
 | |
| 						<div class="hvac-filter-group">
 | |
| 							<label for="filter-region">Region:</label>
 | |
| 							<select id="filter-region" name="region">
 | |
| 								<option value="">All Regions</option>
 | |
| 								<?php echo $this->get_regions_options(); ?>
 | |
| 							</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="Trainer name or email..." />
 | |
| 						</div>
 | |
| 
 | |
| 						<!-- Filter Actions -->
 | |
| 						<div class="hvac-filter-actions">
 | |
| 							<button type="submit" class="hvac-btn hvac-btn-primary">Filter Trainers</button>
 | |
| 							<button type="button" class="hvac-btn hvac-btn-secondary" id="clear-filters">Clear All</button>
 | |
| 						</div>
 | |
| 
 | |
| 					</div>
 | |
| 				</form>
 | |
| 			</div>
 | |
| 
 | |
| 			<!-- Trainers Content Section -->
 | |
| 			<div class="hvac-trainers-content">
 | |
| 				<div class="hvac-trainers-table-view" id="hvac-trainers-table-view">
 | |
| 					<div class="hvac-trainers-loading" id="hvac-trainers-loading">
 | |
| 						<div class="hvac-spinner"></div>
 | |
| 						<p>Loading trainers...</p>
 | |
| 					</div>
 | |
| 					
 | |
| 					<div class="hvac-trainers-table-container" id="hvac-trainers-table-container" style="display: none;">
 | |
| 						<!-- Trainers table will be loaded via AJAX -->
 | |
| 					</div>
 | |
| 				</div>
 | |
| 			</div>
 | |
| 
 | |
| 		</div>
 | |
| 
 | |
| 		<!-- Hidden nonce for AJAX -->
 | |
| 		<?php wp_nonce_field( 'hvac_master_trainers_nonce', 'hvac_master_trainers_nonce', false ); ?>
 | |
| 
 | |
| 		<script type="text/javascript">
 | |
| 		// Pass AJAX URL and nonce to JavaScript
 | |
| 		var hvac_master_trainers_ajax = {
 | |
| 			ajax_url: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
 | |
| 			nonce: '<?php echo esc_js( wp_create_nonce( 'hvac_master_trainers_nonce' ) ); ?>'
 | |
| 		};
 | |
| 		</script>
 | |
| 		<?php
 | |
| 		
 | |
| 		return ob_get_clean();
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get regions options for filter dropdown
 | |
| 	 */
 | |
| 	private function get_regions_options() {
 | |
| 		if ( ! $this->dashboard_data ) {
 | |
| 			return '';
 | |
| 		}
 | |
| 
 | |
| 		$trainer_stats = $this->dashboard_data->get_trainer_statistics();
 | |
| 		$regions = array();
 | |
| 		$options = '';
 | |
| 
 | |
| 		if ( ! empty( $trainer_stats['trainer_data'] ) ) {
 | |
| 			foreach ( $trainer_stats['trainer_data'] as $trainer ) {
 | |
| 				$user_meta = get_user_meta( $trainer->trainer_id );
 | |
| 				$state = isset( $user_meta['billing_state'] ) ? $user_meta['billing_state'][0] : '';
 | |
| 				$country = isset( $user_meta['billing_country'] ) ? $user_meta['billing_country'][0] : '';
 | |
| 				
 | |
| 				if ( ! empty( $state ) && ! in_array( $state, $regions ) ) {
 | |
| 					$regions[] = $state;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		sort( $regions );
 | |
| 		
 | |
| 		foreach ( $regions as $region ) {
 | |
| 			$options .= sprintf(
 | |
| 				'<option value="%s">%s</option>',
 | |
| 				esc_attr( $region ),
 | |
| 				esc_html( $region )
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		return $options;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * AJAX handler for filtering trainers
 | |
| 	 */
 | |
| 	public function ajax_filter_trainers() {
 | |
| 		// Verify nonce
 | |
| 		if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_trainers_nonce' ) ) {
 | |
| 			wp_send_json_error( array( 'message' => 'Security check failed' ) );
 | |
| 		}
 | |
| 
 | |
| 		// Check permissions
 | |
| 		if ( ! $this->can_view_trainers() ) {
 | |
| 			wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
 | |
| 		}
 | |
| 
 | |
| 		// Get filter parameters
 | |
| 		$args = array(
 | |
| 			'status' => isset( $_POST['status'] ) ? sanitize_text_field( $_POST['status'] ) : 'all',
 | |
| 			'region' => isset( $_POST['region'] ) ? sanitize_text_field( $_POST['region'] ) : '',
 | |
| 			'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'] ) : 'display_name',
 | |
| 			'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'ASC',
 | |
| 		);
 | |
| 
 | |
| 		// Get trainers data
 | |
| 		if ( $this->dashboard_data ) {
 | |
| 			$trainer_stats = $this->dashboard_data->get_trainer_statistics();
 | |
| 			
 | |
| 			// Format trainers for display
 | |
| 			$formatted_trainers = $this->format_trainers_for_display( $trainer_stats['trainer_data'], $args );
 | |
| 			
 | |
| 			wp_send_json_success( array(
 | |
| 				'trainers' => $formatted_trainers,
 | |
| 				'total_found' => count( $formatted_trainers )
 | |
| 			) );
 | |
| 		}
 | |
| 
 | |
| 		wp_send_json_error( array( 'message' => 'Unable to load trainers data' ) );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * AJAX handler for getting trainer stats
 | |
| 	 */
 | |
| 	public function ajax_get_stats() {
 | |
| 		// Verify nonce
 | |
| 		if ( ! wp_verify_nonce( $_POST['nonce'], 'hvac_master_trainers_nonce' ) ) {
 | |
| 			wp_send_json_error( array( 'message' => 'Security check failed' ) );
 | |
| 		}
 | |
| 
 | |
| 		// Check permissions
 | |
| 		if ( ! $this->can_view_trainers() ) {
 | |
| 			wp_send_json_error( array( 'message' => 'Insufficient permissions' ) );
 | |
| 		}
 | |
| 
 | |
| 		if ( $this->dashboard_data ) {
 | |
| 			$trainer_stats = $this->dashboard_data->get_trainer_statistics();
 | |
| 			
 | |
| 			$stats = array(
 | |
| 				'total_trainers' => $trainer_stats['total_trainers'],
 | |
| 				'active_trainers' => $this->count_trainers_by_status( 'active' ),
 | |
| 				'pending_trainers' => $this->count_trainers_by_status( 'pending' ),
 | |
| 				'total_events' => $trainer_stats['total_events'],
 | |
| 				'total_revenue' => $trainer_stats['total_revenue']
 | |
| 			);
 | |
| 
 | |
| 			wp_send_json_success( $stats );
 | |
| 		}
 | |
| 
 | |
| 		wp_send_json_error( array( 'message' => 'Unable to load trainer stats' ) );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Format trainers for table display
 | |
| 	 */
 | |
| 	private function format_trainers_for_display( $trainers, $filters = array() ) {
 | |
| 		$formatted = array();
 | |
| 
 | |
| 		foreach ( $trainers as $trainer ) {
 | |
| 			$user_meta = get_user_meta( $trainer->trainer_id );
 | |
| 			$user_data = get_userdata( $trainer->trainer_id );
 | |
| 			
 | |
| 			if ( ! $user_data ) continue;
 | |
| 			
 | |
| 			// Apply filters
 | |
| 			if ( ! empty( $filters['search'] ) ) {
 | |
| 				$search_term = strtolower( $filters['search'] );
 | |
| 				$search_fields = strtolower( $trainer->display_name . ' ' . $user_data->user_email );
 | |
| 				if ( strpos( $search_fields, $search_term ) === false ) {
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			if ( ! empty( $filters['region'] ) ) {
 | |
| 				$user_state = isset( $user_meta['billing_state'] ) ? $user_meta['billing_state'][0] : '';
 | |
| 				if ( $user_state !== $filters['region'] ) {
 | |
| 					continue;
 | |
| 				}
 | |
| 			}
 | |
| 			
 | |
| 			$status = isset( $user_meta['hvac_trainer_status'] ) ? $user_meta['hvac_trainer_status'][0] : 'active';
 | |
| 			if ( $filters['status'] !== 'all' && $status !== $filters['status'] ) {
 | |
| 				continue;
 | |
| 			}
 | |
| 
 | |
| 			$formatted[] = array(
 | |
| 				'id' => $trainer->trainer_id,
 | |
| 				'name' => $trainer->display_name,
 | |
| 				'email' => $user_data->user_email,
 | |
| 				'status' => ucfirst( $status ),
 | |
| 				'status_class' => 'hvac-status-' . esc_attr( $status ),
 | |
| 				'total_events' => $trainer->total_events,
 | |
| 				'location' => $this->get_trainer_location( $user_meta ),
 | |
| 				'registered' => date( 'M j, Y', strtotime( $user_data->user_registered ) ),
 | |
| 				'profile_link' => home_url( '/master-trainer/trainer-profile/' . $trainer->trainer_id . '/' ),
 | |
| 				'edit_link' => home_url( '/master-trainer/edit-trainer/' . $trainer->trainer_id . '/' )
 | |
| 			);
 | |
| 		}
 | |
| 
 | |
| 		return $formatted;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get trainer location from user meta
 | |
| 	 */
 | |
| 	private function get_trainer_location( $user_meta ) {
 | |
| 		$city = isset( $user_meta['billing_city'] ) ? $user_meta['billing_city'][0] : '';
 | |
| 		$state = isset( $user_meta['billing_state'] ) ? $user_meta['billing_state'][0] : '';
 | |
| 		$country = isset( $user_meta['billing_country'] ) ? $user_meta['billing_country'][0] : '';
 | |
| 		
 | |
| 		$location_parts = array_filter( array( $city, $state, $country ) );
 | |
| 		return implode( ', ', $location_parts );
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Count trainers by status
 | |
| 	 */
 | |
| 	private function count_trainers_by_status( $status ) {
 | |
| 		$users = get_users( array(
 | |
| 			'role' => 'hvac_trainer',
 | |
| 			'meta_key' => 'hvac_trainer_status',
 | |
| 			'meta_value' => $status,
 | |
| 			'count_total' => true
 | |
| 		) );
 | |
| 		
 | |
| 		return is_array( $users ) ? count( $users ) : 0;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check if current user can view trainers
 | |
| 	 */
 | |
| 	private function can_view_trainers() {
 | |
| 		$user = wp_get_current_user();
 | |
| 		return in_array( 'hvac_master_trainer', $user->roles ) || current_user_can( 'manage_options' );
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Initialize the class
 | |
| HVAC_Master_Trainers_Overview::instance();
 |