upskill-event-manager/includes/find-trainer/class-hvac-find-trainer-page.php
Ben c3e7fe9140 feat: comprehensive HVAC plugin development framework and modernization
## Major Enhancements

### 🏗️ Architecture & Infrastructure
- Implement comprehensive Docker testing infrastructure with hermetic environment
- Add Forgejo Actions CI/CD pipeline for automated deployments
- Create Page Object Model (POM) testing architecture reducing test duplication by 90%
- Establish security-first development patterns with input validation and output escaping

### 🧪 Testing Framework Modernization
- Migrate 146+ tests from 80 duplicate files to centralized architecture
- Add comprehensive E2E test suites for all user roles and workflows
- Implement WordPress error detection with automatic site health monitoring
- Create robust browser lifecycle management with proper cleanup

### 📚 Documentation & Guides
- Add comprehensive development best practices guide
- Create detailed administrator setup documentation
- Establish user guides for trainers and master trainers
- Document security incident reports and migration guides

### 🔧 Core Plugin Features
- Enhance trainer profile management with certification system
- Improve find trainer functionality with advanced filtering
- Strengthen master trainer area with content management
- Add comprehensive venue and organizer management

### 🛡️ Security & Reliability
- Implement security-first patterns throughout codebase
- Add comprehensive input validation and output escaping
- Create secure credential management system
- Establish proper WordPress role-based access control

### 🎯 WordPress Integration
- Strengthen singleton pattern implementation across all classes
- Enhance template hierarchy with proper WordPress integration
- Improve page manager with hierarchical URL structure
- Add comprehensive shortcode and menu system

### 🔍 Developer Experience
- Add extensive debugging and troubleshooting tools
- Create comprehensive test data seeding scripts
- Implement proper error handling and logging
- Establish consistent code patterns and standards

### 📊 Performance & Optimization
- Optimize database queries and caching strategies
- Improve asset loading and script management
- Enhance template rendering performance
- Streamline user experience across all interfaces

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 11:26:10 -03:00

996 lines
No EOL
34 KiB
PHP

