init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // AJAX handlers add_action('wp_ajax_hvac_get_filtered_trainers', [$this, 'ajax_get_filtered_trainers']); add_action('wp_ajax_nopriv_hvac_get_filtered_trainers', [$this, 'ajax_get_filtered_trainers']); add_action('wp_ajax_hvac_get_trainer_profile', [$this, 'ajax_get_trainer_profile']); add_action('wp_ajax_nopriv_hvac_get_trainer_profile', [$this, 'ajax_get_trainer_profile']); add_action('wp_ajax_hvac_search_trainers', [$this, 'ajax_search_trainers']); add_action('wp_ajax_nopriv_hvac_search_trainers', [$this, 'ajax_search_trainers']); // Clear cache on profile updates add_action('save_post_trainer_profile', [$this, 'clear_cache']); add_action('deleted_post', [$this, 'clear_cache']); } /** * Get trainers based on filters * * @param array $args Query arguments * @return array */ public function get_trainers($args = []) { $defaults = [ 'per_page' => 12, 'page' => 1, 'state' => '', 'business_type' => '', 'training_format' => '', 'training_resources' => '', 'search' => '', 'orderby' => 'name', 'order' => 'ASC' ]; $args = wp_parse_args($args, $defaults); // Generate cache key $cache_key = 'trainers_' . md5(serialize($args)); $cached = wp_cache_get($cache_key, $this->cache_group); if (false !== $cached) { return $cached; } // Build query $query_args = [ 'post_type' => 'trainer_profile', 'posts_per_page' => $args['per_page'], 'paged' => $args['page'], 'post_status' => 'publish', 'meta_query' => [ [ 'key' => 'is_public_profile', 'value' => '1', 'compare' => '=' ] ] ]; // Add meta queries $meta_query = $this->build_meta_query($args); if (!empty($meta_query)) { $query_args['meta_query'] = array_merge($query_args['meta_query'], $meta_query); } // Add taxonomy queries $tax_query = $this->build_tax_query($args); if (!empty($tax_query)) { $query_args['tax_query'] = $tax_query; } // Add search if (!empty($args['search'])) { $query_args['meta_query'][] = [ 'relation' => 'OR', [ 'key' => 'trainer_display_name', 'value' => $args['search'], 'compare' => 'LIKE' ], [ 'key' => 'trainer_city', 'value' => $args['search'], 'compare' => 'LIKE' ], [ 'key' => 'company_name', 'value' => $args['search'], 'compare' => 'LIKE' ] ]; } // Add ordering $query_args = $this->add_ordering($query_args, $args['orderby'], $args['order']); // Execute query $query = new WP_Query($query_args); $result = [ 'trainers' => [], 'total' => $query->found_posts, 'pages' => $query->max_num_pages, 'current_page' => $args['page'] ]; if ($query->have_posts()) { while ($query->have_posts()) { $query->the_post(); $result['trainers'][] = $this->format_trainer_data(get_the_ID()); } } wp_reset_postdata(); // Cache result wp_cache_set($cache_key, $result, $this->cache_group, $this->cache_expiration); return $result; } /** * Build meta query from filters * * @param array $filters Filter values * @return array */ private function build_meta_query($filters) { $meta_query = []; if (!empty($filters['state'])) { $meta_query[] = [ 'key' => 'trainer_state', 'value' => sanitize_text_field($filters['state']), 'compare' => '=' ]; } return $meta_query; } /** * Build taxonomy query from filters * * @param array $filters Filter values * @return array */ private function build_tax_query($filters) { $tax_query = []; if (!empty($filters['business_type'])) { $tax_query[] = [ 'taxonomy' => 'business_type', 'field' => 'slug', 'terms' => sanitize_text_field($filters['business_type']) ]; } if (!empty($filters['training_format'])) { $tax_query[] = [ 'taxonomy' => 'training_formats', 'field' => 'slug', 'terms' => sanitize_text_field($filters['training_format']) ]; } if (!empty($filters['training_resources'])) { $tax_query[] = [ 'taxonomy' => 'training_resources', 'field' => 'slug', 'terms' => sanitize_text_field($filters['training_resources']) ]; } if (!empty($tax_query)) { $tax_query['relation'] = 'AND'; } return $tax_query; } /** * Add ordering to query * * @param array $query_args Query arguments * @param string $orderby Order by field * @param string $order Order direction * @return array */ private function add_ordering($query_args, $orderby, $order) { switch ($orderby) { case 'name': $query_args['meta_key'] = 'trainer_display_name'; $query_args['orderby'] = 'meta_value'; break; case 'city': $query_args['meta_key'] = 'trainer_city'; $query_args['orderby'] = 'meta_value'; break; case 'state': $query_args['meta_key'] = 'trainer_state'; $query_args['orderby'] = 'meta_value'; break; case 'events': $query_args['orderby'] = 'meta_value_num'; $query_args['meta_key'] = 'total_events_count'; break; default: $query_args['orderby'] = 'date'; break; } $query_args['order'] = in_array($order, ['ASC', 'DESC']) ? $order : 'ASC'; return $query_args; } /** * Format trainer data for response * * @param int $profile_id Trainer profile post ID * @return array */ private function format_trainer_data($profile_id) { $user_id = get_post_meta($profile_id, 'user_id', true); $data = [ 'profile_id' => $profile_id, 'user_id' => $user_id, '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), 'country' => get_post_meta($profile_id, 'trainer_country', true) ?: 'USA', 'certification_type' => get_post_meta($profile_id, 'certification_type', true), 'certification_status' => get_post_meta($profile_id, 'certification_status', true), 'profile_image' => get_post_meta($profile_id, 'profile_image_url', true), 'company_name' => get_post_meta($profile_id, 'company_name', true), 'company_website' => get_post_meta($profile_id, 'company_website', true), 'phone' => get_post_meta($profile_id, 'trainer_phone', true), 'email' => get_post_meta($profile_id, 'trainer_email', true), 'bio' => get_post_meta($profile_id, 'trainer_bio', true), 'latitude' => get_post_meta($profile_id, 'latitude', true), 'longitude' => get_post_meta($profile_id, 'longitude', true) ]; // Get taxonomies $data['business_type'] = wp_get_post_terms($profile_id, 'business_type', ['fields' => 'names']); $data['training_formats'] = wp_get_post_terms($profile_id, 'training_formats', ['fields' => 'names']); $data['training_locations'] = wp_get_post_terms($profile_id, 'training_locations', ['fields' => 'names']); $data['training_resources'] = wp_get_post_terms($profile_id, 'training_resources', ['fields' => 'names']); $data['training_audience'] = wp_get_post_terms($profile_id, 'training_audience', ['fields' => 'names']); // Get upcoming events $data['upcoming_events'] = $this->get_trainer_events($user_id); $data['total_events'] = get_post_meta($profile_id, 'total_events_count', true) ?: 0; return $data; } /** * Get trainer's upcoming events * * @param int $user_id Trainer user ID * @return array */ private function get_trainer_events($user_id, $limit = 5) { if (!function_exists('tribe_get_events')) { return []; } $events = tribe_get_events([ 'author' => $user_id, 'eventDisplay' => 'upcoming', 'posts_per_page' => $limit ]); $formatted_events = []; foreach ($events as $event) { $formatted_events[] = [ 'id' => $event->ID, 'title' => $event->post_title, 'start_date' => tribe_get_start_date($event->ID, false, 'Y-m-d'), 'start_time' => tribe_get_start_time($event->ID), 'end_date' => tribe_get_end_date($event->ID, false, 'Y-m-d'), 'venue' => tribe_get_venue($event->ID), 'city' => tribe_get_city($event->ID), 'state' => tribe_get_state($event->ID), 'url' => get_permalink($event->ID) ]; } return $formatted_events; } /** * Get filter options for a taxonomy * * @param string $taxonomy Taxonomy name * @return array */ public function get_filter_options($taxonomy) { $valid_taxonomies = [ 'business_type', 'training_formats', 'training_locations', 'training_resources', 'training_audience' ]; if (!in_array($taxonomy, $valid_taxonomies)) { return []; } $terms = get_terms([ 'taxonomy' => $taxonomy, 'hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC' ]); $options = []; if (!is_wp_error($terms)) { foreach ($terms as $term) { $options[] = [ 'value' => $term->slug, 'label' => $term->name, 'count' => $term->count ]; } } return $options; } /** * Get state/province options * * @return array */ public function get_state_options() { global $wpdb; $table = $wpdb->postmeta; $states = $wpdb->get_col( "SELECT DISTINCT meta_value FROM $table WHERE meta_key = 'trainer_state' AND meta_value != '' ORDER BY meta_value ASC" ); $options = []; foreach ($states as $state) { $options[] = [ 'value' => $state, 'label' => $state ]; } return $options; } /** * AJAX handler for getting filtered trainers */ public function ajax_get_filtered_trainers() { check_ajax_referer('hvac_find_trainer', 'nonce'); $filters = $_POST['filters'] ?? []; $page = intval($_POST['page'] ?? 1); $per_page = intval($_POST['per_page'] ?? 12); $args = [ 'page' => $page, 'per_page' => $per_page, 'state' => sanitize_text_field($filters['state'] ?? ''), 'business_type' => sanitize_text_field($filters['business_type'] ?? ''), 'training_format' => sanitize_text_field($filters['training_format'] ?? ''), 'training_resources' => sanitize_text_field($filters['training_resources'] ?? ''), 'search' => sanitize_text_field($filters['search'] ?? '') ]; $result = $this->get_trainers($args); // Generate HTML for directory ob_start(); if (!empty($result['trainers'])) { foreach ($result['trainers'] as $trainer) { $this->render_trainer_card($trainer); } } else { echo '
No trainers found matching your criteria.