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.
[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() {
?>
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) : ?>
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;
}
?>
'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(
'',
$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();
}
}