Add venue taxonomies and filter /find-training to show only approved labs: - Create venue_type, venue_equipment, venue_amenities taxonomies - Filter venue markers by mq-approved-lab taxonomy term - Add equipment and amenities badges to venue modal - Add venue contact form with AJAX handler and email notification - Include POC (Point of Contact) meta for each training lab 9 approved training labs configured: - Fast Track Learning Lab, Progressive Training Lab, NAVAC Technical Training Center - Stevens Equipment Phoenix/Johnstown, San Jacinto College, Johnstone Supply - TruTech Tools Training Center (new), Auer Steel & Heating Supply (new) Note: Venues not displaying on map - to be debugged next session. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1375 lines
No EOL
48 KiB
PHP
1375 lines
No EOL
48 KiB
PHP
<?php
|
|
/**
|
|
* HVAC AJAX Handlers
|
|
*
|
|
* Implements missing AJAX endpoints with comprehensive security
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Ajax_Handlers
|
|
*
|
|
* Handles AJAX requests for trainer stats and announcement management
|
|
*/
|
|
class HVAC_Ajax_Handlers {
|
|
|
|
/**
|
|
* Instance of this class
|
|
*
|
|
* @var HVAC_Ajax_Handlers
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Get instance of this class
|
|
*
|
|
* @return HVAC_Ajax_Handlers
|
|
*/
|
|
public static function get_instance() {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
private function __construct() {
|
|
$this->init_hooks();
|
|
$this->init_cache_invalidation_hooks();
|
|
}
|
|
|
|
/**
|
|
* Initialize AJAX hooks
|
|
*/
|
|
private function init_hooks() {
|
|
// Trainer stats endpoint
|
|
add_action('wp_ajax_hvac_get_trainer_stats', array($this, 'get_trainer_stats'));
|
|
add_action('wp_ajax_nopriv_hvac_get_trainer_stats', array($this, 'unauthorized_access'));
|
|
|
|
// Announcement management endpoint
|
|
add_action('wp_ajax_hvac_manage_announcement', array($this, 'manage_announcement'));
|
|
add_action('wp_ajax_nopriv_hvac_manage_announcement', array($this, 'unauthorized_access'));
|
|
|
|
// Enhanced approval endpoint (wrapper for existing)
|
|
add_action('wp_ajax_hvac_approve_trainer_v2', array($this, 'approve_trainer_secure'));
|
|
add_action('wp_ajax_nopriv_hvac_approve_trainer_v2', array($this, 'unauthorized_access'));
|
|
|
|
// Password reset endpoint for master trainers
|
|
add_action('wp_ajax_hvac_send_password_reset', array($this, 'send_password_reset'));
|
|
add_action('wp_ajax_nopriv_hvac_send_password_reset', array($this, 'unauthorized_access'));
|
|
|
|
// Contact trainer form (Find Training page)
|
|
add_action('wp_ajax_hvac_submit_contact_form', array($this, 'submit_trainer_contact_form'));
|
|
add_action('wp_ajax_nopriv_hvac_submit_contact_form', array($this, 'submit_trainer_contact_form'));
|
|
|
|
// Contact venue form (Find Training page - Approved Labs)
|
|
add_action('wp_ajax_hvac_submit_venue_contact', array($this, 'submit_venue_contact_form'));
|
|
add_action('wp_ajax_nopriv_hvac_submit_venue_contact', array($this, 'submit_venue_contact_form'));
|
|
}
|
|
|
|
/**
|
|
* Get trainer statistics
|
|
*
|
|
* Provides statistics for trainers with proper security validation
|
|
*/
|
|
public function get_trainer_stats() {
|
|
// Security verification
|
|
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
|
'get_trainer_stats',
|
|
HVAC_Ajax_Security::NONCE_GENERAL,
|
|
array('hvac_master_trainer', 'view_master_dashboard', 'manage_options'),
|
|
false
|
|
);
|
|
|
|
if (is_wp_error($security_check)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $security_check->get_error_message(),
|
|
'code' => $security_check->get_error_code()
|
|
),
|
|
$security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Input validation
|
|
$input_rules = array(
|
|
'trainer_id' => array(
|
|
'type' => 'int',
|
|
'required' => false,
|
|
'min' => 1
|
|
),
|
|
'date_from' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
if (!empty($value) && !strtotime($value)) {
|
|
return new WP_Error('invalid_date', 'Invalid date format');
|
|
}
|
|
return true;
|
|
}
|
|
),
|
|
'date_to' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
if (!empty($value) && !strtotime($value)) {
|
|
return new WP_Error('invalid_date', 'Invalid date format');
|
|
}
|
|
return true;
|
|
}
|
|
),
|
|
'stat_type' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
$valid_types = array('events', 'attendees', 'revenue', 'ratings', 'all');
|
|
if (!empty($value) && !in_array($value, $valid_types)) {
|
|
return new WP_Error('invalid_type', 'Invalid statistics type');
|
|
}
|
|
return true;
|
|
}
|
|
)
|
|
);
|
|
|
|
$params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($params)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $params->get_error_message(),
|
|
'errors' => $params->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Set defaults
|
|
$trainer_id = isset($params['trainer_id']) ? $params['trainer_id'] : null;
|
|
$date_from = isset($params['date_from']) ? $params['date_from'] : date('Y-m-d', strtotime('-30 days'));
|
|
$date_to = isset($params['date_to']) ? $params['date_to'] : date('Y-m-d');
|
|
$stat_type = isset($params['stat_type']) ? $params['stat_type'] : 'all';
|
|
|
|
// Get statistics
|
|
$stats = $this->compile_trainer_stats($trainer_id, $date_from, $date_to, $stat_type);
|
|
|
|
if (is_wp_error($stats)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $stats->get_error_message()
|
|
),
|
|
500
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Log successful stats retrieval
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Trainer stats retrieved', 'AJAX', array(
|
|
'user_id' => get_current_user_id(),
|
|
'trainer_id' => $trainer_id,
|
|
'date_range' => $date_from . ' to ' . $date_to
|
|
));
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'stats' => $stats,
|
|
'parameters' => array(
|
|
'trainer_id' => $trainer_id,
|
|
'date_from' => $date_from,
|
|
'date_to' => $date_to,
|
|
'stat_type' => $stat_type
|
|
),
|
|
'generated_at' => current_time('mysql')
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Compile trainer statistics
|
|
*
|
|
* @param int|null $trainer_id Specific trainer or all
|
|
* @param string $date_from Start date
|
|
* @param string $date_to End date
|
|
* @param string $stat_type Type of statistics
|
|
* @return array|WP_Error
|
|
*/
|
|
private function compile_trainer_stats($trainer_id, $date_from, $date_to, $stat_type) {
|
|
// Generate cache key based on parameters (WordPress Best Practice)
|
|
$cache_key = 'hvac_trainer_stats_' . md5(
|
|
$trainer_id . $date_from . $date_to . $stat_type . get_current_user_id()
|
|
);
|
|
|
|
// Try to get cached result (WordPress Transient API)
|
|
$cached_stats = get_transient($cache_key);
|
|
if (false !== $cached_stats) {
|
|
// Log cache hit for performance monitoring
|
|
error_log("[HVAC Performance] Cache hit for stats key: {$cache_key}");
|
|
return $cached_stats;
|
|
}
|
|
|
|
// Execute heavy query logic if not cached
|
|
$stats = $this->execute_heavy_queries($trainer_id, $date_from, $date_to, $stat_type);
|
|
|
|
if (!is_wp_error($stats)) {
|
|
// Cache for 5 minutes (300 seconds) - WordPress Best Practice
|
|
set_transient($cache_key, $stats, 300);
|
|
error_log("[HVAC Performance] Cached stats for key: {$cache_key}");
|
|
}
|
|
|
|
return $stats;
|
|
}
|
|
|
|
/**
|
|
* Execute heavy database queries for trainer statistics
|
|
*
|
|
* Separated from caching logic for better maintainability.
|
|
* Contains the original heavy query logic.
|
|
*
|
|
* @param int $trainer_id Trainer ID
|
|
* @param string $date_from Start date
|
|
* @param string $date_to End date
|
|
* @param string $stat_type Type of statistics
|
|
* @return array|WP_Error
|
|
*/
|
|
private function execute_heavy_queries($trainer_id, $date_from, $date_to, $stat_type) {
|
|
global $wpdb;
|
|
|
|
$stats = array();
|
|
|
|
try {
|
|
// Base query conditions
|
|
$where_conditions = array("1=1");
|
|
$query_params = array();
|
|
|
|
if ($trainer_id) {
|
|
$where_conditions[] = "trainer_id = %d";
|
|
$query_params[] = $trainer_id;
|
|
}
|
|
|
|
// Events statistics
|
|
if (in_array($stat_type, array('events', 'all'))) {
|
|
$events_query = "
|
|
SELECT
|
|
COUNT(DISTINCT p.ID) as total_events,
|
|
SUM(CASE WHEN p.post_status = 'publish' THEN 1 ELSE 0 END) as published_events,
|
|
SUM(CASE WHEN p.post_status = 'draft' THEN 1 ELSE 0 END) as draft_events
|
|
FROM {$wpdb->posts} p
|
|
INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE p.post_type = 'tribe_events'
|
|
AND pm.meta_key = '_EventStartDate'
|
|
AND pm.meta_value BETWEEN %s AND %s
|
|
";
|
|
|
|
if ($trainer_id) {
|
|
$events_query .= " AND p.post_author = %d";
|
|
$events_result = $wpdb->get_row($wpdb->prepare($events_query, $date_from, $date_to, $trainer_id));
|
|
} else {
|
|
$events_result = $wpdb->get_row($wpdb->prepare($events_query, $date_from, $date_to));
|
|
}
|
|
|
|
$stats['events'] = array(
|
|
'total' => intval($events_result->total_events),
|
|
'published' => intval($events_result->published_events),
|
|
'draft' => intval($events_result->draft_events)
|
|
);
|
|
}
|
|
|
|
// Attendees statistics
|
|
if (in_array($stat_type, array('attendees', 'all'))) {
|
|
// This would integrate with your attendee tracking system
|
|
$stats['attendees'] = array(
|
|
'total' => 0,
|
|
'unique' => 0,
|
|
'average_per_event' => 0
|
|
);
|
|
|
|
// If you have attendee data, query it here
|
|
// Example: Query from custom attendee table or meta
|
|
}
|
|
|
|
// Trainer summary
|
|
if (!$trainer_id) {
|
|
$trainer_stats = $wpdb->get_row("
|
|
SELECT
|
|
COUNT(DISTINCT u.ID) as total_trainers,
|
|
SUM(CASE WHEN um.meta_value = 'approved' THEN 1 ELSE 0 END) as approved_trainers,
|
|
SUM(CASE WHEN um.meta_value = 'pending' THEN 1 ELSE 0 END) as pending_trainers
|
|
FROM {$wpdb->users} u
|
|
INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id
|
|
WHERE um.meta_key = 'hvac_trainer_status'
|
|
");
|
|
|
|
$stats['trainers'] = array(
|
|
'total' => intval($trainer_stats->total_trainers),
|
|
'approved' => intval($trainer_stats->approved_trainers),
|
|
'pending' => intval($trainer_stats->pending_trainers)
|
|
);
|
|
}
|
|
|
|
// Add metadata
|
|
$stats['meta'] = array(
|
|
'date_from' => $date_from,
|
|
'date_to' => $date_to,
|
|
'generated' => current_time('mysql')
|
|
);
|
|
|
|
return $stats;
|
|
|
|
} catch (Exception $e) {
|
|
return new WP_Error('stats_error', 'Failed to compile statistics: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manage announcements
|
|
*
|
|
* Unified endpoint for announcement CRUD operations
|
|
*/
|
|
public function manage_announcement() {
|
|
// Determine action
|
|
$action = isset($_POST['announcement_action']) ? sanitize_text_field($_POST['announcement_action']) : '';
|
|
|
|
if (empty($action)) {
|
|
wp_send_json_error(
|
|
array('message' => 'No action specified'),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Map actions to required capabilities
|
|
$action_capabilities = array(
|
|
'create' => array('hvac_master_trainer', 'edit_posts', 'manage_options'),
|
|
'update' => array('hvac_master_trainer', 'edit_posts', 'manage_options'),
|
|
'delete' => array('hvac_master_trainer', 'delete_posts', 'manage_options'),
|
|
'read' => array('hvac_trainer', 'hvac_master_trainer', 'read')
|
|
);
|
|
|
|
$required_caps = isset($action_capabilities[$action]) ? $action_capabilities[$action] : array('manage_options');
|
|
$is_sensitive = in_array($action, array('delete'));
|
|
|
|
// Security verification
|
|
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
|
'manage_announcement_' . $action,
|
|
HVAC_Ajax_Security::NONCE_ANNOUNCEMENT,
|
|
$required_caps,
|
|
$is_sensitive
|
|
);
|
|
|
|
if (is_wp_error($security_check)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $security_check->get_error_message(),
|
|
'code' => $security_check->get_error_code()
|
|
),
|
|
$security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Route to appropriate handler
|
|
switch ($action) {
|
|
case 'create':
|
|
$this->create_announcement();
|
|
break;
|
|
case 'update':
|
|
$this->update_announcement();
|
|
break;
|
|
case 'delete':
|
|
$this->delete_announcement();
|
|
break;
|
|
case 'read':
|
|
$this->read_announcement();
|
|
break;
|
|
default:
|
|
wp_send_json_error(
|
|
array('message' => 'Invalid action'),
|
|
400
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create announcement
|
|
*/
|
|
private function create_announcement() {
|
|
// Input validation rules
|
|
$input_rules = array(
|
|
'title' => array(
|
|
'type' => 'text',
|
|
'required' => true,
|
|
'max_length' => 200
|
|
),
|
|
'content' => array(
|
|
'type' => 'html',
|
|
'required' => true,
|
|
'allowed_html' => wp_kses_allowed_html('post')
|
|
),
|
|
'excerpt' => array(
|
|
'type' => 'textarea',
|
|
'required' => false,
|
|
'max_length' => 500
|
|
),
|
|
'status' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
$valid_statuses = array('publish', 'draft', 'private');
|
|
if (!empty($value) && !in_array($value, $valid_statuses)) {
|
|
return new WP_Error('invalid_status', 'Invalid announcement status');
|
|
}
|
|
return true;
|
|
}
|
|
),
|
|
'categories' => array(
|
|
'type' => 'array',
|
|
'required' => false
|
|
),
|
|
'tags' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'max_length' => 500
|
|
)
|
|
);
|
|
|
|
$data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($data)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $data->get_error_message(),
|
|
'errors' => $data->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Create announcement post
|
|
$post_data = array(
|
|
'post_title' => $data['title'],
|
|
'post_content' => $data['content'],
|
|
'post_excerpt' => isset($data['excerpt']) ? $data['excerpt'] : '',
|
|
'post_status' => isset($data['status']) ? $data['status'] : 'draft',
|
|
'post_type' => 'hvac_announcement',
|
|
'post_author' => get_current_user_id()
|
|
);
|
|
|
|
$post_id = wp_insert_post($post_data, true);
|
|
|
|
if (is_wp_error($post_id)) {
|
|
wp_send_json_error(
|
|
array('message' => 'Failed to create announcement: ' . $post_id->get_error_message()),
|
|
500
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Set categories and tags if provided
|
|
if (!empty($data['categories'])) {
|
|
wp_set_object_terms($post_id, $data['categories'], 'hvac_announcement_category');
|
|
}
|
|
|
|
if (!empty($data['tags'])) {
|
|
$tags = array_map('trim', explode(',', $data['tags']));
|
|
wp_set_object_terms($post_id, $tags, 'hvac_announcement_tag');
|
|
}
|
|
|
|
// Log creation
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Announcement created', 'AJAX', array(
|
|
'post_id' => $post_id,
|
|
'user_id' => get_current_user_id(),
|
|
'title' => $data['title']
|
|
));
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'message' => 'Announcement created successfully',
|
|
'post_id' => $post_id,
|
|
'redirect' => get_permalink($post_id)
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Update announcement
|
|
*/
|
|
private function update_announcement() {
|
|
// Input validation rules
|
|
$input_rules = array(
|
|
'post_id' => array(
|
|
'type' => 'int',
|
|
'required' => true,
|
|
'min' => 1
|
|
),
|
|
'title' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'max_length' => 200
|
|
),
|
|
'content' => array(
|
|
'type' => 'html',
|
|
'required' => false,
|
|
'allowed_html' => wp_kses_allowed_html('post')
|
|
),
|
|
'excerpt' => array(
|
|
'type' => 'textarea',
|
|
'required' => false,
|
|
'max_length' => 500
|
|
),
|
|
'status' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
$valid_statuses = array('publish', 'draft', 'private', 'trash');
|
|
if (!empty($value) && !in_array($value, $valid_statuses)) {
|
|
return new WP_Error('invalid_status', 'Invalid announcement status');
|
|
}
|
|
return true;
|
|
}
|
|
)
|
|
);
|
|
|
|
$data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($data)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $data->get_error_message(),
|
|
'errors' => $data->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Verify post exists and user can edit
|
|
$post = get_post($data['post_id']);
|
|
if (!$post || $post->post_type !== 'hvac_announcement') {
|
|
wp_send_json_error(
|
|
array('message' => 'Announcement not found'),
|
|
404
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!current_user_can('edit_post', $data['post_id'])) {
|
|
wp_send_json_error(
|
|
array('message' => 'You do not have permission to edit this announcement'),
|
|
403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Update post data
|
|
$update_data = array(
|
|
'ID' => $data['post_id']
|
|
);
|
|
|
|
if (isset($data['title'])) {
|
|
$update_data['post_title'] = $data['title'];
|
|
}
|
|
if (isset($data['content'])) {
|
|
$update_data['post_content'] = $data['content'];
|
|
}
|
|
if (isset($data['excerpt'])) {
|
|
$update_data['post_excerpt'] = $data['excerpt'];
|
|
}
|
|
if (isset($data['status'])) {
|
|
$update_data['post_status'] = $data['status'];
|
|
}
|
|
|
|
$result = wp_update_post($update_data, true);
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error(
|
|
array('message' => 'Failed to update announcement: ' . $result->get_error_message()),
|
|
500
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Log update
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Announcement updated', 'AJAX', array(
|
|
'post_id' => $data['post_id'],
|
|
'user_id' => get_current_user_id(),
|
|
'changes' => array_keys($update_data)
|
|
));
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'message' => 'Announcement updated successfully',
|
|
'post_id' => $data['post_id']
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Delete announcement
|
|
*/
|
|
private function delete_announcement() {
|
|
// Input validation
|
|
$input_rules = array(
|
|
'post_id' => array(
|
|
'type' => 'int',
|
|
'required' => true,
|
|
'min' => 1
|
|
),
|
|
'permanent' => array(
|
|
'type' => 'boolean',
|
|
'required' => false
|
|
)
|
|
);
|
|
|
|
$data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($data)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $data->get_error_message(),
|
|
'errors' => $data->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Verify post exists and user can delete
|
|
$post = get_post($data['post_id']);
|
|
if (!$post || $post->post_type !== 'hvac_announcement') {
|
|
wp_send_json_error(
|
|
array('message' => 'Announcement not found'),
|
|
404
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!current_user_can('delete_post', $data['post_id'])) {
|
|
wp_send_json_error(
|
|
array('message' => 'You do not have permission to delete this announcement'),
|
|
403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Delete or trash post
|
|
$permanent = isset($data['permanent']) ? $data['permanent'] : false;
|
|
|
|
if ($permanent) {
|
|
$result = wp_delete_post($data['post_id'], true);
|
|
} else {
|
|
$result = wp_trash_post($data['post_id']);
|
|
}
|
|
|
|
if (!$result) {
|
|
wp_send_json_error(
|
|
array('message' => 'Failed to delete announcement'),
|
|
500
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Log deletion
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::warning('Announcement deleted', 'AJAX', array(
|
|
'post_id' => $data['post_id'],
|
|
'user_id' => get_current_user_id(),
|
|
'permanent' => $permanent
|
|
));
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'message' => 'Announcement deleted successfully',
|
|
'post_id' => $data['post_id']
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Read announcement
|
|
*/
|
|
private function read_announcement() {
|
|
// Input validation
|
|
$input_rules = array(
|
|
'post_id' => array(
|
|
'type' => 'int',
|
|
'required' => true,
|
|
'min' => 1
|
|
)
|
|
);
|
|
|
|
$data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($data)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $data->get_error_message(),
|
|
'errors' => $data->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Get announcement
|
|
$post = get_post($data['post_id']);
|
|
if (!$post || $post->post_type !== 'hvac_announcement') {
|
|
wp_send_json_error(
|
|
array('message' => 'Announcement not found'),
|
|
404
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Check if user can read
|
|
if ($post->post_status === 'private' && !current_user_can('read_private_posts')) {
|
|
wp_send_json_error(
|
|
array('message' => 'You do not have permission to view this announcement'),
|
|
403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Prepare response
|
|
$response = array(
|
|
'id' => $post->ID,
|
|
'title' => $post->post_title,
|
|
'content' => apply_filters('the_content', $post->post_content),
|
|
'excerpt' => $post->post_excerpt,
|
|
'status' => $post->post_status,
|
|
'author' => get_the_author_meta('display_name', $post->post_author),
|
|
'date' => $post->post_date,
|
|
'modified' => $post->post_modified,
|
|
'categories' => wp_get_object_terms($post->ID, 'hvac_announcement_category', array('fields' => 'names')),
|
|
'tags' => wp_get_object_terms($post->ID, 'hvac_announcement_tag', array('fields' => 'names')),
|
|
'can_edit' => current_user_can('edit_post', $post->ID),
|
|
'can_delete' => current_user_can('delete_post', $post->ID)
|
|
);
|
|
|
|
wp_send_json_success($response);
|
|
}
|
|
|
|
/**
|
|
* Enhanced secure trainer approval
|
|
*
|
|
* Wrapper for existing approval with enhanced security
|
|
*/
|
|
public function approve_trainer_secure() {
|
|
// Enhanced security verification
|
|
$security_check = HVAC_Ajax_Security::verify_ajax_request(
|
|
'approve_trainer',
|
|
HVAC_Ajax_Security::NONCE_APPROVAL,
|
|
array('hvac_master_trainer', 'hvac_master_manage_approvals', 'manage_options'),
|
|
true // This is a sensitive action
|
|
);
|
|
|
|
if (is_wp_error($security_check)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $security_check->get_error_message(),
|
|
'code' => $security_check->get_error_code()
|
|
),
|
|
$security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Enhanced input validation
|
|
$input_rules = array(
|
|
'user_id' => array(
|
|
'type' => 'int',
|
|
'required' => true,
|
|
'min' => 1,
|
|
'validate' => function($value) {
|
|
// Verify user exists and is a trainer
|
|
$user = get_userdata($value);
|
|
if (!$user) {
|
|
return new WP_Error('invalid_user', 'User not found');
|
|
}
|
|
|
|
$is_trainer = in_array('hvac_trainer', $user->roles) ||
|
|
get_user_meta($value, 'hvac_trainer_status', true);
|
|
|
|
if (!$is_trainer) {
|
|
return new WP_Error('not_trainer', 'User is not a trainer');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
),
|
|
'reason' => array(
|
|
'type' => 'textarea',
|
|
'required' => false,
|
|
'max_length' => 1000
|
|
),
|
|
'action' => array(
|
|
'type' => 'text',
|
|
'required' => false,
|
|
'validate' => function($value) {
|
|
$valid_actions = array('approve', 'reject');
|
|
if (!empty($value) && !in_array($value, $valid_actions)) {
|
|
return new WP_Error('invalid_action', 'Invalid approval action');
|
|
}
|
|
return true;
|
|
}
|
|
)
|
|
);
|
|
|
|
$data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules);
|
|
|
|
if (is_wp_error($data)) {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => $data->get_error_message(),
|
|
'errors' => $data->get_error_data()
|
|
),
|
|
400
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Call existing approval handler if available, or implement here
|
|
if (class_exists('HVAC_Master_Pending_Approvals')) {
|
|
$approvals = HVAC_Master_Pending_Approvals::get_instance();
|
|
if (method_exists($approvals, 'ajax_approve_trainer')) {
|
|
// Set up the sanitized POST data for the existing handler
|
|
$_POST['user_id'] = $data['user_id'];
|
|
$_POST['reason'] = isset($data['reason']) ? $data['reason'] : '';
|
|
$_POST['nonce'] = $_REQUEST['nonce']; // Pass through the verified nonce
|
|
|
|
// Call existing handler
|
|
$approvals->ajax_approve_trainer();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Fallback implementation if existing handler not available
|
|
$this->process_trainer_approval($data);
|
|
}
|
|
|
|
/**
|
|
* Process trainer approval (fallback)
|
|
*
|
|
* @param array $data Sanitized input data
|
|
*/
|
|
private function process_trainer_approval($data) {
|
|
$user_id = $data['user_id'];
|
|
$reason = isset($data['reason']) ? $data['reason'] : '';
|
|
$action = isset($data['action']) ? $data['action'] : 'approve';
|
|
|
|
// Update trainer status
|
|
if ($action === 'approve') {
|
|
update_user_meta($user_id, 'hvac_trainer_status', 'approved');
|
|
update_user_meta($user_id, 'hvac_trainer_approved_date', current_time('mysql'));
|
|
update_user_meta($user_id, 'hvac_trainer_approved_by', get_current_user_id());
|
|
|
|
// Add trainer role if not present
|
|
$user = new WP_User($user_id);
|
|
if (!in_array('hvac_trainer', $user->roles)) {
|
|
$user->add_role('hvac_trainer');
|
|
}
|
|
|
|
$message = 'Trainer approved successfully';
|
|
} else {
|
|
update_user_meta($user_id, 'hvac_trainer_status', 'rejected');
|
|
update_user_meta($user_id, 'hvac_trainer_rejected_date', current_time('mysql'));
|
|
update_user_meta($user_id, 'hvac_trainer_rejected_by', get_current_user_id());
|
|
|
|
$message = 'Trainer rejected';
|
|
}
|
|
|
|
// Store reason if provided
|
|
if (!empty($reason)) {
|
|
update_user_meta($user_id, 'hvac_trainer_' . $action . '_reason', $reason);
|
|
}
|
|
|
|
// Log the action
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Trainer ' . $action, 'AJAX', array(
|
|
'trainer_id' => $user_id,
|
|
'approved_by' => get_current_user_id(),
|
|
'reason' => $reason
|
|
));
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'message' => $message,
|
|
'user_id' => $user_id,
|
|
'new_status' => $action === 'approve' ? 'approved' : 'rejected'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Handle unauthorized access
|
|
*/
|
|
public function unauthorized_access() {
|
|
wp_send_json_error(
|
|
array(
|
|
'message' => 'Authentication required',
|
|
'code' => 'unauthorized'
|
|
),
|
|
401
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Initialize cache invalidation hooks
|
|
*
|
|
* Sets up WordPress hooks to clear trainer stats cache when data changes.
|
|
* Implements WordPress best practice for cache invalidation.
|
|
*/
|
|
public function init_cache_invalidation_hooks() {
|
|
// Clear trainer stats cache when events are updated
|
|
add_action('tribe_events_update_event', [$this, 'clear_trainer_stats_cache']);
|
|
add_action('save_post_tribe_events', [$this, 'clear_trainer_stats_cache']);
|
|
add_action('delete_post', [$this, 'clear_trainer_stats_cache']);
|
|
|
|
// Clear cache when user meta changes (trainer status, etc.)
|
|
add_action('update_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3);
|
|
add_action('add_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3);
|
|
add_action('delete_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3);
|
|
}
|
|
|
|
/**
|
|
* Clear trainer stats cache when data changes
|
|
*
|
|
* @param int $post_id Post ID (optional)
|
|
*/
|
|
public function clear_trainer_stats_cache($post_id = 0) {
|
|
global $wpdb;
|
|
|
|
// Clear all trainer stats cache entries
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_hvac_trainer_stats_%'");
|
|
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_hvac_trainer_stats_%'");
|
|
|
|
error_log('[HVAC Performance] Cleared trainer stats cache due to data change');
|
|
}
|
|
|
|
/**
|
|
* Clear trainer stats cache when user meta changes
|
|
*
|
|
* @param int $meta_id Meta ID
|
|
* @param int $user_id User ID
|
|
* @param string $meta_key Meta key
|
|
*/
|
|
public function clear_trainer_stats_cache_on_user_meta($meta_id, $user_id, $meta_key) {
|
|
// Only clear cache for trainer-related meta changes
|
|
$trainer_meta_keys = [
|
|
'hvac_trainer_status',
|
|
'wp_capabilities',
|
|
'certification_status'
|
|
];
|
|
|
|
if (in_array($meta_key, $trainer_meta_keys)) {
|
|
$this->clear_trainer_stats_cache();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send password reset email to a trainer
|
|
*
|
|
* Allows master trainers to trigger a password reset email for any trainer.
|
|
* Uses WordPress built-in password reset functionality.
|
|
*/
|
|
public function send_password_reset() {
|
|
// Verify nonce
|
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_profile_edit')) {
|
|
wp_send_json_error('Invalid security token', 403);
|
|
return;
|
|
}
|
|
|
|
// Check if user is logged in
|
|
if (!is_user_logged_in()) {
|
|
wp_send_json_error('You must be logged in', 401);
|
|
return;
|
|
}
|
|
|
|
// Check if user has permission (master trainer or admin)
|
|
$current_user = wp_get_current_user();
|
|
if (!in_array('hvac_master_trainer', $current_user->roles) && !in_array('administrator', $current_user->roles)) {
|
|
wp_send_json_error('You do not have permission to perform this action', 403);
|
|
return;
|
|
}
|
|
|
|
// Get target user ID
|
|
$user_id = isset($_POST['user_id']) ? absint($_POST['user_id']) : 0;
|
|
if (!$user_id) {
|
|
wp_send_json_error('Invalid user ID', 400);
|
|
return;
|
|
}
|
|
|
|
// Get target user
|
|
$user = get_userdata($user_id);
|
|
if (!$user) {
|
|
wp_send_json_error('User not found', 404);
|
|
return;
|
|
}
|
|
|
|
// Use WordPress built-in password reset
|
|
$result = retrieve_password($user->user_login);
|
|
|
|
if (is_wp_error($result)) {
|
|
wp_send_json_error($result->get_error_message(), 500);
|
|
return;
|
|
}
|
|
|
|
// Log the action
|
|
error_log(sprintf(
|
|
'[HVAC] Password reset email sent for user %d (%s) by master trainer %d (%s)',
|
|
$user_id,
|
|
$user->user_email,
|
|
$current_user->ID,
|
|
$current_user->user_login
|
|
));
|
|
|
|
wp_send_json_success('Password reset email sent to ' . $user->user_email);
|
|
}
|
|
|
|
/**
|
|
* Handle trainer contact form submission from Find Training page
|
|
*
|
|
* Sends an email to the trainer with the visitor's inquiry.
|
|
* Available to both logged-in and anonymous users.
|
|
*/
|
|
public function submit_trainer_contact_form() {
|
|
// Verify nonce
|
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_find_training')) {
|
|
wp_send_json_error(['message' => 'Invalid security token'], 403);
|
|
return;
|
|
}
|
|
|
|
// Rate limiting - max 5 submissions per IP per hour
|
|
$ip = $this->get_client_ip();
|
|
$rate_key = 'hvac_contact_rate_' . md5($ip);
|
|
$submissions = get_transient($rate_key) ?: 0;
|
|
|
|
if ($submissions >= 5) {
|
|
wp_send_json_error(['message' => 'Too many submissions. Please try again later.'], 429);
|
|
return;
|
|
}
|
|
|
|
// Validate required fields
|
|
$required_fields = ['first_name', 'last_name', 'email', 'trainer_id'];
|
|
foreach ($required_fields as $field) {
|
|
if (empty($_POST[$field])) {
|
|
wp_send_json_error(['message' => "Missing required field: {$field}"], 400);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Sanitize inputs
|
|
$first_name = sanitize_text_field($_POST['first_name']);
|
|
$last_name = sanitize_text_field($_POST['last_name']);
|
|
$email = sanitize_email($_POST['email']);
|
|
$phone = sanitize_text_field($_POST['phone'] ?? '');
|
|
$city = sanitize_text_field($_POST['city'] ?? '');
|
|
$state = sanitize_text_field($_POST['state_province'] ?? '');
|
|
$company = sanitize_text_field($_POST['company'] ?? '');
|
|
$message = sanitize_textarea_field($_POST['message'] ?? '');
|
|
$trainer_id = absint($_POST['trainer_id']);
|
|
$profile_id = absint($_POST['trainer_profile_id'] ?? 0);
|
|
|
|
// Validate email
|
|
if (!is_email($email)) {
|
|
wp_send_json_error(['message' => 'Invalid email address'], 400);
|
|
return;
|
|
}
|
|
|
|
// Get trainer data
|
|
$trainer = get_userdata($trainer_id);
|
|
if (!$trainer) {
|
|
wp_send_json_error(['message' => 'Trainer not found'], 404);
|
|
return;
|
|
}
|
|
|
|
// Get trainer's display name from profile if available
|
|
$trainer_name = $trainer->display_name;
|
|
if ($profile_id) {
|
|
$profile_name = get_post_meta($profile_id, 'trainer_display_name', true);
|
|
if ($profile_name) {
|
|
$trainer_name = $profile_name;
|
|
}
|
|
}
|
|
|
|
// Build email content
|
|
$subject = sprintf(
|
|
'[Upskill HVAC] Training Inquiry from %s %s',
|
|
$first_name,
|
|
$last_name
|
|
);
|
|
|
|
$body = sprintf(
|
|
"Hello %s,\n\n" .
|
|
"You have received a training inquiry through the Upskill HVAC directory.\n\n" .
|
|
"--- Contact Details ---\n" .
|
|
"Name: %s %s\n" .
|
|
"Email: %s\n" .
|
|
"%s" . // Phone (optional)
|
|
"%s" . // Location (optional)
|
|
"%s" . // Company (optional)
|
|
"\n--- Message ---\n%s\n\n" .
|
|
"---\n" .
|
|
"This message was sent via the Find Training page at %s\n" .
|
|
"Please respond directly to the sender's email address.\n",
|
|
$trainer_name,
|
|
$first_name,
|
|
$last_name,
|
|
$email,
|
|
$phone ? "Phone: {$phone}\n" : '',
|
|
($city || $state) ? "Location: " . trim("{$city}, {$state}", ', ') . "\n" : '',
|
|
$company ? "Company: {$company}\n" : '',
|
|
$message ?: '(No message provided)',
|
|
home_url('/find-training/')
|
|
);
|
|
|
|
// Email headers
|
|
$headers = [
|
|
'Content-Type: text/plain; charset=UTF-8',
|
|
sprintf('Reply-To: %s %s <%s>', $first_name, $last_name, $email),
|
|
sprintf('From: Upskill HVAC <noreply@%s>', parse_url(home_url(), PHP_URL_HOST))
|
|
];
|
|
|
|
// Send email to trainer
|
|
$sent = wp_mail($trainer->user_email, $subject, $body, $headers);
|
|
|
|
if (!$sent) {
|
|
// Log failure
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::error('Failed to send trainer contact email', 'AJAX', [
|
|
'trainer_id' => $trainer_id,
|
|
'sender_email' => $email
|
|
]);
|
|
}
|
|
wp_send_json_error(['message' => 'Failed to send message. Please try again.'], 500);
|
|
return;
|
|
}
|
|
|
|
// Update rate limit
|
|
set_transient($rate_key, $submissions + 1, HOUR_IN_SECONDS);
|
|
|
|
// Log success
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Trainer contact form submitted', 'AJAX', [
|
|
'trainer_id' => $trainer_id,
|
|
'sender_email' => $email,
|
|
'has_message' => !empty($message)
|
|
]);
|
|
}
|
|
|
|
// Store lead if training leads system exists
|
|
if (class_exists('HVAC_Training_Leads')) {
|
|
$leads = HVAC_Training_Leads::instance();
|
|
if (method_exists($leads, 'create_lead')) {
|
|
$leads->create_lead([
|
|
'first_name' => $first_name,
|
|
'last_name' => $last_name,
|
|
'email' => $email,
|
|
'phone' => $phone,
|
|
'city' => $city,
|
|
'state' => $state,
|
|
'company' => $company,
|
|
'message' => $message,
|
|
'trainer_id' => $trainer_id,
|
|
'source' => 'find_training_page'
|
|
]);
|
|
}
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => 'Your message has been sent to the trainer.',
|
|
'trainer_name' => $trainer_name
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle venue contact form submission from Find Training page
|
|
*
|
|
* Sends an email to the venue POC (Point of Contact) with the visitor's inquiry.
|
|
* Available to both logged-in and anonymous users.
|
|
*/
|
|
public function submit_venue_contact_form() {
|
|
// Verify nonce
|
|
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_find_training')) {
|
|
wp_send_json_error(['message' => 'Invalid security token'], 403);
|
|
return;
|
|
}
|
|
|
|
// Rate limiting - max 5 submissions per IP per hour
|
|
$ip = $this->get_client_ip();
|
|
$rate_key = 'hvac_venue_contact_rate_' . md5($ip);
|
|
$submissions = get_transient($rate_key) ?: 0;
|
|
|
|
if ($submissions >= 5) {
|
|
wp_send_json_error(['message' => 'Too many submissions. Please try again later.'], 429);
|
|
return;
|
|
}
|
|
|
|
// Validate required fields
|
|
$required_fields = ['first_name', 'last_name', 'email', 'venue_id'];
|
|
foreach ($required_fields as $field) {
|
|
if (empty($_POST[$field])) {
|
|
wp_send_json_error(['message' => "Missing required field: {$field}"], 400);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Sanitize inputs
|
|
$first_name = sanitize_text_field($_POST['first_name']);
|
|
$last_name = sanitize_text_field($_POST['last_name']);
|
|
$email = sanitize_email($_POST['email']);
|
|
$phone = sanitize_text_field($_POST['phone'] ?? '');
|
|
$company = sanitize_text_field($_POST['company'] ?? '');
|
|
$message = sanitize_textarea_field($_POST['message'] ?? '');
|
|
$venue_id = absint($_POST['venue_id']);
|
|
|
|
// Validate email
|
|
if (!is_email($email)) {
|
|
wp_send_json_error(['message' => 'Invalid email address'], 400);
|
|
return;
|
|
}
|
|
|
|
// Get venue data
|
|
$venue = get_post($venue_id);
|
|
if (!$venue || $venue->post_type !== 'tribe_venue') {
|
|
wp_send_json_error(['message' => 'Venue not found'], 404);
|
|
return;
|
|
}
|
|
|
|
$venue_name = $venue->post_title;
|
|
|
|
// Get POC information from venue meta
|
|
$poc_user_id = get_post_meta($venue_id, '_venue_poc_user_id', true);
|
|
$poc_email = get_post_meta($venue_id, '_venue_poc_email', true);
|
|
$poc_name = get_post_meta($venue_id, '_venue_poc_name', true);
|
|
|
|
// Fallback to post author if no POC meta
|
|
if (empty($poc_user_id)) {
|
|
$poc_user_id = $venue->post_author;
|
|
}
|
|
|
|
if (empty($poc_email)) {
|
|
$author = get_userdata($poc_user_id);
|
|
if ($author) {
|
|
$poc_email = $author->user_email;
|
|
$poc_name = $poc_name ?: $author->display_name;
|
|
}
|
|
}
|
|
|
|
if (empty($poc_email)) {
|
|
wp_send_json_error(['message' => 'Unable to find contact for this venue'], 500);
|
|
return;
|
|
}
|
|
|
|
// Build email content
|
|
$subject = sprintf(
|
|
'[Upskill HVAC] Training Lab Inquiry - %s',
|
|
$venue_name
|
|
);
|
|
|
|
$body = sprintf(
|
|
"Hello %s,\n\n" .
|
|
"You have received an inquiry about your training lab through the Upskill HVAC directory.\n\n" .
|
|
"--- Training Lab ---\n" .
|
|
"%s\n\n" .
|
|
"--- Contact Details ---\n" .
|
|
"Name: %s %s\n" .
|
|
"Email: %s\n" .
|
|
"%s" . // Phone (optional)
|
|
"%s" . // Company (optional)
|
|
"\n--- Message ---\n%s\n\n" .
|
|
"---\n" .
|
|
"This message was sent via the Find Training page at %s\n" .
|
|
"Please respond directly to the sender's email address.\n",
|
|
$poc_name ?: 'Training Lab Contact',
|
|
$venue_name,
|
|
$first_name,
|
|
$last_name,
|
|
$email,
|
|
$phone ? "Phone: {$phone}\n" : '',
|
|
$company ? "Company: {$company}\n" : '',
|
|
$message ?: '(No message provided)',
|
|
home_url('/find-training/')
|
|
);
|
|
|
|
// Email headers
|
|
$headers = [
|
|
'Content-Type: text/plain; charset=UTF-8',
|
|
sprintf('Reply-To: %s %s <%s>', $first_name, $last_name, $email),
|
|
sprintf('From: Upskill HVAC <noreply@%s>', parse_url(home_url(), PHP_URL_HOST))
|
|
];
|
|
|
|
// Send email to POC
|
|
$sent = wp_mail($poc_email, $subject, $body, $headers);
|
|
|
|
if (!$sent) {
|
|
// Log failure
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::error('Failed to send venue contact email', 'AJAX', [
|
|
'venue_id' => $venue_id,
|
|
'poc_email' => $poc_email,
|
|
'sender_email' => $email
|
|
]);
|
|
}
|
|
wp_send_json_error(['message' => 'Failed to send message. Please try again.'], 500);
|
|
return;
|
|
}
|
|
|
|
// Update rate limit
|
|
set_transient($rate_key, $submissions + 1, HOUR_IN_SECONDS);
|
|
|
|
// Log success
|
|
if (class_exists('HVAC_Logger')) {
|
|
HVAC_Logger::info('Venue contact form submitted', 'AJAX', [
|
|
'venue_id' => $venue_id,
|
|
'venue_name' => $venue_name,
|
|
'poc_email' => $poc_email,
|
|
'sender_email' => $email,
|
|
'has_message' => !empty($message)
|
|
]);
|
|
}
|
|
|
|
// Store lead if training leads system exists
|
|
if (class_exists('HVAC_Training_Leads')) {
|
|
$leads = HVAC_Training_Leads::instance();
|
|
if (method_exists($leads, 'create_lead')) {
|
|
$leads->create_lead([
|
|
'first_name' => $first_name,
|
|
'last_name' => $last_name,
|
|
'email' => $email,
|
|
'phone' => $phone,
|
|
'company' => $company,
|
|
'message' => $message,
|
|
'venue_id' => $venue_id,
|
|
'venue_name' => $venue_name,
|
|
'source' => 'find_training_venue_contact'
|
|
]);
|
|
}
|
|
}
|
|
|
|
wp_send_json_success([
|
|
'message' => 'Your message has been sent to the training lab.',
|
|
'venue_name' => $venue_name
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get client IP address safely
|
|
*
|
|
* @return string IP address
|
|
*/
|
|
private function get_client_ip(): string {
|
|
// Use REMOTE_ADDR only to prevent IP spoofing
|
|
return sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1');
|
|
}
|
|
}
|
|
|
|
// Initialize the handlers
|
|
HVAC_Ajax_Handlers::get_instance(); |