init_hooks(); } /** * Initialize hooks */ private function init_hooks() { add_action('init', [$this, 'register_page']); add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']); add_filter('body_class', [$this, 'add_body_classes']); add_shortcode('hvac_find_trainer', [$this, 'render_shortcode']); add_shortcode('hvac_trainer_directory', [$this, 'render_directory_shortcode']); // AJAX handlers add_action('wp_ajax_hvac_get_trainer_upcoming_events', [$this, 'ajax_get_upcoming_events']); add_action('wp_ajax_nopriv_hvac_get_trainer_upcoming_events', [$this, 'ajax_get_upcoming_events']); // AJAX handlers for filtering add_action('wp_ajax_hvac_filter_trainers', [$this, 'ajax_filter_trainers']); add_action('wp_ajax_nopriv_hvac_filter_trainers', [$this, 'ajax_filter_trainers']); add_action('wp_ajax_hvac_get_filter_options', [$this, 'ajax_get_filter_options']); add_action('wp_ajax_nopriv_hvac_get_filter_options', [$this, 'ajax_get_filter_options']); } /** * Register the Find a Trainer page */ public function register_page() { // Check if page exists $page = get_page_by_path($this->page_slug); if (!$page) { $this->create_page(); } } /** * Create the Find a Trainer page */ private function create_page() { $page_content = $this->get_page_content(); $page_data = [ 'post_title' => 'Find a Trainer', 'post_name' => $this->page_slug, 'post_content' => $page_content, 'post_status' => 'publish', 'post_type' => 'page', 'post_author' => 1, 'meta_input' => [ '_wp_page_template' => 'default', 'ast-site-content-layout' => 'page-builder', 'site-post-title' => 'disabled', 'site-sidebar-layout' => 'no-sidebar', 'ast-main-header-display' => 'enabled', 'ast-hfb-above-header-display' => 'disabled', 'ast-hfb-below-header-display' => 'disabled', 'ast-featured-img' => 'disabled' ] ]; $page_id = wp_insert_post($page_data); if ($page_id && !is_wp_error($page_id)) { update_option('hvac_find_trainer_page_id', $page_id); } } /** * Get the page content with Gutenberg blocks */ private function get_page_content() { return '

Find certified HVAC trainers in your area. Use the interactive map and filters below to discover trainers who match your specific needs. Click on any trainer to view their profile and contact them directly.

[display-map id="5872"]
Filters:
[hvac_trainer_directory]

Are you an HVAC Trainer that wants to be listed in our directory?

'; } /** * Enqueue assets for the Find a Trainer page */ public function enqueue_assets() { if (!$this->is_find_trainer_page()) { return; } // Enqueue CSS wp_enqueue_style( 'hvac-find-trainer', HVAC_PLUGIN_URL . 'assets/css/find-trainer.css', ['astra-theme-css'], HVAC_VERSION ); // Enqueue JavaScript wp_enqueue_script( 'hvac-find-trainer', HVAC_PLUGIN_URL . 'assets/js/find-trainer.js', ['jquery'], HVAC_VERSION, true ); // Localize script wp_localize_script('hvac-find-trainer', 'hvac_find_trainer', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('hvac_find_trainer'), 'map_id' => '5872', 'messages' => [ 'loading' => __('Loading...', 'hvac'), 'error' => __('An error occurred. Please try again.', 'hvac'), 'no_results' => __('No trainers found matching your criteria.', 'hvac'), 'form_error' => __('Please check the form and try again.', 'hvac'), 'form_success' => __('Your message has been sent! Check your inbox for more details.', 'hvac') ] ]); } /** * Add body classes for the Find a Trainer page */ public function add_body_classes($classes) { if ($this->is_find_trainer_page()) { $classes[] = 'hvac-find-trainer-page'; $classes[] = 'hvac-full-width'; } return $classes; } /** * Check if current page is the Find a Trainer page */ private function is_find_trainer_page() { return is_page($this->page_slug) || is_page(get_option('hvac_find_trainer_page_id')); } /** * Render the main shortcode */ public function render_shortcode($atts) { $atts = shortcode_atts([ 'show_map' => true, 'show_filters' => true, 'show_directory' => true ], $atts); ob_start(); ?>
render_filters(); ?>
render_directory(); ?>
12, 'columns' => 2 ], $atts); ob_start(); $this->render_directory($atts); return ob_get_clean(); } /** * Render filter controls */ private function render_filters() { ?>
Filters:
12, 'columns' => 2 ]; $args = wp_parse_args($args, $defaults); // Get trainers $query_args = [ 'post_type' => 'trainer_profile', 'posts_per_page' => $args['per_page'], 'post_status' => 'publish', 'meta_query' => [ [ 'key' => 'is_public_profile', 'value' => '1', 'compare' => '=' ] ] ]; $trainers = new WP_Query($query_args); ?>
have_posts()) : ?> have_posts()) : $trainers->the_post(); ?> render_trainer_card(get_the_ID()); ?>

