init_hooks(); $this->init_cache_invalidation_hooks(); } /** * Initialize AJAX hooks */ private function init_hooks() { // Trainer stats endpoint add_action('wp_ajax_hvac_get_trainer_stats', array($this, 'get_trainer_stats')); add_action('wp_ajax_nopriv_hvac_get_trainer_stats', array($this, 'unauthorized_access')); // Announcement management endpoint add_action('wp_ajax_hvac_manage_announcement', array($this, 'manage_announcement')); add_action('wp_ajax_nopriv_hvac_manage_announcement', array($this, 'unauthorized_access')); // Enhanced approval endpoint (wrapper for existing) add_action('wp_ajax_hvac_approve_trainer_v2', array($this, 'approve_trainer_secure')); add_action('wp_ajax_nopriv_hvac_approve_trainer_v2', array($this, 'unauthorized_access')); // AI Event Population endpoint add_action('wp_ajax_hvac_ai_populate_event', array($this, 'ai_populate_event')); add_action('wp_ajax_nopriv_hvac_ai_populate_event', array($this, 'unauthorized_access')); // Searchable Selector endpoints add_action('wp_ajax_hvac_search_organizers', array($this, 'search_organizers')); add_action('wp_ajax_nopriv_hvac_search_organizers', array($this, 'unauthorized_access')); add_action('wp_ajax_hvac_search_categories', array($this, 'search_categories')); add_action('wp_ajax_nopriv_hvac_search_categories', array($this, 'unauthorized_access')); add_action('wp_ajax_hvac_search_venues', array($this, 'search_venues')); add_action('wp_ajax_nopriv_hvac_search_venues', array($this, 'unauthorized_access')); // Create New endpoints for modal forms add_action('wp_ajax_hvac_create_organizer', array($this, 'create_organizer')); add_action('wp_ajax_nopriv_hvac_create_organizer', array($this, 'unauthorized_access')); add_action('wp_ajax_hvac_create_category', array($this, 'create_category')); add_action('wp_ajax_nopriv_hvac_create_category', array($this, 'unauthorized_access')); add_action('wp_ajax_hvac_create_venue', array($this, 'create_venue')); add_action('wp_ajax_nopriv_hvac_create_venue', array($this, 'unauthorized_access')); } /** * Get trainer statistics * * Provides statistics for trainers with proper security validation */ public function get_trainer_stats() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'get_trainer_stats', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_master_trainer', 'view_master_dashboard', 'manage_options'), false ); if (is_wp_error($security_check)) { wp_send_json_error( array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), $security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403 ); return; } // Input validation $input_rules = array( 'trainer_id' => array( 'type' => 'int', 'required' => false, 'min' => 1 ), 'date_from' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { if (!empty($value) && !strtotime($value)) { return new WP_Error('invalid_date', 'Invalid date format'); } return true; } ), 'date_to' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { if (!empty($value) && !strtotime($value)) { return new WP_Error('invalid_date', 'Invalid date format'); } return true; } ), 'stat_type' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { $valid_types = array('events', 'attendees', 'revenue', 'ratings', 'all'); if (!empty($value) && !in_array($value, $valid_types)) { return new WP_Error('invalid_type', 'Invalid statistics type'); } return true; } ) ); $params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($params)) { wp_send_json_error( array( 'message' => $params->get_error_message(), 'errors' => $params->get_error_data() ), 400 ); return; } // Set defaults $trainer_id = isset($params['trainer_id']) ? $params['trainer_id'] : null; $date_from = isset($params['date_from']) ? $params['date_from'] : date('Y-m-d', strtotime('-30 days')); $date_to = isset($params['date_to']) ? $params['date_to'] : date('Y-m-d'); $stat_type = isset($params['stat_type']) ? $params['stat_type'] : 'all'; // Get statistics $stats = $this->compile_trainer_stats($trainer_id, $date_from, $date_to, $stat_type); if (is_wp_error($stats)) { wp_send_json_error( array( 'message' => $stats->get_error_message() ), 500 ); return; } // Log successful stats retrieval if (class_exists('HVAC_Logger')) { HVAC_Logger::info('Trainer stats retrieved', 'AJAX', array( 'user_id' => get_current_user_id(), 'trainer_id' => $trainer_id, 'date_range' => $date_from . ' to ' . $date_to )); } wp_send_json_success(array( 'stats' => $stats, 'parameters' => array( 'trainer_id' => $trainer_id, 'date_from' => $date_from, 'date_to' => $date_to, 'stat_type' => $stat_type ), 'generated_at' => current_time('mysql') )); } /** * Compile trainer statistics * * @param int|null $trainer_id Specific trainer or all * @param string $date_from Start date * @param string $date_to End date * @param string $stat_type Type of statistics * @return array|WP_Error */ private function compile_trainer_stats($trainer_id, $date_from, $date_to, $stat_type) { // Generate cache key based on parameters (WordPress Best Practice) $cache_key = 'hvac_trainer_stats_' . md5( $trainer_id . $date_from . $date_to . $stat_type . get_current_user_id() ); // Try to get cached result (WordPress Transient API) $cached_stats = get_transient($cache_key); if (false !== $cached_stats) { // Log cache hit for performance monitoring error_log("[HVAC Performance] Cache hit for stats key: {$cache_key}"); return $cached_stats; } // Execute heavy query logic if not cached $stats = $this->execute_heavy_queries($trainer_id, $date_from, $date_to, $stat_type); if (!is_wp_error($stats)) { // Cache for 5 minutes (300 seconds) - WordPress Best Practice set_transient($cache_key, $stats, 300); error_log("[HVAC Performance] Cached stats for key: {$cache_key}"); } return $stats; } /** * Execute heavy database queries for trainer statistics * * Separated from caching logic for better maintainability. * Contains the original heavy query logic. * * @param int $trainer_id Trainer ID * @param string $date_from Start date * @param string $date_to End date * @param string $stat_type Type of statistics * @return array|WP_Error */ private function execute_heavy_queries($trainer_id, $date_from, $date_to, $stat_type) { global $wpdb; $stats = array(); try { // Base query conditions $where_conditions = array("1=1"); $query_params = array(); if ($trainer_id) { $where_conditions[] = "trainer_id = %d"; $query_params[] = $trainer_id; } // Events statistics if (in_array($stat_type, array('events', 'all'))) { $events_query = " SELECT COUNT(DISTINCT p.ID) as total_events, SUM(CASE WHEN p.post_status = 'publish' THEN 1 ELSE 0 END) as published_events, SUM(CASE WHEN p.post_status = 'draft' THEN 1 ELSE 0 END) as draft_events FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id WHERE p.post_type = 'tribe_events' AND pm.meta_key = '_EventStartDate' AND pm.meta_value BETWEEN %s AND %s "; if ($trainer_id) { $events_query .= " AND p.post_author = %d"; $events_result = $wpdb->get_row($wpdb->prepare($events_query, $date_from, $date_to, $trainer_id)); } else { $events_result = $wpdb->get_row($wpdb->prepare($events_query, $date_from, $date_to)); } $stats['events'] = array( 'total' => intval($events_result->total_events), 'published' => intval($events_result->published_events), 'draft' => intval($events_result->draft_events) ); } // Attendees statistics if (in_array($stat_type, array('attendees', 'all'))) { // This would integrate with your attendee tracking system $stats['attendees'] = array( 'total' => 0, 'unique' => 0, 'average_per_event' => 0 ); // If you have attendee data, query it here // Example: Query from custom attendee table or meta } // Trainer summary if (!$trainer_id) { $trainer_stats = $wpdb->get_row(" SELECT COUNT(DISTINCT u.ID) as total_trainers, SUM(CASE WHEN um.meta_value = 'approved' THEN 1 ELSE 0 END) as approved_trainers, SUM(CASE WHEN um.meta_value = 'pending' THEN 1 ELSE 0 END) as pending_trainers FROM {$wpdb->users} u INNER JOIN {$wpdb->usermeta} um ON u.ID = um.user_id WHERE um.meta_key = 'hvac_trainer_status' "); $stats['trainers'] = array( 'total' => intval($trainer_stats->total_trainers), 'approved' => intval($trainer_stats->approved_trainers), 'pending' => intval($trainer_stats->pending_trainers) ); } // Add metadata $stats['meta'] = array( 'date_from' => $date_from, 'date_to' => $date_to, 'generated' => current_time('mysql') ); return $stats; } catch (Exception $e) { return new WP_Error('stats_error', 'Failed to compile statistics: ' . $e->getMessage()); } } /** * Manage announcements * * Unified endpoint for announcement CRUD operations */ public function manage_announcement() { // Determine action $action = isset($_POST['announcement_action']) ? sanitize_text_field($_POST['announcement_action']) : ''; if (empty($action)) { wp_send_json_error( array('message' => 'No action specified'), 400 ); return; } // Map actions to required capabilities $action_capabilities = array( 'create' => array('hvac_master_trainer', 'edit_posts', 'manage_options'), 'update' => array('hvac_master_trainer', 'edit_posts', 'manage_options'), 'delete' => array('hvac_master_trainer', 'delete_posts', 'manage_options'), 'read' => array('hvac_trainer', 'hvac_master_trainer', 'read') ); $required_caps = isset($action_capabilities[$action]) ? $action_capabilities[$action] : array('manage_options'); $is_sensitive = in_array($action, array('delete')); // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'manage_announcement_' . $action, HVAC_Ajax_Security::NONCE_ANNOUNCEMENT, $required_caps, $is_sensitive ); if (is_wp_error($security_check)) { wp_send_json_error( array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), $security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403 ); return; } // Route to appropriate handler switch ($action) { case 'create': $this->create_announcement(); break; case 'update': $this->update_announcement(); break; case 'delete': $this->delete_announcement(); break; case 'read': $this->read_announcement(); break; default: wp_send_json_error( array('message' => 'Invalid action'), 400 ); } } /** * Create announcement */ private function create_announcement() { // Input validation rules $input_rules = array( 'title' => array( 'type' => 'text', 'required' => true, 'max_length' => 200 ), 'content' => array( 'type' => 'html', 'required' => true, 'allowed_html' => wp_kses_allowed_html('post') ), 'excerpt' => array( 'type' => 'textarea', 'required' => false, 'max_length' => 500 ), 'status' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { $valid_statuses = array('publish', 'draft', 'private'); if (!empty($value) && !in_array($value, $valid_statuses)) { return new WP_Error('invalid_status', 'Invalid announcement status'); } return true; } ), 'categories' => array( 'type' => 'array', 'required' => false ), 'tags' => array( 'type' => 'text', 'required' => false, 'max_length' => 500 ) ); $data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($data)) { wp_send_json_error( array( 'message' => $data->get_error_message(), 'errors' => $data->get_error_data() ), 400 ); return; } // Create announcement post $post_data = array( 'post_title' => $data['title'], 'post_content' => $data['content'], 'post_excerpt' => isset($data['excerpt']) ? $data['excerpt'] : '', 'post_status' => isset($data['status']) ? $data['status'] : 'draft', 'post_type' => 'hvac_announcement', 'post_author' => get_current_user_id() ); $post_id = wp_insert_post($post_data, true); if (is_wp_error($post_id)) { wp_send_json_error( array('message' => 'Failed to create announcement: ' . $post_id->get_error_message()), 500 ); return; } // Set categories and tags if provided if (!empty($data['categories'])) { wp_set_object_terms($post_id, $data['categories'], 'hvac_announcement_category'); } if (!empty($data['tags'])) { $tags = array_map('trim', explode(',', $data['tags'])); wp_set_object_terms($post_id, $tags, 'hvac_announcement_tag'); } // Log creation if (class_exists('HVAC_Logger')) { HVAC_Logger::info('Announcement created', 'AJAX', array( 'post_id' => $post_id, 'user_id' => get_current_user_id(), 'title' => $data['title'] )); } wp_send_json_success(array( 'message' => 'Announcement created successfully', 'post_id' => $post_id, 'redirect' => get_permalink($post_id) )); } /** * Update announcement */ private function update_announcement() { // Input validation rules $input_rules = array( 'post_id' => array( 'type' => 'int', 'required' => true, 'min' => 1 ), 'title' => array( 'type' => 'text', 'required' => false, 'max_length' => 200 ), 'content' => array( 'type' => 'html', 'required' => false, 'allowed_html' => wp_kses_allowed_html('post') ), 'excerpt' => array( 'type' => 'textarea', 'required' => false, 'max_length' => 500 ), 'status' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { $valid_statuses = array('publish', 'draft', 'private', 'trash'); if (!empty($value) && !in_array($value, $valid_statuses)) { return new WP_Error('invalid_status', 'Invalid announcement status'); } return true; } ) ); $data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($data)) { wp_send_json_error( array( 'message' => $data->get_error_message(), 'errors' => $data->get_error_data() ), 400 ); return; } // Verify post exists and user can edit $post = get_post($data['post_id']); if (!$post || $post->post_type !== 'hvac_announcement') { wp_send_json_error( array('message' => 'Announcement not found'), 404 ); return; } if (!current_user_can('edit_post', $data['post_id'])) { wp_send_json_error( array('message' => 'You do not have permission to edit this announcement'), 403 ); return; } // Update post data $update_data = array( 'ID' => $data['post_id'] ); if (isset($data['title'])) { $update_data['post_title'] = $data['title']; } if (isset($data['content'])) { $update_data['post_content'] = $data['content']; } if (isset($data['excerpt'])) { $update_data['post_excerpt'] = $data['excerpt']; } if (isset($data['status'])) { $update_data['post_status'] = $data['status']; } $result = wp_update_post($update_data, true); if (is_wp_error($result)) { wp_send_json_error( array('message' => 'Failed to update announcement: ' . $result->get_error_message()), 500 ); return; } // Log update if (class_exists('HVAC_Logger')) { HVAC_Logger::info('Announcement updated', 'AJAX', array( 'post_id' => $data['post_id'], 'user_id' => get_current_user_id(), 'changes' => array_keys($update_data) )); } wp_send_json_success(array( 'message' => 'Announcement updated successfully', 'post_id' => $data['post_id'] )); } /** * Delete announcement */ private function delete_announcement() { // Input validation $input_rules = array( 'post_id' => array( 'type' => 'int', 'required' => true, 'min' => 1 ), 'permanent' => array( 'type' => 'boolean', 'required' => false ) ); $data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($data)) { wp_send_json_error( array( 'message' => $data->get_error_message(), 'errors' => $data->get_error_data() ), 400 ); return; } // Verify post exists and user can delete $post = get_post($data['post_id']); if (!$post || $post->post_type !== 'hvac_announcement') { wp_send_json_error( array('message' => 'Announcement not found'), 404 ); return; } if (!current_user_can('delete_post', $data['post_id'])) { wp_send_json_error( array('message' => 'You do not have permission to delete this announcement'), 403 ); return; } // Delete or trash post $permanent = isset($data['permanent']) ? $data['permanent'] : false; if ($permanent) { $result = wp_delete_post($data['post_id'], true); } else { $result = wp_trash_post($data['post_id']); } if (!$result) { wp_send_json_error( array('message' => 'Failed to delete announcement'), 500 ); return; } // Log deletion if (class_exists('HVAC_Logger')) { HVAC_Logger::warning('Announcement deleted', 'AJAX', array( 'post_id' => $data['post_id'], 'user_id' => get_current_user_id(), 'permanent' => $permanent )); } wp_send_json_success(array( 'message' => 'Announcement deleted successfully', 'post_id' => $data['post_id'] )); } /** * Read announcement */ private function read_announcement() { // Input validation $input_rules = array( 'post_id' => array( 'type' => 'int', 'required' => true, 'min' => 1 ) ); $data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($data)) { wp_send_json_error( array( 'message' => $data->get_error_message(), 'errors' => $data->get_error_data() ), 400 ); return; } // Get announcement $post = get_post($data['post_id']); if (!$post || $post->post_type !== 'hvac_announcement') { wp_send_json_error( array('message' => 'Announcement not found'), 404 ); return; } // Check if user can read if ($post->post_status === 'private' && !current_user_can('read_private_posts')) { wp_send_json_error( array('message' => 'You do not have permission to view this announcement'), 403 ); return; } // Prepare response $response = array( 'id' => $post->ID, 'title' => $post->post_title, 'content' => apply_filters('the_content', $post->post_content), 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'author' => get_the_author_meta('display_name', $post->post_author), 'date' => $post->post_date, 'modified' => $post->post_modified, 'categories' => wp_get_object_terms($post->ID, 'hvac_announcement_category', array('fields' => 'names')), 'tags' => wp_get_object_terms($post->ID, 'hvac_announcement_tag', array('fields' => 'names')), 'can_edit' => current_user_can('edit_post', $post->ID), 'can_delete' => current_user_can('delete_post', $post->ID) ); wp_send_json_success($response); } /** * Enhanced secure trainer approval * * Wrapper for existing approval with enhanced security */ public function approve_trainer_secure() { // Enhanced security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'approve_trainer', HVAC_Ajax_Security::NONCE_APPROVAL, array('hvac_master_trainer', 'hvac_master_manage_approvals', 'manage_options'), true // This is a sensitive action ); if (is_wp_error($security_check)) { wp_send_json_error( array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), $security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403 ); return; } // Enhanced input validation $input_rules = array( 'user_id' => array( 'type' => 'int', 'required' => true, 'min' => 1, 'validate' => function($value) { // Verify user exists and is a trainer $user = get_userdata($value); if (!$user) { return new WP_Error('invalid_user', 'User not found'); } $is_trainer = in_array('hvac_trainer', $user->roles) || get_user_meta($value, 'hvac_trainer_status', true); if (!$is_trainer) { return new WP_Error('not_trainer', 'User is not a trainer'); } return true; } ), 'reason' => array( 'type' => 'textarea', 'required' => false, 'max_length' => 1000 ), 'action' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { $valid_actions = array('approve', 'reject'); if (!empty($value) && !in_array($value, $valid_actions)) { return new WP_Error('invalid_action', 'Invalid approval action'); } return true; } ) ); $data = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($data)) { wp_send_json_error( array( 'message' => $data->get_error_message(), 'errors' => $data->get_error_data() ), 400 ); return; } // Call existing approval handler if available, or implement here if (class_exists('HVAC_Master_Pending_Approvals')) { $approvals = HVAC_Master_Pending_Approvals::get_instance(); if (method_exists($approvals, 'ajax_approve_trainer')) { // Set up the sanitized POST data for the existing handler $_POST['user_id'] = $data['user_id']; $_POST['reason'] = isset($data['reason']) ? $data['reason'] : ''; $_POST['nonce'] = $_REQUEST['nonce']; // Pass through the verified nonce // Call existing handler $approvals->ajax_approve_trainer(); return; } } // Fallback implementation if existing handler not available $this->process_trainer_approval($data); } /** * Process trainer approval (fallback) * * @param array $data Sanitized input data */ private function process_trainer_approval($data) { $user_id = $data['user_id']; $reason = isset($data['reason']) ? $data['reason'] : ''; $action = isset($data['action']) ? $data['action'] : 'approve'; // Update trainer status if ($action === 'approve') { update_user_meta($user_id, 'hvac_trainer_status', 'approved'); update_user_meta($user_id, 'hvac_trainer_approved_date', current_time('mysql')); update_user_meta($user_id, 'hvac_trainer_approved_by', get_current_user_id()); // Add trainer role if not present $user = new WP_User($user_id); if (!in_array('hvac_trainer', $user->roles)) { $user->add_role('hvac_trainer'); } $message = 'Trainer approved successfully'; } else { update_user_meta($user_id, 'hvac_trainer_status', 'rejected'); update_user_meta($user_id, 'hvac_trainer_rejected_date', current_time('mysql')); update_user_meta($user_id, 'hvac_trainer_rejected_by', get_current_user_id()); $message = 'Trainer rejected'; } // Store reason if provided if (!empty($reason)) { update_user_meta($user_id, 'hvac_trainer_' . $action . '_reason', $reason); } // Log the action if (class_exists('HVAC_Logger')) { HVAC_Logger::info('Trainer ' . $action, 'AJAX', array( 'trainer_id' => $user_id, 'approved_by' => get_current_user_id(), 'reason' => $reason )); } wp_send_json_success(array( 'message' => $message, 'user_id' => $user_id, 'new_status' => $action === 'approve' ? 'approved' : 'rejected' )); } /** * Handle unauthorized access */ public function unauthorized_access() { wp_send_json_error( array( 'message' => 'Authentication required', 'code' => 'unauthorized' ), 401 ); } /** * AI Event Population AJAX handler * * Processes user input through AI service and returns structured event data */ public function ai_populate_event() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'ai_populate_event', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_trainer', 'hvac_master_trainer', 'manage_options'), false ); if (is_wp_error($security_check)) { wp_send_json_error( array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), $security_check->get_error_data() ? $security_check->get_error_data()['status'] : 403 ); return; } // Input validation $input_rules = array( 'input' => array( 'type' => 'text', 'required' => true, 'min_length' => 10, 'max_length' => 50000, 'validate' => function($value) { $value = trim($value); if (empty($value)) { return new WP_Error('empty_input', 'Please provide event information to process'); } return true; } ), 'input_type' => array( 'type' => 'text', 'required' => false, 'validate' => function($value) { $valid_types = array('auto', 'url', 'text', 'description'); if (!empty($value) && !in_array($value, $valid_types)) { return new WP_Error('invalid_input_type', 'Invalid input type specified'); } return true; } ) ); $params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($params)) { wp_send_json_error( array( 'message' => $params->get_error_message(), 'errors' => $params->get_error_data() ), 400 ); return; } // Get parameters $input = sanitize_textarea_field($params['input']); $input_type = isset($params['input_type']) ? sanitize_text_field($params['input_type']) : 'auto'; // Rate limiting check (basic implementation) $user_id = get_current_user_id(); $rate_limit_key = "hvac_ai_requests_{$user_id}"; $request_count = get_transient($rate_limit_key) ?: 0; if ($request_count >= 10) { // 10 requests per hour limit wp_send_json_error( array( 'message' => 'Rate limit exceeded. Please try again later.', 'code' => 'rate_limit_exceeded' ), 429 ); return; } // Increment rate limit counter set_transient($rate_limit_key, $request_count + 1, HOUR_IN_SECONDS); // Initialize AI service $ai_populator = HVAC_AI_Event_Populator::instance(); // Process input $result = $ai_populator->populate_from_input($input, $input_type); if (is_wp_error($result)) { // Log error for debugging error_log('HVAC AI Population Error: ' . $result->get_error_message()); wp_send_json_error( array( 'message' => $result->get_error_message(), 'code' => $result->get_error_code() ), $result->get_error_data()['status'] ?? 500 ); return; } // Log successful AI processing if (class_exists('HVAC_Logger')) { HVAC_Logger::info('AI event population successful', 'AI', array( 'user_id' => $user_id, 'input_type' => $input_type, 'input_length' => strlen($input), 'confidence' => $result['confidence']['overall'] ?? 0 )); } // Return successful response wp_send_json_success(array( 'event_data' => $result, 'input_type_detected' => $input_type, 'processed_at' => current_time('mysql'), 'cache_used' => isset($result['_cached']) ? $result['_cached'] : false )); } /** * Initialize cache invalidation hooks * * Sets up WordPress hooks to clear trainer stats cache when data changes. * Implements WordPress best practice for cache invalidation. */ public function init_cache_invalidation_hooks() { // Clear trainer stats cache when events are updated add_action('tribe_events_update_event', [$this, 'clear_trainer_stats_cache']); add_action('save_post_tribe_events', [$this, 'clear_trainer_stats_cache']); add_action('delete_post', [$this, 'clear_trainer_stats_cache']); // Clear cache when user meta changes (trainer status, etc.) add_action('update_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3); add_action('add_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3); add_action('delete_user_meta', [$this, 'clear_trainer_stats_cache_on_user_meta'], 10, 3); } /** * Clear trainer stats cache when data changes * * @param int $post_id Post ID (optional) */ public function clear_trainer_stats_cache($post_id = 0) { global $wpdb; // Clear all trainer stats cache entries $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_hvac_trainer_stats_%'"); $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_hvac_trainer_stats_%'"); error_log('[HVAC Performance] Cleared trainer stats cache due to data change'); } /** * Clear trainer stats cache when user meta changes * * @param int $meta_id Meta ID * @param int $user_id User ID * @param string $meta_key Meta key */ public function clear_trainer_stats_cache_on_user_meta($meta_id, $user_id, $meta_key) { // Only clear cache for trainer-related meta changes $trainer_meta_keys = [ 'hvac_trainer_status', 'wp_capabilities', 'certification_status' ]; if (in_array($meta_key, $trainer_meta_keys)) { $this->clear_trainer_stats_cache(); } } /** * Search organizers for searchable selector */ public function search_organizers() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'search_organizers', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_trainer', 'hvac_master_trainer'), false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Get search query $search = sanitize_text_field($_POST['search'] ?? ''); // Query organizers $args = array( 'post_type' => 'tribe_organizer', 'posts_per_page' => 20, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ); if (!empty($search)) { $args['s'] = $search; } $organizers = get_posts($args); $results = array(); foreach ($organizers as $organizer) { $email = get_post_meta($organizer->ID, '_OrganizerEmail', true); $phone = get_post_meta($organizer->ID, '_OrganizerPhone', true); $subtitle = array(); if ($email) $subtitle[] = $email; if ($phone) $subtitle[] = $phone; $results[] = array( 'id' => $organizer->ID, 'title' => $organizer->post_title, 'subtitle' => implode(' • ', $subtitle) ); } wp_send_json_success($results); } /** * Search categories for searchable selector */ public function search_categories() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'search_categories', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_trainer', 'hvac_master_trainer'), false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Get search query $search = sanitize_text_field($_POST['search'] ?? ''); // Query categories $args = array( 'taxonomy' => 'tribe_events_cat', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', 'number' => 20 ); if (!empty($search)) { $args['search'] = $search; } $categories = get_terms($args); $results = array(); if (!is_wp_error($categories)) { foreach ($categories as $category) { $results[] = array( 'id' => $category->term_id, 'title' => $category->name, 'subtitle' => $category->description ? wp_trim_words($category->description, 10) : null ); } } wp_send_json_success($results); } /** * Search venues for searchable selector */ public function search_venues() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'search_venues', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_trainer', 'hvac_master_trainer'), false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Get search query $search = sanitize_text_field($_POST['search'] ?? ''); // Query venues $args = array( 'post_type' => 'tribe_venue', 'posts_per_page' => 20, 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC' ); if (!empty($search)) { $args['s'] = $search; } $venues = get_posts($args); $results = array(); foreach ($venues as $venue) { $address = get_post_meta($venue->ID, '_VenueAddress', true); $city = get_post_meta($venue->ID, '_VenueCity', true); $state = get_post_meta($venue->ID, '_VenueState', true); $subtitle_parts = array_filter(array($address, $city, $state)); $subtitle = implode(', ', $subtitle_parts); $results[] = array( 'id' => $venue->ID, 'title' => $venue->post_title, 'subtitle' => $subtitle ?: null ); } wp_send_json_success($results); } /** * Create new organizer */ public function create_organizer() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'create_organizer', HVAC_Ajax_Security::NONCE_GENERAL, array('administrator', 'hvac_trainer', 'hvac_master_trainer'), false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Input validation $input_rules = array( 'organizer_name' => array( 'type' => 'text', 'required' => true, 'min_length' => 2, 'max_length' => 255 ), 'organizer_email' => array( 'type' => 'email', 'required' => false ), 'organizer_website' => array( 'type' => 'url', 'required' => false ), 'organizer_phone' => array( 'type' => 'text', 'required' => false, 'max_length' => 20 ), 'organizer_featured_image' => array( 'type' => 'text', 'required' => false ) ); $params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($params)) { wp_send_json_error(array( 'message' => 'Invalid input: ' . $params->get_error_message(), 'code' => 'validation_failed' ), 400); return; } // Create organizer post $organizer_data = array( 'post_title' => $params['organizer_name'], 'post_type' => 'tribe_organizer', 'post_status' => 'publish', 'post_author' => get_current_user_id() ); $organizer_id = wp_insert_post($organizer_data); if (is_wp_error($organizer_id)) { wp_send_json_error(array( 'message' => 'Failed to create organizer', 'code' => 'creation_failed' ), 500); return; } // Add organizer meta if (!empty($params['organizer_email'])) { update_post_meta($organizer_id, '_OrganizerEmail', $params['organizer_email']); } if (!empty($params['organizer_website'])) { update_post_meta($organizer_id, '_OrganizerWebsite', $params['organizer_website']); } if (!empty($params['organizer_phone'])) { update_post_meta($organizer_id, '_OrganizerPhone', $params['organizer_phone']); } // Set featured image if provided if (!empty($params['organizer_featured_image'])) { $image_id = absint($params['organizer_featured_image']); if ($image_id && wp_attachment_is_image($image_id)) { set_post_thumbnail($organizer_id, $image_id); } } // Return created organizer data wp_send_json_success(array( 'id' => $organizer_id, 'title' => $params['organizer_name'], 'subtitle' => $params['organizer_email'] ?: null )); } /** * Create new category */ public function create_category() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'create_category', HVAC_Ajax_Security::NONCE_GENERAL, array('hvac_master_trainer'), // Only master trainers can create categories false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Input validation $input_rules = array( 'category_name' => array( 'type' => 'text', 'required' => true, 'min_length' => 2, 'max_length' => 255 ), 'category_description' => array( 'type' => 'text', 'required' => false, 'max_length' => 1000 ) ); $params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($params)) { wp_send_json_error(array( 'message' => 'Invalid input: ' . $params->get_error_message(), 'code' => 'validation_failed' ), 400); return; } // Check if category already exists $existing = term_exists($params['category_name'], 'tribe_events_cat'); if ($existing) { wp_send_json_error(array( 'message' => 'A category with this name already exists', 'code' => 'category_exists' ), 400); return; } // Create category $category_data = array( 'description' => $params['category_description'] ?: '', 'slug' => sanitize_title($params['category_name']) ); $result = wp_insert_term($params['category_name'], 'tribe_events_cat', $category_data); if (is_wp_error($result)) { wp_send_json_error(array( 'message' => 'Failed to create category: ' . $result->get_error_message(), 'code' => 'creation_failed' ), 500); return; } // Return created category data wp_send_json_success(array( 'id' => $result['term_id'], 'title' => $params['category_name'], 'subtitle' => $params['category_description'] ? wp_trim_words($params['category_description'], 10) : null )); } /** * Create new venue */ public function create_venue() { // Security verification $security_check = HVAC_Ajax_Security::verify_ajax_request( 'create_venue', HVAC_Ajax_Security::NONCE_GENERAL, array('administrator', 'hvac_trainer', 'hvac_master_trainer'), false ); if (is_wp_error($security_check)) { wp_send_json_error(array( 'message' => $security_check->get_error_message(), 'code' => $security_check->get_error_code() ), 403); return; } // Input validation $input_rules = array( 'venue_name' => array( 'type' => 'text', 'required' => true, 'min_length' => 2, 'max_length' => 255 ), 'venue_address' => array( 'type' => 'text', 'required' => false, 'max_length' => 255 ), 'venue_city' => array( 'type' => 'text', 'required' => false, 'max_length' => 100 ), 'venue_state' => array( 'type' => 'text', 'required' => false, 'max_length' => 100 ), 'venue_zip' => array( 'type' => 'text', 'required' => false, 'max_length' => 20 ), 'venue_country' => array( 'type' => 'text', 'required' => false, 'max_length' => 100 ), 'venue_website' => array( 'type' => 'url', 'required' => false ), 'venue_phone' => array( 'type' => 'text', 'required' => false, 'max_length' => 20 ), 'venue_featured_image' => array( 'type' => 'text', 'required' => false ) ); $params = HVAC_Ajax_Security::sanitize_input($_POST, $input_rules); if (is_wp_error($params)) { wp_send_json_error(array( 'message' => 'Invalid input: ' . $params->get_error_message(), 'code' => 'validation_failed' ), 400); return; } // Create venue post $venue_data = array( 'post_title' => $params['venue_name'], 'post_type' => 'tribe_venue', 'post_status' => 'publish', 'post_author' => get_current_user_id() ); $venue_id = wp_insert_post($venue_data); if (is_wp_error($venue_id)) { wp_send_json_error(array( 'message' => 'Failed to create venue', 'code' => 'creation_failed' ), 500); return; } // Add venue meta $meta_fields = array( 'venue_address' => '_VenueAddress', 'venue_city' => '_VenueCity', 'venue_state' => '_VenueState', 'venue_zip' => '_VenueZip', 'venue_country' => '_VenueCountry', 'venue_website' => '_VenueURL', 'venue_phone' => '_VenuePhone' ); foreach ($meta_fields as $param_key => $meta_key) { if (!empty($params[$param_key])) { update_post_meta($venue_id, $meta_key, $params[$param_key]); } } // Set featured image if provided if (!empty($params['venue_featured_image'])) { $image_id = absint($params['venue_featured_image']); if ($image_id && wp_attachment_is_image($image_id)) { set_post_thumbnail($venue_id, $image_id); } } // Build subtitle for display $subtitle_parts = array_filter(array( $params['venue_address'], $params['venue_city'], $params['venue_state'] )); $subtitle = implode(', ', $subtitle_parts); // Return created venue data wp_send_json_success(array( 'id' => $venue_id, 'title' => $params['venue_name'], 'subtitle' => $subtitle ?: null )); } } // Initialize the handlers HVAC_Ajax_Handlers::get_instance();