<?php
/**
* Find a Trainer Page Handler
*
* @package HVAC_Plugin
* @since 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Class HVAC_Find_Trainer_Page
* Manages the Find a Trainer page functionality
*/
class HVAC_Find_Trainer_Page {
/**
* Instance of this class
*
* @var HVAC_Find_Trainer_Page
*/
private static $instance = null;
/**
* Page slug
*
* @var string
*/
private $page_slug = 'find-a-trainer';
/**
* Get instance of this class
*
* @return HVAC_Find_Trainer_Page
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
private function __construct() {
$this->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 '<!-- wp:group {"className":"hvac-find-trainer-wrapper ast-container"} -->
<div class="wp-block-group hvac-find-trainer-wrapper ast-container">
<!-- wp:group {"className":"hvac-find-trainer-intro"} -->
<div class="wp-block-group hvac-find-trainer-intro">
<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->
<!-- wp:columns {"className":"hvac-map-filter-section"} -->
<div class="wp-block-columns hvac-map-filter-section">
<!-- wp:column {"width":"66.66%","className":"hvac-map-container"} -->
<div class="wp-block-column hvac-map-container" style="flex-basis:66.66%">
<!-- wp:shortcode -->
[display-map id="5872"]
<!-- /wp:shortcode -->
</div>
<!-- /wp:column -->
<!-- wp:column {"width":"33.33%","className":"hvac-filter-sidebar"} -->
<div class="wp-block-column hvac-filter-sidebar" style="flex-basis:33.33%">
<!-- wp:html -->
<div class="hvac-filter-controls">
<input type="text" class="hvac-search-input" placeholder="Search trainers..." aria-label="Search trainers">
<div class="hvac-filter-label">Filters:</div>
<button class="hvac-filter-button" data-filter="state" aria-label="Filter by State or Province">
<span class="hvac-filter-icon">▼</span> State / Province
</button>
<button class="hvac-filter-button" data-filter="business_type" aria-label="Filter by Business Type">
<span class="hvac-filter-icon">▼</span> Business Type
</button>
<button class="hvac-filter-button" data-filter="training_format" aria-label="Filter by Training Format">
<span class="hvac-filter-icon">▼</span> Training Format
</button>
<button class="hvac-filter-button" data-filter="training_resources" aria-label="Filter by Training Resources">
<span class="hvac-filter-icon">▼</span> Training Resources
</button>
<div class="hvac-active-filters"></div>
</div>
<!-- /wp:html -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->
<!-- wp:shortcode -->
[hvac_trainer_directory]
<!-- /wp:shortcode -->
<!-- wp:group {"className":"hvac-trainer-cta"} -->
<div class="wp-block-group hvac-trainer-cta">
<!-- wp:paragraph -->
<p>Are you an HVAC Trainer that wants to be listed in our directory?</p>
<!-- /wp:paragraph -->
<!-- wp:buttons -->
<div class="wp-block-buttons">
<!-- wp:button {"className":"hvac-become-trainer-btn"} -->
<div class="wp-block-button hvac-become-trainer-btn">
<a class="wp-block-button__link" href="/trainer-registration/">Become a Trainer</a>
</div>
<!-- /wp:button -->
</div>
<!-- /wp:buttons -->
</div>
<!-- /wp:group -->
</div>
<!-- /wp:group -->';
}
/**
* 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();
?>
<div class="hvac-find-trainer-container">
<?php if ($atts['show_map']) : ?>
<div class="hvac-map-section">
<?php echo do_shortcode('[display-map id="5872"]'); ?>
</div>
<?php endif; ?>
<?php if ($atts['show_filters']) : ?>
<div class="hvac-filter-section">
<?php $this->render_filters(); ?>
</div>
<?php endif; ?>
<?php if ($atts['show_directory']) : ?>
<div class="hvac-directory-section">
<?php $this->render_directory(); ?>
</div>
<?php endif; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Render the trainer directory shortcode
*/
public function render_directory_shortcode($atts) {
$atts = shortcode_atts([
'per_page' => 12,
'columns' => 2
], $atts);
ob_start();
$this->render_directory($atts);
return ob_get_clean();
}
/**
* Render filter controls
*/
private function render_filters() {
?>
<div class="hvac-filter-controls">
<input type="text" class="hvac-search-input" placeholder="Search trainers..." aria-label="Search trainers">
<div class="hvac-filter-label">Filters:</div>
<button class="hvac-filter-button" data-filter="state">
<span class="hvac-filter-icon">▼</span> State / Province
</button>
<button class="hvac-filter-button" data-filter="business_type">
<span class="hvac-filter-icon">▼</span> Business Type
</button>
<button class="hvac-filter-button" data-filter="training_format">
<span class="hvac-filter-icon">▼</span> Training Format
</button>
<button class="hvac-filter-button" data-filter="training_resources">
<span class="hvac-filter-icon">▼</span> Training Resources
</button>
<div class="hvac-active-filters"></div>
</div>
<?php
}
/**
* Render trainer directory
*/
private function render_directory($args = []) {
$defaults = [
'per_page' => 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);
?>
<div class="hvac-trainer-directory" data-columns="<?php echo esc_attr($args['columns']); ?>">
<div class="hvac-trainer-grid">
<?php if ($trainers->have_posts()) : ?>
<?php while ($trainers->have_posts()) : $trainers->the_post(); ?>
<?php $this->render_trainer_card(get_the_ID()); ?>
<?php endwhile; ?>
<?php else : ?>
<div class="hvac-no-results">
<p><?php _e('No trainers found. Please try adjusting your filters.', 'hvac'); ?></p>
</div>
<?php endif; ?>
</div>
<?php if ($trainers->max_num_pages > 1) : ?>
<div class="hvac-pagination">
<?php
echo paginate_links([
'total' => $trainers->max_num_pages,
'current' => max(1, get_query_var('paged')),
'prev_text' => '&laquo; Previous',
'next_text' => 'Next &raquo;'
]);
?>
</div>
<?php endif; ?>
</div>
<?php
wp_reset_postdata();
}
/**
* Render a trainer card
*/
private function render_trainer_card($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);
$legacy_certification = get_post_meta($profile_id, 'certification_type', true);
$profile_image = get_post_meta($profile_id, 'profile_image_url', true);
// 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);
// 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;
}
?>
<div class="<?php echo esc_attr(implode(' ', $card_classes)); ?>" data-profile-id="<?php echo esc_attr($profile_id); ?>" data-event-count="0">
<div class="hvac-trainer-image">
<?php if ($profile_image) : ?>
<img src="<?php echo esc_url($profile_image); ?>" alt="<?php echo esc_attr($trainer_name); ?>">
<?php else : ?>
<div class="hvac-trainer-avatar">
<span class="dashicons dashicons-businessperson"></span>
</div>
<?php endif; ?>
<?php if ($is_clickable) : ?>
<!-- Add mQ badge for trainers -->
<?php foreach ($certifications as $cert) : ?>
<?php if (strpos(strtolower($cert['type']), 'trainer') !== false) : ?>
<img src="/wp-content/uploads/2025/08/mQ-Certified-trainer.png" alt="measureQuick Certified Trainer" class="hvac-mq-badge">
<?php break; ?>
<?php endif; ?>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="hvac-trainer-info">
<div class="hvac-trainer-name">
<?php if ($is_clickable) : ?>
<a href="#" class="hvac-open-profile" data-profile-id="<?php echo esc_attr($profile_id); ?>">
<?php echo esc_html($trainer_name); ?>
</a>
<?php else : ?>
<span class="hvac-champion-name"><?php echo esc_html($trainer_name); ?></span>
<?php endif; ?>
</div>
<div class="hvac-trainer-location">
<?php echo esc_html($city . ', ' . $state); ?>
</div>
<!-- Multiple Certifications -->
<div class="hvac-trainer-certifications">
<?php if (!empty($certifications)) : ?>
<?php foreach ($certifications as $cert) : ?>
<span class="hvac-trainer-cert-badge hvac-cert-<?php
echo esc_attr(strtolower(str_replace(['measureQuick Certified ', ' '], ['', '-'], $cert['type'])));
?><?php echo $cert['status'] === 'legacy' ? ' hvac-cert-legacy' : ''; ?>">
<?php echo esc_html($cert['type']); ?>
</span>
<?php endforeach; ?>
<?php else : ?>
<span class="hvac-trainer-cert-badge hvac-cert-default">HVAC Trainer</span>
<?php endif; ?>
</div>
</div>
</div>
<?php
}
/**
* Get map markers data for all trainers
*/
public function get_map_markers_data() {
$markers = [];
$args = [
'post_type' => '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(
'<div class="hvac-marker-popup" data-profile-id="%d">
<h4>%s</h4>
<p>%s, %s</p>
<button class="hvac-view-profile" data-profile-id="%d">View Profile</button>
</div>',
$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();
}
}