diff --git a/assets/images/marker-trainer.svg b/assets/images/marker-trainer.svg index 507fdbd5..0609cfad 100644 Binary files a/assets/images/marker-trainer.svg and b/assets/images/marker-trainer.svg differ diff --git a/assets/images/marker-venue.svg b/assets/images/marker-venue.svg index 4ca8337b..ab902270 100644 Binary files a/assets/images/marker-venue.svg and b/assets/images/marker-venue.svg differ diff --git a/hvac-community-events.php b/hvac-community-events.php index 2f3341e2..344464eb 100644 --- a/hvac-community-events.php +++ b/hvac-community-events.php @@ -3,7 +3,7 @@ * Plugin Name: HVAC Community Events * Plugin URI: https://upskillhvac.com * Description: Custom plugin for HVAC trainer event management system - * Version: 2.1.7 + * Version: 2.2.0 * Author: Upskill HVAC * Author URI: https://upskillhvac.com * License: GPL-2.0+ @@ -17,6 +17,50 @@ if (!defined('ABSPATH')) { exit; } +/** + * Staging Email Filter + * Prevents emails from being sent to anyone except allowed addresses on staging. + * This protects real users from receiving test emails during development. + */ +function hvac_is_staging_environment() { + $host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : ''; + $staging_indicators = array( 'staging', 'upskill-staging', 'localhost', '127.0.0.1' ); + + foreach ( $staging_indicators as $indicator ) { + if ( stripos( $host, $indicator ) !== false ) { + return true; + } + } + return false; +} + +function hvac_staging_email_filter( $args ) { + if ( ! hvac_is_staging_environment() ) { + return $args; + } + + $allowed_emails = array( 'ben@tealmaker.com', 'ben@measurequick.com' ); + $to = $args['to']; + + // Extract email address + $email_address = is_array( $to ) ? ( isset( $to[0] ) ? $to[0] : '' ) : $to; + if ( preg_match( '/<([^>]+)>/', $email_address, $matches ) ) { + $email_address = $matches[1]; + } + $email_address = trim( strtolower( $email_address ) ); + + if ( ! in_array( $email_address, $allowed_emails, true ) ) { + error_log( sprintf( '[HVAC Staging] Blocked email to: %s | Subject: %s', + is_array( $to ) ? implode( ', ', $to ) : $to, $args['subject'] ) ); + $args['to'] = ''; + return $args; + } + + $args['subject'] = '[STAGING] ' . $args['subject']; + return $args; +} +add_filter( 'wp_mail', 'hvac_staging_email_filter', 1 ); + // Load the main plugin class require_once plugin_dir_path(__FILE__) . 'includes/class-hvac-plugin.php'; diff --git a/includes/class-hvac-ajax-handlers.php b/includes/class-hvac-ajax-handlers.php index c5b402fc..409a515f 100644 --- a/includes/class-hvac-ajax-handlers.php +++ b/includes/class-hvac-ajax-handlers.php @@ -1046,6 +1046,15 @@ class HVAC_Ajax_Handlers { return; } + // Verify reCAPTCHA + if (class_exists('HVAC_Recaptcha')) { + $recaptcha_response = sanitize_text_field($_POST['g-recaptcha-response'] ?? ''); + if (!HVAC_Recaptcha::instance()->verify_response($recaptcha_response)) { + wp_send_json_error(['message' => 'CAPTCHA verification failed. Please try again.'], 400); + return; + } + } + // Rate limiting - max 5 submissions per IP per hour $ip = $this->get_client_ip(); $rate_key = 'hvac_contact_rate_' . md5($ip); @@ -1202,6 +1211,15 @@ class HVAC_Ajax_Handlers { return; } + // Verify reCAPTCHA + if (class_exists('HVAC_Recaptcha')) { + $recaptcha_response = sanitize_text_field($_POST['g-recaptcha-response'] ?? ''); + if (!HVAC_Recaptcha::instance()->verify_response($recaptcha_response)) { + wp_send_json_error(['message' => 'CAPTCHA verification failed. Please try again.'], 400); + return; + } + } + // Rate limiting - max 5 submissions per IP per hour $ip = $this->get_client_ip(); $rate_key = 'hvac_venue_contact_rate_' . md5($ip); diff --git a/includes/class-hvac-master-trainers-overview.php b/includes/class-hvac-master-trainers-overview.php index e561dcec..9f623593 100644 --- a/includes/class-hvac-master-trainers-overview.php +++ b/includes/class-hvac-master-trainers-overview.php @@ -291,6 +291,8 @@ class HVAC_Master_Trainers_Overview { /** * AJAX handler for filtering trainers + * + * Uses get_trainers_table_data() from dashboard data class for reliable trainer listing */ public function ajax_filter_trainers() { // Verify nonce @@ -306,7 +308,6 @@ class HVAC_Master_Trainers_Overview { // 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, @@ -314,12 +315,21 @@ class HVAC_Master_Trainers_Overview { 'order' => isset( $_POST['order'] ) ? sanitize_text_field( $_POST['order'] ) : 'ASC', ); - // Get trainers data + // Use the reliable get_trainers_table_data method (same as Master Dashboard) if ( $this->dashboard_data ) { - $trainer_stats = $this->dashboard_data->get_trainer_statistics(); + $trainer_data = $this->dashboard_data->get_trainers_table_data( $args ); + $trainers = $trainer_data['trainers']; - // Format trainers for display - $formatted_trainers = $this->format_trainers_for_display( $trainer_stats['trainer_data'], $args ); + // Apply region filter if specified (not handled by get_trainers_table_data) + $region_filter = isset( $_POST['region'] ) ? sanitize_text_field( $_POST['region'] ) : ''; + if ( ! empty( $region_filter ) ) { + $trainers = array_filter( $trainers, function( $trainer ) use ( $region_filter ) { + $user_meta = get_user_meta( $trainer['id'] ); + $user_state = isset( $user_meta['billing_state'][0] ) ? $user_meta['billing_state'][0] : ''; + return $user_state === $region_filter; + } ); + $trainers = array_values( $trainers ); // Re-index array + } // Generate HTML table ob_start(); @@ -329,20 +339,20 @@ class HVAC_Master_Trainers_Overview {