- Add featured image field to main event creation form with WordPress media uploader - Implement featured image upload in organizer and venue creation modals - Update AJAX handlers to process and validate featured image attachments - Add comprehensive media upload UI with preview and removal functionality - Include proper permission validation for administrator, trainer, and master trainer roles - Create authoritative documentation for complete event creation page functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			1602 lines
		
	
	
		
			No EOL
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			1602 lines
		
	
	
		
			No EOL
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC AJAX Handlers
 | |
|  * 
 | |
|  * Implements missing AJAX endpoints with comprehensive security
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @since 2.0.0
 | |
|  */
 | |
| 
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Class HVAC_Ajax_Handlers
 | |
|  * 
 | |
|  * Handles AJAX requests for trainer stats and announcement management
 | |
|  */
 | |
| class HVAC_Ajax_Handlers {
 | |
|     
 | |
|     /**
 | |
|      * Instance of this class
 | |
|      *
 | |
|      * @var HVAC_Ajax_Handlers
 | |
|      */
 | |
|     private static $instance = null;
 | |
|     
 | |
|     /**
 | |
|      * Get instance of this class
 | |
|      *
 | |
|      * @return HVAC_Ajax_Handlers
 | |
|      */
 | |
|     public static function get_instance() {
 | |
|         if (null === self::$instance) {
 | |
|             self::$instance = new self();
 | |
|         }
 | |
|         return self::$instance;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Constructor
 | |
|      */
 | |
|     private function __construct() {
 | |
|         $this->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(); |