max_num_pages > 1) : ?>
$trainers->max_num_pages, 'current' => max(1, get_query_var('paged')), 'prev_text' => '« Previous', 'next_text' => 'Next »' ]); ?>
get_trainer_certifications($user_id); foreach ($trainer_certifications as $cert) { $cert_type = get_post_meta($cert->ID, 'certification_type', true); $status = get_post_meta($cert->ID, 'status', true) ?: 'active'; $expiration_date = get_post_meta($cert->ID, 'expiration_date', true); // Check expiration $is_expired = false; if ($expiration_date && strtotime($expiration_date) < time()) { $is_expired = true; } // Only include active, non-expired certifications if ($status === 'active' && !$is_expired) { $certifications[] = [ 'type' => $cert_type, 'status' => $status, 'is_expired' => $is_expired ]; } } } // Fallback to legacy certification if no new certifications found if (empty($certifications) && $legacy_certification) { $certifications[] = [ 'type' => $legacy_certification, 'status' => 'legacy', 'is_expired' => false ]; } // Determine card class and clickability based on certifications $card_classes = ['hvac-trainer-card']; $is_clickable = false; foreach ($certifications as $cert) { if (strpos(strtolower($cert['type']), 'champion') !== false) { $card_classes[] = 'hvac-champion-card'; } if (strpos(strtolower($cert['type']), 'trainer') !== false) { $is_clickable = true; } } // Champions are only clickable if they're also trainers if (in_array('hvac-champion-card', $card_classes) && !$is_clickable) { $is_clickable = false; } ?>
<?php echo esc_attr($trainer_name); ?>
measureQuick Certified Trainer
HVAC Trainer
'trainer_profile', 'posts_per_page' => -1, 'post_status' => 'publish', 'meta_query' => [ [ 'key' => 'is_public_profile', 'value' => '1', 'compare' => '=' ], [ 'key' => 'latitude', 'compare' => 'EXISTS' ], [ 'key' => 'longitude', 'compare' => 'EXISTS' ] ] ]; $trainers = new WP_Query($args); if ($trainers->have_posts()) { while ($trainers->have_posts()) { $trainers->the_post(); $profile_id = get_the_ID(); $markers[] = $this->format_marker_data($profile_id); } } wp_reset_postdata(); return $markers; } /** * Format marker data for a trainer */ private function format_marker_data($profile_id) { $user_id = get_post_meta($profile_id, 'user_id', true); $trainer_name = get_post_meta($profile_id, 'trainer_display_name', true); $city = get_post_meta($profile_id, 'trainer_city', true); $state = get_post_meta($profile_id, 'trainer_state', true); $lat = get_post_meta($profile_id, 'latitude', true); $lng = get_post_meta($profile_id, 'longitude', true); $legacy_certification = get_post_meta($profile_id, 'certification_type', true); $business_types = wp_get_post_terms($profile_id, 'business_type', ['fields' => 'names']); // Get certifications from new system (with fallback to legacy) $certifications = []; if (class_exists('HVAC_Trainer_Certification_Manager')) { $cert_manager = HVAC_Trainer_Certification_Manager::instance(); $trainer_certifications = $cert_manager->get_trainer_certifications($user_id); foreach ($trainer_certifications as $cert) { $cert_type = get_post_meta($cert->ID, 'certification_type', true); $status = get_post_meta($cert->ID, 'status', true) ?: 'active'; $expiration_date = get_post_meta($cert->ID, 'expiration_date', true); $is_expired = false; if ($expiration_date && strtotime($expiration_date) < time()) { $is_expired = true; } if ($status === 'active' && !$is_expired) { $certifications[] = $cert_type; } } } // Fallback to legacy certification if no new certifications found if (empty($certifications) && $legacy_certification) { $certifications[] = $legacy_certification; } return [ 'id' => $profile_id, 'title' => $trainer_name, 'lat' => floatval($lat), 'lng' => floatval($lng), 'content' => $this->generate_marker_content($profile_id), 'data' => [ 'trainer_id' => $user_id, 'profile_id' => $profile_id, 'certification' => $legacy_certification, // Keep for backward compatibility 'certifications' => $certifications, // New field for multiple certifications 'business_type' => $business_types, 'state' => $state, 'city' => $city ] ]; } /** * Generate marker popup content */ private function generate_marker_content($profile_id) { $trainer_name = get_post_meta($profile_id, 'trainer_display_name', true); $city = get_post_meta($profile_id, 'trainer_city', true); $state = get_post_meta($profile_id, 'trainer_state', true); return sprintf( '

%s

%s, %s

', $profile_id, esc_html($trainer_name), esc_html($city), esc_html($state), $profile_id ); } /** * AJAX handler for getting trainer upcoming events */ public function ajax_get_upcoming_events() { // Verify nonce if (!wp_verify_nonce($_POST['nonce'], 'hvac_find_trainer')) { wp_send_json_error('Invalid nonce'); } $profile_id = intval($_POST['profile_id']); if (!$profile_id) { wp_send_json_error('Invalid profile ID'); } // Get the user ID from the profile $user_id = get_post_meta($profile_id, 'user_id', true); if (!$user_id) { wp_send_json_error('No user found for this profile'); } $upcoming_events = []; // Get upcoming events using the fixed query if (function_exists('tribe_get_events')) { $events = tribe_get_events([ 'author' => $user_id, 'posts_per_page' => 5, 'ends_after' => 'now', 'orderby' => 'event_date', 'order' => 'ASC' ]); foreach ($events as $event) { $upcoming_events[] = [ 'title' => $event->post_title, 'date' => tribe_get_start_date($event->ID, false, 'M j, Y'), 'url' => get_permalink($event->ID) ]; } } wp_send_json_success([ 'events' => $upcoming_events, 'count' => count($upcoming_events) ]); } /** * AJAX handler for filtering trainers */ public function ajax_filter_trainers() { // Check if this is a valid AJAX request if (!defined('DOING_AJAX') || !DOING_AJAX) { wp_send_json_error('Not an AJAX request'); return; } // Verify nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'hvac_find_trainer')) { wp_send_json_error('Invalid nonce'); return; } $filters = []; $search_term = sanitize_text_field($_POST['search'] ?? ''); // Parse filter parameters if (!empty($_POST['state'])) { $filters['state'] = sanitize_text_field($_POST['state']); } if (!empty($_POST['business_type'])) { $filters['business_type'] = sanitize_text_field($_POST['business_type']); } if (!empty($_POST['training_format'])) { $filters['training_format'] = sanitize_text_field($_POST['training_format']); } if (!empty($_POST['training_resources'])) { $filters['training_resources'] = sanitize_text_field($_POST['training_resources']); } // Handle pagination $page = isset($_POST['page']) ? absint($_POST['page']) : 1; $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 12; // Build query $query_args = [ 'post_type' => 'trainer_profile', 'posts_per_page' => $per_page, 'paged' => $page, 'post_status' => 'publish', 'meta_query' => [ 'relation' => 'AND', [ 'key' => 'is_public_profile', 'value' => '1', 'compare' => '=' ] ] ]; // Add search term if (!empty($search_term)) { $query_args['meta_query'][] = [ 'relation' => 'OR', [ 'key' => 'trainer_display_name', 'value' => $search_term, 'compare' => 'LIKE' ], [ 'key' => 'trainer_city', 'value' => $search_term, 'compare' => 'LIKE' ], [ 'key' => 'trainer_state', 'value' => $search_term, 'compare' => 'LIKE' ] ]; } // Add state filter if (!empty($filters['state'])) { $query_args['meta_query'][] = [ 'key' => 'trainer_state', 'value' => $filters['state'], 'compare' => '=' ]; } // Add business type filter (taxonomy) if (!empty($filters['business_type'])) { $query_args['tax_query'] = [ [ 'taxonomy' => 'business_type', 'field' => 'slug', 'terms' => $filters['business_type'] ] ]; } // Add training format filter if (!empty($filters['training_format'])) { $query_args['meta_query'][] = [ 'key' => 'training_formats', 'value' => $filters['training_format'], 'compare' => 'LIKE' ]; } // Add training resources filter if (!empty($filters['training_resources'])) { $query_args['meta_query'][] = [ 'key' => 'training_resources', 'value' => $filters['training_resources'], 'compare' => 'LIKE' ]; } $trainers = new WP_Query($query_args); $results = []; if ($trainers->have_posts()) { while ($trainers->have_posts()) { $trainers->the_post(); $profile_id = get_the_ID(); $results[] = $this->render_trainer_card_for_ajax($profile_id); } } wp_reset_postdata(); wp_send_json_success([ 'trainers' => $results, 'count' => $trainers->found_posts, 'page' => $page, 'per_page' => $per_page, 'max_pages' => $trainers->max_num_pages, 'filters_applied' => $filters, 'search_term' => $search_term ]); } /** * AJAX handler for getting filter options */ public function ajax_get_filter_options() { // Verify nonce if (!wp_verify_nonce($_POST['nonce'], 'hvac_find_trainer')) { wp_send_json_error('Invalid nonce'); } $filter_type = sanitize_text_field($_POST['filter_type'] ?? ''); $options = []; switch ($filter_type) { case 'state': $options = $this->get_state_options(); break; case 'business_type': $options = $this->get_business_type_options(); break; case 'training_format': $options = $this->get_training_format_options(); break; case 'training_resources': $options = $this->get_training_resources_options(); break; default: wp_send_json_error('Invalid filter type'); } wp_send_json_success(['options' => $options]); } /** * Get unique state options from trainer profiles */ private function get_state_options() { global $wpdb; $states = $wpdb->get_col(" SELECT DISTINCT meta_value FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = 'trainer_state' AND p.post_type = 'trainer_profile' AND p.post_status = 'publish' AND pm.meta_value != '' ORDER BY pm.meta_value ASC "); return array_filter($states); } /** * Get business type options from taxonomy */ private function get_business_type_options() { $terms = get_terms([ 'taxonomy' => 'business_type', 'hide_empty' => true, 'orderby' => 'name' ]); $options = []; if (!is_wp_error($terms)) { foreach ($terms as $term) { $options[] = [ 'value' => $term->slug, 'label' => $term->name, 'count' => $term->count ]; } } return $options; } /** * Get training format options */ private function get_training_format_options() { global $wpdb; $formats_raw = $wpdb->get_col(" SELECT DISTINCT meta_value FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = 'training_formats' AND p.post_type = 'trainer_profile' AND p.post_status = 'publish' AND pm.meta_value != '' ORDER BY pm.meta_value ASC "); // Process comma-separated values and create objects $options = []; $format_counts = []; foreach ($formats_raw as $format_string) { if (empty($format_string)) continue; $individual_formats = array_map('trim', explode(',', $format_string)); foreach ($individual_formats as $format) { if (!empty($format)) { $format_counts[$format] = isset($format_counts[$format]) ? $format_counts[$format] + 1 : 1; } } } foreach ($format_counts as $format => $count) { $options[] = [ 'value' => $format, 'label' => $format, 'count' => $count ]; } // Sort by label usort($options, function($a, $b) { return strcmp($a['label'], $b['label']); }); return $options; } /** * Get training resources options */ private function get_training_resources_options() { global $wpdb; $resources_raw = $wpdb->get_col(" SELECT DISTINCT meta_value FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = 'training_resources' AND p.post_type = 'trainer_profile' AND p.post_status = 'publish' AND pm.meta_value != '' ORDER BY pm.meta_value ASC "); // Process comma-separated values and create objects $options = []; $resource_counts = []; foreach ($resources_raw as $resource_string) { if (empty($resource_string)) continue; $individual_resources = array_map('trim', explode(',', $resource_string)); foreach ($individual_resources as $resource) { if (!empty($resource)) { $resource_counts[$resource] = isset($resource_counts[$resource]) ? $resource_counts[$resource] + 1 : 1; } } } foreach ($resource_counts as $resource => $count) { $options[] = [ 'value' => $resource, 'label' => $resource, 'count' => $count ]; } // Sort by label usort($options, function($a, $b) { return strcmp($a['label'], $b['label']); }); return $options; } /** * Render trainer card for AJAX responses */ private function render_trainer_card_for_ajax($profile_id) { ob_start(); $this->render_trainer_card($profile_id); return ob_get_clean(); } }