upskill-event-manager/includes/class-hvac-ajax-handlers.php
ben 91873c6a9c feat: implement comprehensive featured image system for events, organizers, and venues
- 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>
2025-09-26 20:24:31 -03:00

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();