init_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')); } /** * 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) { 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 ); } } // Initialize the handlers HVAC_Ajax_Handlers::get_instance();