upskill-event-manager/includes/find-trainer/class-hvac-trainer-directory-query.php
bengizmo 37f4180e1c feat: Add massive missing plugin infrastructure to repository
🚨 CRITICAL: Fixed deployment blockers by adding missing core directories:

**Community System (CRITICAL)**
- includes/community/ - Login_Handler and all community classes
- templates/community/ - Community login forms

**Certificate System (CRITICAL)**
- includes/certificates/ - 8+ certificate classes and handlers
- templates/certificates/ - Certificate reports and generation templates

**Core Individual Classes (CRITICAL)**
- includes/class-hvac-event-summary.php
- includes/class-hvac-trainer-profile-manager.php
- includes/class-hvac-master-dashboard-data.php
- Plus 40+ other individual HVAC classes

**Major Feature Systems (HIGH)**
- includes/database/ - Training leads database tables
- includes/find-trainer/ - Find trainer directory and MapGeo integration
- includes/google-sheets/ - Google Sheets integration system
- includes/zoho/ - Complete Zoho CRM integration
- includes/communication/ - Communication templates system

**Template Infrastructure**
- templates/attendee/, templates/email-attendees/
- templates/event-summary/, templates/status/
- templates/template-parts/ - Shared template components

**Impact:**
- 70+ files added covering 10+ missing directories
- Resolves ALL deployment blockers and feature breakdowns
- Plugin activation should now work correctly
- Multi-machine deployment fully supported

🔧 Generated with Claude Code

Co-Authored-By: Ben Reed <ben@tealmaker.com>
2025-08-11 13:30:11 -03:00

732 lines
No EOL
26 KiB
PHP

<?php
/**
* Trainer Directory Query Handler
*
* @package HVAC_Plugin
* @since 1.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Class HVAC_Trainer_Directory_Query
* Handles querying and filtering of trainer profiles
*/
class HVAC_Trainer_Directory_Query {
/**
* Instance of this class
*
* @var HVAC_Trainer_Directory_Query
*/
private static $instance = null;
/**
* Cache group for queries
*
* @var string
*/
private $cache_group = 'hvac_trainers';
/**
* Cache expiration time
*
* @var int
*/
private $cache_expiration = 3600; // 1 hour
/**
* Get instance of this class
*
* @return HVAC_Trainer_Directory_Query
*/
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() {
// 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_filter_options', [$this, 'ajax_get_filter_options']);
add_action('wp_ajax_nopriv_hvac_get_filter_options', [$this, 'ajax_get_filter_options']);
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 '<div class="hvac-no-results"><p>No trainers found matching your criteria.</p></div>';
}
$html = ob_get_clean();
wp_send_json_success([
'trainers' => $result['trainers'],
'html' => $html,
'total' => $result['total'],
'pages' => $result['pages'],
'current_page' => $result['current_page']
]);
}
/**
* AJAX handler for getting filter options
*/
public function ajax_get_filter_options() {
check_ajax_referer('hvac_find_trainer', 'nonce');
$filter_type = sanitize_text_field($_POST['filter_type'] ?? '');
if ($filter_type === 'state') {
$options = $this->get_state_options();
} else {
$options = $this->get_filter_options($filter_type);
}
wp_send_json_success(['options' => $options]);
}
/**
* AJAX handler for getting trainer profile
*/
public function ajax_get_trainer_profile() {
check_ajax_referer('hvac_find_trainer', 'nonce');
$profile_id = intval($_POST['profile_id'] ?? 0);
if (!$profile_id) {
wp_send_json_error(['message' => 'Invalid trainer profile']);
}
$trainer_data = $this->format_trainer_data($profile_id);
if (empty($trainer_data['name'])) {
wp_send_json_error(['message' => 'Trainer not found']);
}
// Generate profile HTML
ob_start();
$this->render_trainer_profile_modal($trainer_data);
$html = ob_get_clean();
wp_send_json_success([
'trainer' => $trainer_data,
'html' => $html
]);
}
/**
* AJAX handler for searching trainers
*/
public function ajax_search_trainers() {
check_ajax_referer('hvac_find_trainer', 'nonce');
$search = sanitize_text_field($_POST['search'] ?? '');
if (strlen($search) < 2) {
wp_send_json_error(['message' => 'Search term too short']);
}
$result = $this->get_trainers(['search' => $search, 'per_page' => 20]);
wp_send_json_success([
'trainers' => $result['trainers'],
'total' => $result['total']
]);
}
/**
* Render trainer card
*
* @param array $trainer Trainer data
*/
private function render_trainer_card($trainer) {
?>
<div class="hvac-trainer-card" data-profile-id="<?php echo esc_attr($trainer['profile_id']); ?>">
<div class="hvac-trainer-card-inner">
<div class="hvac-trainer-avatar">
<?php if ($trainer['profile_image']) : ?>
<img src="<?php echo esc_url($trainer['profile_image']); ?>" alt="<?php echo esc_attr($trainer['name']); ?>">
<?php else : ?>
<div class="hvac-default-avatar">
<span><?php echo esc_html(substr($trainer['name'], 0, 1)); ?></span>
</div>
<?php endif; ?>
</div>
<div class="hvac-trainer-info">
<h3 class="trainer-name">
<a href="#" class="hvac-view-profile" data-profile-id="<?php echo esc_attr($trainer['profile_id']); ?>">
<?php echo esc_html($trainer['name']); ?>
</a>
</h3>
<p class="trainer-location">
<?php echo esc_html($trainer['city']); ?>, <?php echo esc_html($trainer['state']); ?>
</p>
<?php if ($trainer['certification_type']) : ?>
<p class="trainer-certification">
<?php echo esc_html($trainer['certification_type']); ?>
</p>
<?php endif; ?>
<div class="hvac-trainer-actions">
<button class="hvac-see-events" data-profile-id="<?php echo esc_attr($trainer['profile_id']); ?>">
<span class="dashicons dashicons-calendar-alt"></span> See Events
</button>
</div>
</div>
</div>
</div>
<?php
}
/**
* Render trainer profile modal content
*
* @param array $trainer Trainer data
*/
private function render_trainer_profile_modal($trainer) {
?>
<div class="hvac-trainer-modal-header">
<h2 id="trainer-modal-title"><?php echo esc_html($trainer['name']); ?></h2>
<button class="hvac-modal-close" aria-label="Close modal">&times;</button>
</div>
<div class="hvac-trainer-modal-body">
<div class="hvac-trainer-profile-section">
<div class="hvac-trainer-profile-header">
<?php if ($trainer['profile_image']) : ?>
<img src="<?php echo esc_url($trainer['profile_image']); ?>" alt="<?php echo esc_attr($trainer['name']); ?>" class="hvac-trainer-profile-image">
<?php else : ?>
<div class="hvac-trainer-profile-avatar">
<span><?php echo esc_html(substr($trainer['name'], 0, 1)); ?></span>
</div>
<?php endif; ?>
<div class="hvac-trainer-profile-info">
<p class="hvac-trainer-location">
<?php echo esc_html($trainer['city']); ?>, <?php echo esc_html($trainer['state']); ?>
</p>
<?php if ($trainer['certification_type']) : ?>
<p class="hvac-trainer-certification">
<?php echo esc_html($trainer['certification_type']); ?>
</p>
<?php endif; ?>
<?php if (!empty($trainer['business_type'])) : ?>
<p class="hvac-trainer-business-type">
<?php echo esc_html(implode(', ', $trainer['business_type'])); ?>
</p>
<?php endif; ?>
<p class="hvac-trainer-events-count">
Total Training Events: <?php echo esc_html($trainer['total_events']); ?>
</p>
</div>
</div>
<?php if (!empty($trainer['training_formats'])) : ?>
<div class="hvac-trainer-detail">
<strong>Training Formats:</strong> <?php echo esc_html(implode(', ', $trainer['training_formats'])); ?>
</div>
<?php endif; ?>
<?php if (!empty($trainer['training_locations'])) : ?>
<div class="hvac-trainer-detail">
<strong>Training Locations:</strong> <?php echo esc_html(implode(', ', $trainer['training_locations'])); ?>
</div>
<?php endif; ?>
<?php if (!empty($trainer['upcoming_events'])) : ?>
<div class="hvac-trainer-events">
<h3>Upcoming Events:</h3>
<ul>
<?php foreach ($trainer['upcoming_events'] as $event) : ?>
<li>
<a href="<?php echo esc_url($event['url']); ?>" target="_blank">
<?php echo esc_html($event['title']); ?>
</a>
- <?php echo esc_html($event['start_date']); ?>
<?php if ($event['city'] && $event['state']) : ?>
(<?php echo esc_html($event['city'] . ', ' . $event['state']); ?>)
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
<div class="hvac-trainer-contact-section">
<h3>Contact</h3>
<form class="hvac-contact-form" data-trainer-id="<?php echo esc_attr($trainer['user_id']); ?>" data-profile-id="<?php echo esc_attr($trainer['profile_id']); ?>">
<div class="hvac-form-row">
<div class="hvac-form-field">
<input type="text" name="first_name" id="contact-first-name" placeholder="First Name" required>
</div>
<div class="hvac-form-field">
<input type="text" name="last_name" id="contact-last-name" placeholder="Last Name" required>
</div>
</div>
<div class="hvac-form-row">
<div class="hvac-form-field">
<input type="email" name="email" id="contact-email" placeholder="Email" required>
</div>
<div class="hvac-form-field">
<input type="tel" name="phone" id="contact-phone" placeholder="Phone Number">
</div>
</div>
<div class="hvac-form-row">
<div class="hvac-form-field">
<input type="text" name="city" id="contact-city" placeholder="City">
</div>
<div class="hvac-form-field">
<input type="text" name="state_province" id="contact-state" placeholder="State/Province">
</div>
</div>
<div class="hvac-form-field">
<input type="text" name="company" id="contact-company" placeholder="Company">
</div>
<div class="hvac-form-field">
<textarea name="message" id="contact-message" placeholder="Message" rows="4"></textarea>
</div>
<div class="hvac-form-submit">
<button type="submit" class="hvac-contact-submit">Submit</button>
</div>
</form>
<div class="hvac-contact-success" style="display: none;">
<p>Your message has been sent! Check your inbox for more details.</p>
</div>
<div class="hvac-contact-error" style="display: none;">
<p>There was an error sending your message. Please try again.</p>
</div>
</div>
</div>
<?php
}
/**
* Clear cache
*/
public function clear_cache() {
// Clear cache using available WordPress functions
if (function_exists('wp_cache_delete_group')) {
wp_cache_delete_group($this->cache_group);
} else {
// Fallback for older WordPress versions
wp_cache_flush();
}
}
}