Some checks failed
		
		
	
	HVAC Plugin CI/CD Pipeline / Code Quality & Standards (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Unit Tests (push) Has been cancelled
				
			Security Monitoring & Compliance / Secrets & Credential Scan (push) Has been cancelled
				
			Security Monitoring & Compliance / WordPress Security Analysis (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Security Analysis (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Integration Tests (push) Has been cancelled
				
			Security Monitoring & Compliance / Dependency Vulnerability Scan (push) Has been cancelled
				
			Security Monitoring & Compliance / Static Code Security Analysis (push) Has been cancelled
				
			Security Monitoring & Compliance / Security Compliance Validation (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Deploy to Production (push) Has been cancelled
				
			HVAC Plugin CI/CD Pipeline / Notification (push) Has been cancelled
				
			Security Monitoring & Compliance / Security Summary Report (push) Has been cancelled
				
			Security Monitoring & Compliance / Security Team Notification (push) Has been cancelled
				
			- Deploy 6 simultaneous WordPress specialized agents using sequential thinking and Zen MCP - Resolve all critical issues: permissions, jQuery dependencies, CDN mapping, security vulnerabilities - Implement bulletproof jQuery loading system with WordPress hook timing fixes - Create professional MapGeo Safety system with CDN health monitoring and fallback UI - Fix privilege escalation vulnerability with capability-based authorization - Add complete announcement admin system with modal forms and AJAX handling - Enhance import/export functionality (54 trainers successfully exported) - Achieve 100% operational master trainer functionality verified via MCP Playwright E2E testing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			974 lines
		
	
	
		
			No EOL
		
	
	
		
			40 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			974 lines
		
	
	
		
			No EOL
		
	
	
		
			40 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Master Pending Approvals
 | |
|  *
 | |
|  * Handles the master trainer pending approvals interface and functionality
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @since 1.0.0
 | |
|  */
 | |
| 
 | |
| if (!defined('ABSPATH')) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| class HVAC_Master_Pending_Approvals {
 | |
|     
 | |
|     /**
 | |
|      * Instance of this class
 | |
|      *
 | |
|      * @var HVAC_Master_Pending_Approvals
 | |
|      */
 | |
|     private static $instance = null;
 | |
|     
 | |
|     /**
 | |
|      * Get instance of this class
 | |
|      *
 | |
|      * @return HVAC_Master_Pending_Approvals
 | |
|      */
 | |
|     public static function instance() {
 | |
|         if (self::$instance === null) {
 | |
|             self::$instance = new self();
 | |
|         }
 | |
|         return self::$instance;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Constructor
 | |
|      */
 | |
|     private function __construct() {
 | |
|         // Hook into WordPress
 | |
|         add_action('init', array($this, 'init'));
 | |
|         
 | |
|         // AJAX handlers
 | |
|         add_action('wp_ajax_hvac_approve_trainer', array($this, 'ajax_approve_trainer'));
 | |
|         add_action('wp_ajax_hvac_reject_trainer', array($this, 'ajax_reject_trainer'));
 | |
|         add_action('wp_ajax_hvac_bulk_trainer_action', array($this, 'ajax_bulk_trainer_action'));
 | |
|         add_action('wp_ajax_hvac_get_trainer_details', array($this, 'ajax_get_trainer_details'));
 | |
|         
 | |
|         // Register shortcode
 | |
|         add_shortcode('hvac_pending_approvals', array($this, 'render_pending_approvals'));
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Initialize the class
 | |
|      */
 | |
|     public function init() {
 | |
|         // Add capability to master trainer role if not exists
 | |
|         $this->ensure_capabilities();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Ensure required capabilities exist
 | |
|      */
 | |
|     private function ensure_capabilities() {
 | |
|         $master_role = get_role('hvac_master_trainer');
 | |
|         if ($master_role && !$master_role->has_cap('hvac_master_manage_approvals')) {
 | |
|             $master_role->add_cap('hvac_master_manage_approvals');
 | |
|         }
 | |
|         
 | |
|         // Also add to admin role
 | |
|         $admin_role = get_role('administrator');
 | |
|         if ($admin_role && !$admin_role->has_cap('hvac_master_manage_approvals')) {
 | |
|             $admin_role->add_cap('hvac_master_manage_approvals');
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Check if current user can manage approvals
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public function can_manage_approvals() {
 | |
|         $user = wp_get_current_user();
 | |
|         return in_array('hvac_master_trainer', $user->roles) || 
 | |
|                current_user_can('hvac_master_manage_approvals') || 
 | |
|                current_user_can('manage_options');
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Render pending approvals interface
 | |
|      */
 | |
|     public function render_pending_approvals($atts = array()) {
 | |
|         // Check permissions
 | |
|         if (!$this->can_manage_approvals()) {
 | |
|             return '<div class="hvac-error">You do not have permission to access this page.</div>';
 | |
|         }
 | |
|         
 | |
|         // Get filters from request
 | |
|         $status_filter = sanitize_text_field($_GET['status_filter'] ?? 'pending');
 | |
|         $region_filter = sanitize_text_field($_GET['region_filter'] ?? '');
 | |
|         $date_from = sanitize_text_field($_GET['date_from'] ?? '');
 | |
|         $date_to = sanitize_text_field($_GET['date_to'] ?? '');
 | |
|         $search_term = sanitize_text_field($_GET['search'] ?? '');
 | |
|         $page = max(1, intval($_GET['paged'] ?? 1));
 | |
|         $per_page = 20;
 | |
|         
 | |
|         // Get trainers data
 | |
|         $trainers_data = $this->get_trainers_data(array(
 | |
|             'status' => $status_filter,
 | |
|             'region' => $region_filter,
 | |
|             'date_from' => $date_from,
 | |
|             'date_to' => $date_to,
 | |
|             'search' => $search_term,
 | |
|             'page' => $page,
 | |
|             'per_page' => $per_page
 | |
|         ));
 | |
|         
 | |
|         ob_start();
 | |
|         ?>
 | |
|         <div class="hvac-pending-approvals-wrapper">
 | |
|             
 | |
|             <!-- Page Header -->
 | |
|             <div class="hvac-page-header">
 | |
|                 <h1>Trainer Approvals</h1>
 | |
|                 <p class="hvac-page-description">Review and manage trainer registration approvals</p>
 | |
|             </div>
 | |
|             
 | |
|             <!-- Filters Section -->
 | |
|             <div class="hvac-filters-section">
 | |
|                 <form method="get" class="hvac-filters-form" id="hvac-approvals-filters">
 | |
|                     <input type="hidden" name="page_id" value="<?php echo get_the_ID(); ?>">
 | |
|                     
 | |
|                     <div class="hvac-filter-group">
 | |
|                         <label for="status_filter">Status:</label>
 | |
|                         <select name="status_filter" id="status_filter">
 | |
|                             <option value="pending" <?php selected($status_filter, 'pending'); ?>>Pending</option>
 | |
|                             <option value="approved" <?php selected($status_filter, 'approved'); ?>>Approved</option>
 | |
|                             <option value="rejected" <?php selected($status_filter, 'rejected'); ?>>Rejected</option>
 | |
|                             <option value="all" <?php selected($status_filter, 'all'); ?>>All Statuses</option>
 | |
|                         </select>
 | |
|                     </div>
 | |
|                     
 | |
|                     <div class="hvac-filter-group">
 | |
|                         <label for="region_filter">Region:</label>
 | |
|                         <select name="region_filter" id="region_filter">
 | |
|                             <option value="">All Regions</option>
 | |
|                             <?php foreach ($this->get_regions() as $region): ?>
 | |
|                                 <option value="<?php echo esc_attr($region); ?>" <?php selected($region_filter, $region); ?>>
 | |
|                                     <?php echo esc_html($region); ?>
 | |
|                                 </option>
 | |
|                             <?php endforeach; ?>
 | |
|                         </select>
 | |
|                     </div>
 | |
|                     
 | |
|                     <div class="hvac-filter-group">
 | |
|                         <label for="date_from">Date From:</label>
 | |
|                         <input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>">
 | |
|                     </div>
 | |
|                     
 | |
|                     <div class="hvac-filter-group">
 | |
|                         <label for="date_to">Date To:</label>
 | |
|                         <input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>">
 | |
|                     </div>
 | |
|                     
 | |
|                     <div class="hvac-filter-group">
 | |
|                         <label for="search">Search:</label>
 | |
|                         <input type="text" name="search" id="search" placeholder="Name or email..." value="<?php echo esc_attr($search_term); ?>">
 | |
|                     </div>
 | |
|                     
 | |
|                     <div class="hvac-filter-actions">
 | |
|                         <button type="submit" class="hvac-btn hvac-btn-primary">Filter</button>
 | |
|                         <a href="<?php echo get_permalink(); ?>" class="hvac-btn hvac-btn-secondary">Reset</a>
 | |
|                     </div>
 | |
|                 </form>
 | |
|             </div>
 | |
|             
 | |
|             <!-- Bulk Actions -->
 | |
|             <?php if ($status_filter === 'pending' && !empty($trainers_data['trainers'])): ?>
 | |
|             <div class="hvac-bulk-actions">
 | |
|                 <div class="hvac-bulk-select">
 | |
|                     <input type="checkbox" id="hvac-select-all"> 
 | |
|                     <label for="hvac-select-all">Select All</label>
 | |
|                 </div>
 | |
|                 <div class="hvac-bulk-buttons">
 | |
|                     <button type="button" class="hvac-btn hvac-btn-success" id="hvac-bulk-approve" disabled>
 | |
|                         Approve Selected
 | |
|                     </button>
 | |
|                     <button type="button" class="hvac-btn hvac-btn-danger" id="hvac-bulk-reject" disabled>
 | |
|                         Reject Selected
 | |
|                     </button>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <?php endif; ?>
 | |
|             
 | |
|             <!-- Results Summary -->
 | |
|             <div class="hvac-results-summary">
 | |
|                 <p>
 | |
|                     <?php 
 | |
|                     printf(
 | |
|                         'Showing %d of %d trainers',
 | |
|                         count($trainers_data['trainers']),
 | |
|                         $trainers_data['total']
 | |
|                     ); 
 | |
|                     ?>
 | |
|                 </p>
 | |
|             </div>
 | |
|             
 | |
|             <!-- Trainers Table -->
 | |
|             <div class="hvac-trainers-table-wrapper">
 | |
|                 <?php if (empty($trainers_data['trainers'])): ?>
 | |
|                     <div class="hvac-no-results">
 | |
|                         <p>No trainers found matching your criteria.</p>
 | |
|                     </div>
 | |
|                 <?php else: ?>
 | |
|                     <table class="hvac-trainers-table" id="hvac-trainers-table">
 | |
|                         <thead>
 | |
|                             <tr>
 | |
|                                 <?php if ($status_filter === 'pending'): ?>
 | |
|                                     <th class="hvac-col-select">
 | |
|                                         <input type="checkbox" id="hvac-header-select-all">
 | |
|                                     </th>
 | |
|                                 <?php endif; ?>
 | |
|                                 <th class="hvac-col-date">Date</th>
 | |
|                                 <th class="hvac-col-name">Name</th>
 | |
|                                 <th class="hvac-col-email">Email</th>
 | |
|                                 <th class="hvac-col-location">Location</th>
 | |
|                                 <th class="hvac-col-status">Status</th>
 | |
|                                 <th class="hvac-col-actions">Actions</th>
 | |
|                             </tr>
 | |
|                         </thead>
 | |
|                         <tbody>
 | |
|                             <?php foreach ($trainers_data['trainers'] as $trainer): ?>
 | |
|                                 <tr data-user-id="<?php echo esc_attr($trainer->ID); ?>">
 | |
|                                     <?php if ($status_filter === 'pending'): ?>
 | |
|                                         <td class="hvac-col-select">
 | |
|                                             <input type="checkbox" class="hvac-trainer-select" value="<?php echo esc_attr($trainer->ID); ?>">
 | |
|                                         </td>
 | |
|                                     <?php endif; ?>
 | |
|                                     <td class="hvac-col-date">
 | |
|                                         <?php echo esc_html(date('M j, Y', strtotime($trainer->user_registered))); ?>
 | |
|                                     </td>
 | |
|                                     <td class="hvac-col-name">
 | |
|                                         <button type="button" class="hvac-trainer-name-btn" data-user-id="<?php echo esc_attr($trainer->ID); ?>">
 | |
|                                             <?php echo esc_html($trainer->display_name); ?>
 | |
|                                         </button>
 | |
|                                     </td>
 | |
|                                     <td class="hvac-col-email">
 | |
|                                         <?php echo esc_html($trainer->user_email); ?>
 | |
|                                     </td>
 | |
|                                     <td class="hvac-col-location">
 | |
|                                         <?php echo esc_html($this->get_trainer_location($trainer->ID)); ?>
 | |
|                                     </td>
 | |
|                                     <td class="hvac-col-status">
 | |
|                                         <?php echo $this->get_status_badge(HVAC_Trainer_Status::get_trainer_status($trainer->ID)); ?>
 | |
|                                     </td>
 | |
|                                     <td class="hvac-col-actions">
 | |
|                                         <?php $current_status = HVAC_Trainer_Status::get_trainer_status($trainer->ID); ?>
 | |
|                                         <?php if ($current_status === 'pending'): ?>
 | |
|                                             <button type="button" class="hvac-btn hvac-btn-success hvac-btn-sm hvac-approve-btn" 
 | |
|                                                     data-user-id="<?php echo esc_attr($trainer->ID); ?>">
 | |
|                                                 Approve
 | |
|                                             </button>
 | |
|                                             <button type="button" class="hvac-btn hvac-btn-danger hvac-btn-sm hvac-reject-btn" 
 | |
|                                                     data-user-id="<?php echo esc_attr($trainer->ID); ?>">
 | |
|                                                 Reject
 | |
|                                             </button>
 | |
|                                         <?php else: ?>
 | |
|                                             <span class="hvac-status-text">
 | |
|                                                 <?php echo esc_html(ucfirst($current_status)); ?>
 | |
|                                             </span>
 | |
|                                         <?php endif; ?>
 | |
|                                     </td>
 | |
|                                 </tr>
 | |
|                             <?php endforeach; ?>
 | |
|                         </tbody>
 | |
|                     </table>
 | |
|                 <?php endif; ?>
 | |
|             </div>
 | |
|             
 | |
|             <!-- Pagination -->
 | |
|             <?php if ($trainers_data['total'] > $per_page): ?>
 | |
|                 <div class="hvac-pagination">
 | |
|                     <?php
 | |
|                     $total_pages = ceil($trainers_data['total'] / $per_page);
 | |
|                     $base_url = remove_query_arg('paged', $_SERVER['REQUEST_URI']);
 | |
|                     $base_url = add_query_arg(array(
 | |
|                         'status_filter' => $status_filter,
 | |
|                         'region_filter' => $region_filter,
 | |
|                         'date_from' => $date_from,
 | |
|                         'date_to' => $date_to,
 | |
|                         'search' => $search_term
 | |
|                     ), $base_url);
 | |
|                     
 | |
|                     for ($i = 1; $i <= $total_pages; $i++):
 | |
|                         $url = add_query_arg('paged', $i, $base_url);
 | |
|                         $active_class = ($i === $page) ? 'active' : '';
 | |
|                     ?>
 | |
|                         <a href="<?php echo esc_url($url); ?>" class="hvac-page-link <?php echo $active_class; ?>">
 | |
|                             <?php echo $i; ?>
 | |
|                         </a>
 | |
|                     <?php endfor; ?>
 | |
|                 </div>
 | |
|             <?php endif; ?>
 | |
|             
 | |
|         </div>
 | |
|         
 | |
|         <!-- Modals -->
 | |
|         <?php $this->render_modals(); ?>
 | |
|         
 | |
|         <script>
 | |
|         jQuery(document).ready(function($) {
 | |
|             // Initialize pending approvals functionality
 | |
|             if (typeof HVAC_PendingApprovals !== 'undefined') {
 | |
|                 HVAC_PendingApprovals.init();
 | |
|             }
 | |
|         });
 | |
|         </script>
 | |
|         <?php
 | |
|         
 | |
|         return ob_get_clean();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get trainers data with filters
 | |
|      */
 | |
|     private function get_trainers_data($args = array()) {
 | |
|         $defaults = array(
 | |
|             'status' => 'pending',
 | |
|             'region' => '',
 | |
|             'date_from' => '',
 | |
|             'date_to' => '',
 | |
|             'search' => '',
 | |
|             'page' => 1,
 | |
|             'per_page' => 20
 | |
|         );
 | |
|         
 | |
|         $args = wp_parse_args($args, $defaults);
 | |
|         
 | |
|         // Base query args
 | |
|         $query_args = array(
 | |
|             'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
 | |
|             'number' => $args['per_page'],
 | |
|             'offset' => ($args['page'] - 1) * $args['per_page'],
 | |
|             'meta_query' => array(),
 | |
|             'date_query' => array()
 | |
|         );
 | |
|         
 | |
|         // Status filter
 | |
|         if ($args['status'] !== 'all') {
 | |
|             if ($args['status'] === 'rejected') {
 | |
|                 // Handle rejected status - stored as disabled
 | |
|                 $query_args['meta_query'][] = array(
 | |
|                     'key' => 'account_status',
 | |
|                     'value' => 'disabled',
 | |
|                     'compare' => '='
 | |
|                 );
 | |
|             } else {
 | |
|                 $query_args['meta_query'][] = array(
 | |
|                     'key' => 'account_status',
 | |
|                     'value' => $args['status'],
 | |
|                     'compare' => '='
 | |
|                 );
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Date filters
 | |
|         if (!empty($args['date_from']) || !empty($args['date_to'])) {
 | |
|             $date_query = array();
 | |
|             
 | |
|             if (!empty($args['date_from'])) {
 | |
|                 $date_query['after'] = $args['date_from'];
 | |
|             }
 | |
|             
 | |
|             if (!empty($args['date_to'])) {
 | |
|                 $date_query['before'] = $args['date_to'] . ' 23:59:59';
 | |
|             }
 | |
|             
 | |
|             if (!empty($date_query)) {
 | |
|                 $query_args['date_query'] = array($date_query);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Search filter
 | |
|         if (!empty($args['search'])) {
 | |
|             $query_args['search'] = '*' . $args['search'] . '*';
 | |
|             $query_args['search_columns'] = array('display_name', 'user_login', 'user_email');
 | |
|         }
 | |
|         
 | |
|         // Region filter
 | |
|         if (!empty($args['region'])) {
 | |
|             $query_args['meta_query'][] = array(
 | |
|                 'relation' => 'OR',
 | |
|                 array(
 | |
|                     'key' => 'state',
 | |
|                     'value' => $args['region'],
 | |
|                     'compare' => '='
 | |
|                 ),
 | |
|                 array(
 | |
|                     'key' => 'country',
 | |
|                     'value' => $args['region'],
 | |
|                     'compare' => '='
 | |
|                 )
 | |
|             );
 | |
|         }
 | |
|         
 | |
|         // Get total count for pagination
 | |
|         $count_args = $query_args;
 | |
|         unset($count_args['number'], $count_args['offset']);
 | |
|         $total_query = new WP_User_Query($count_args);
 | |
|         $total = $total_query->get_total();
 | |
|         
 | |
|         // Get trainers
 | |
|         $user_query = new WP_User_Query($query_args);
 | |
|         $trainers = $user_query->get_results();
 | |
|         
 | |
|         return array(
 | |
|             'trainers' => $trainers,
 | |
|             'total' => $total,
 | |
|             'query_args' => $query_args
 | |
|         );
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get available regions from trainer data
 | |
|      */
 | |
|     private function get_regions() {
 | |
|         global $wpdb;
 | |
|         
 | |
|         // Get unique states and countries from trainer meta
 | |
|         $regions = $wpdb->get_col(
 | |
|             "SELECT DISTINCT meta_value 
 | |
|              FROM {$wpdb->usermeta} um
 | |
|              INNER JOIN {$wpdb->users} u ON um.user_id = u.ID
 | |
|              INNER JOIN {$wpdb->usermeta} um_role ON u.ID = um_role.user_id
 | |
|              WHERE um.meta_key IN ('state', 'country') 
 | |
|              AND um.meta_value != ''
 | |
|              AND um_role.meta_key = 'wp_capabilities'
 | |
|              AND (um_role.meta_value LIKE '%hvac_trainer%' OR um_role.meta_value LIKE '%hvac_master_trainer%')
 | |
|              ORDER BY meta_value"
 | |
|         );
 | |
|         
 | |
|         return array_filter($regions);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get trainer location string
 | |
|      */
 | |
|     private function get_trainer_location($user_id) {
 | |
|         $city = get_user_meta($user_id, 'city', true);
 | |
|         $state = get_user_meta($user_id, 'state', true);
 | |
|         $country = get_user_meta($user_id, 'country', true);
 | |
|         
 | |
|         $location_parts = array_filter(array($city, $state, $country));
 | |
|         
 | |
|         return !empty($location_parts) ? implode(', ', $location_parts) : 'Not specified';
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get status badge HTML
 | |
|      */
 | |
|     private function get_status_badge($status) {
 | |
|         $badges = array(
 | |
|             'pending' => '<span class="hvac-status-badge hvac-status-pending">Pending</span>',
 | |
|             'approved' => '<span class="hvac-status-badge hvac-status-approved">Approved</span>',
 | |
|             'active' => '<span class="hvac-status-badge hvac-status-active">Active</span>',
 | |
|             'inactive' => '<span class="hvac-status-badge hvac-status-inactive">Inactive</span>',
 | |
|             'disabled' => '<span class="hvac-status-badge hvac-status-rejected">Rejected</span>'
 | |
|         );
 | |
|         
 | |
|         return isset($badges[$status]) ? $badges[$status] : '<span class="hvac-status-badge">' . esc_html(ucfirst($status)) . '</span>';
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Render modal dialogs
 | |
|      */
 | |
|     private function render_modals() {
 | |
|         ?>
 | |
|         <!-- Trainer Details Modal -->
 | |
|         <div id="hvac-trainer-details-modal" class="hvac-modal" style="display: none;">
 | |
|             <div class="hvac-modal-content">
 | |
|                 <div class="hvac-modal-header">
 | |
|                     <h2>Trainer Details</h2>
 | |
|                     <button type="button" class="hvac-modal-close">×</button>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-body">
 | |
|                     <div id="hvac-trainer-details-content">
 | |
|                         Loading...
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-footer">
 | |
|                     <button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Close</button>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|         
 | |
|         <!-- Approval Reason Modal -->
 | |
|         <div id="hvac-approval-reason-modal" class="hvac-modal" style="display: none;">
 | |
|             <div class="hvac-modal-content">
 | |
|                 <div class="hvac-modal-header">
 | |
|                     <h2 id="hvac-reason-modal-title">Approve Trainer</h2>
 | |
|                     <button type="button" class="hvac-modal-close">×</button>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-body">
 | |
|                     <form id="hvac-approval-reason-form">
 | |
|                         <input type="hidden" id="hvac-reason-user-id">
 | |
|                         <input type="hidden" id="hvac-reason-action">
 | |
|                         
 | |
|                         <div class="hvac-form-group">
 | |
|                             <label for="hvac-approval-reason">Reason (optional):</label>
 | |
|                             <textarea id="hvac-approval-reason" rows="4" placeholder="Add a note about this decision..."></textarea>
 | |
|                         </div>
 | |
|                     </form>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-footer">
 | |
|                     <button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Cancel</button>
 | |
|                     <button type="button" class="hvac-btn hvac-btn-primary" id="hvac-confirm-reason-action">
 | |
|                         Confirm
 | |
|                     </button>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|         
 | |
|         <!-- Bulk Action Modal -->
 | |
|         <div id="hvac-bulk-action-modal" class="hvac-modal" style="display: none;">
 | |
|             <div class="hvac-modal-content">
 | |
|                 <div class="hvac-modal-header">
 | |
|                     <h2 id="hvac-bulk-modal-title">Bulk Action</h2>
 | |
|                     <button type="button" class="hvac-modal-close">×</button>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-body">
 | |
|                     <form id="hvac-bulk-action-form">
 | |
|                         <input type="hidden" id="hvac-bulk-action-type">
 | |
|                         
 | |
|                         <p id="hvac-bulk-action-message"></p>
 | |
|                         
 | |
|                         <div class="hvac-form-group">
 | |
|                             <label for="hvac-bulk-reason">Reason (optional):</label>
 | |
|                             <textarea id="hvac-bulk-reason" rows="4" placeholder="Add a note about this bulk action..."></textarea>
 | |
|                         </div>
 | |
|                     </form>
 | |
|                 </div>
 | |
|                 <div class="hvac-modal-footer">
 | |
|                     <button type="button" class="hvac-btn hvac-btn-secondary hvac-modal-close">Cancel</button>
 | |
|                     <button type="button" class="hvac-btn hvac-btn-primary" id="hvac-confirm-bulk-action">
 | |
|                         Confirm
 | |
|                     </button>
 | |
|                 </div>
 | |
|             </div>
 | |
|         </div>
 | |
|         <?php
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * AJAX: Approve trainer
 | |
|      * Enhanced with comprehensive security measures
 | |
|      */
 | |
|     public function ajax_approve_trainer() {
 | |
|         // Use centralized security verification if available
 | |
|         if (class_exists('HVAC_Ajax_Security')) {
 | |
|             $security_check = HVAC_Ajax_Security::verify_ajax_request(
 | |
|                 'approve_trainer',
 | |
|                 'hvac_master_approvals',
 | |
|                 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;
 | |
|             }
 | |
|         } else {
 | |
|             // Fallback to original security check
 | |
|             check_ajax_referer('hvac_master_approvals', 'nonce');
 | |
|             
 | |
|             // Check permissions
 | |
|             if (!$this->can_manage_approvals()) {
 | |
|                 wp_send_json_error(array('message' => 'Insufficient permissions.'), 403);
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Enhanced input validation
 | |
|         $validation_rules = array(
 | |
|             'user_id' => array(
 | |
|                 'type' => 'int',
 | |
|                 'required' => true,
 | |
|                 'min' => 1
 | |
|             ),
 | |
|             'reason' => array(
 | |
|                 'type' => 'textarea',
 | |
|                 'required' => false,
 | |
|                 'max_length' => 1000
 | |
|             )
 | |
|         );
 | |
|         
 | |
|         // Use centralized sanitization if available
 | |
|         if (class_exists('HVAC_Ajax_Security')) {
 | |
|             $data = HVAC_Ajax_Security::sanitize_input($_POST, $validation_rules);
 | |
|             
 | |
|             if (is_wp_error($data)) {
 | |
|                 wp_send_json_error(
 | |
|                     array(
 | |
|                         'message' => $data->get_error_message(),
 | |
|                         'errors' => $data->get_error_data()
 | |
|                     ),
 | |
|                     400
 | |
|                 );
 | |
|                 return;
 | |
|             }
 | |
|             
 | |
|             $user_id = $data['user_id'];
 | |
|             $reason = isset($data['reason']) ? $data['reason'] : '';
 | |
|         } else {
 | |
|             // Fallback to basic sanitization
 | |
|             $user_id = intval($_POST['user_id'] ?? 0);
 | |
|             $reason = sanitize_textarea_field($_POST['reason'] ?? '');
 | |
|             
 | |
|             // Validate length
 | |
|             if (strlen($reason) > 1000) {
 | |
|                 wp_send_json_error(array('message' => 'Reason text too long (max 1000 characters).'), 400);
 | |
|                 return;
 | |
|             }
 | |
|             
 | |
|             if (!$user_id || $user_id < 1) {
 | |
|                 wp_send_json_error(array('message' => 'Invalid user ID.'), 400);
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // Get trainer info with additional validation
 | |
|         $trainer = get_userdata($user_id);
 | |
|         if (!$trainer) {
 | |
|             wp_send_json_error(array('message' => 'Trainer not found.'), 404);
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Verify this is actually a trainer
 | |
|         $is_trainer = in_array('hvac_trainer', $trainer->roles) || 
 | |
|                       get_user_meta($user_id, 'hvac_trainer_status', true);
 | |
|         
 | |
|         if (!$is_trainer) {
 | |
|             wp_send_json_error(array('message' => 'User is not a trainer.'), 400);
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Check if already approved to prevent duplicate processing
 | |
|         $current_status = get_user_meta($user_id, 'hvac_trainer_status', true);
 | |
|         if ($current_status === 'approved') {
 | |
|             wp_send_json_error(array('message' => 'Trainer is already approved.'), 400);
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         // Begin transaction-like operation
 | |
|         $approval_data = array(
 | |
|             'user_id' => $user_id,
 | |
|             'previous_status' => $current_status,
 | |
|             'new_status' => 'approved',
 | |
|             'approved_by' => get_current_user_id(),
 | |
|             'approved_date' => current_time('mysql'),
 | |
|             'reason' => $reason
 | |
|         );
 | |
|         
 | |
|         // Update status using existing system with error handling
 | |
|         try {
 | |
|             $result = false;
 | |
|             
 | |
|             if (class_exists('HVAC_Trainer_Status')) {
 | |
|                 $result = HVAC_Trainer_Status::set_trainer_status($user_id, HVAC_Trainer_Status::STATUS_APPROVED);
 | |
|             } else {
 | |
|                 // Fallback implementation
 | |
|                 update_user_meta($user_id, 'hvac_trainer_status', 'approved');
 | |
|                 update_user_meta($user_id, 'hvac_trainer_approved_date', $approval_data['approved_date']);
 | |
|                 update_user_meta($user_id, 'hvac_trainer_approved_by', $approval_data['approved_by']);
 | |
|                 
 | |
|                 // Ensure trainer role is assigned
 | |
|                 $trainer->add_role('hvac_trainer');
 | |
|                 $result = true;
 | |
|             }
 | |
|             
 | |
|             if ($result) {
 | |
|                 // Store approval reason if provided
 | |
|                 if (!empty($reason)) {
 | |
|                     update_user_meta($user_id, 'hvac_trainer_approval_reason', $reason);
 | |
|                 }
 | |
|                 
 | |
|                 // Log the approval action with enhanced audit trail
 | |
|                 $this->log_approval_action($user_id, 'approved', $reason);
 | |
|                 
 | |
|                 // Additional security logging
 | |
|                 if (class_exists('HVAC_Logger')) {
 | |
|                     HVAC_Logger::info('Trainer approval successful', 'Security', $approval_data);
 | |
|                 }
 | |
|                 
 | |
|                 // Clear any related caches
 | |
|                 delete_transient('hvac_pending_trainers_count');
 | |
|                 delete_transient('hvac_trainers_list_' . $current_status);
 | |
|                 
 | |
|                 wp_send_json_success(array(
 | |
|                     'message' => sprintf('Trainer %s has been approved successfully.', esc_html($trainer->display_name)),
 | |
|                     'user_id' => $user_id,
 | |
|                     'new_status' => 'approved',
 | |
|                     'timestamp' => $approval_data['approved_date']
 | |
|                 ));
 | |
|             } else {
 | |
|                 throw new Exception('Failed to update trainer status');
 | |
|             }
 | |
|         } catch (Exception $e) {
 | |
|             // Log the failure
 | |
|             if (class_exists('HVAC_Logger')) {
 | |
|                 HVAC_Logger::error('Trainer approval failed', 'Security', array(
 | |
|                     'user_id' => $user_id,
 | |
|                     'error' => $e->getMessage(),
 | |
|                     'approval_data' => $approval_data
 | |
|                 ));
 | |
|             }
 | |
|             
 | |
|             wp_send_json_error(array('message' => 'Failed to approve trainer: ' . $e->getMessage()), 500);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * AJAX: Reject trainer
 | |
|      */
 | |
|     public function ajax_reject_trainer() {
 | |
|         // Check nonce
 | |
|         check_ajax_referer('hvac_master_approvals', 'nonce');
 | |
|         
 | |
|         // Check permissions
 | |
|         if (!$this->can_manage_approvals()) {
 | |
|             wp_send_json_error(array('message' => 'Insufficient permissions.'));
 | |
|         }
 | |
|         
 | |
|         $user_id = intval($_POST['user_id'] ?? 0);
 | |
|         $reason = sanitize_textarea_field($_POST['reason'] ?? '');
 | |
|         
 | |
|         if (!$user_id) {
 | |
|             wp_send_json_error(array('message' => 'Invalid user ID.'));
 | |
|         }
 | |
|         
 | |
|         // Get trainer info
 | |
|         $trainer = get_userdata($user_id);
 | |
|         if (!$trainer) {
 | |
|             wp_send_json_error(array('message' => 'Trainer not found.'));
 | |
|         }
 | |
|         
 | |
|         // Update status - use disabled for rejected
 | |
|         $result = HVAC_Trainer_Status::set_trainer_status($user_id, HVAC_Trainer_Status::STATUS_DISABLED);
 | |
|         
 | |
|         if ($result) {
 | |
|             // Log the rejection action
 | |
|             $this->log_approval_action($user_id, 'rejected', $reason);
 | |
|             
 | |
|             wp_send_json_success(array(
 | |
|                 'message' => sprintf('Trainer %s has been rejected.', $trainer->display_name),
 | |
|                 'user_id' => $user_id,
 | |
|                 'new_status' => 'rejected'
 | |
|             ));
 | |
|         } else {
 | |
|             wp_send_json_error(array('message' => 'Failed to reject trainer.'));
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * AJAX: Bulk trainer actions
 | |
|      */
 | |
|     public function ajax_bulk_trainer_action() {
 | |
|         // Check nonce
 | |
|         check_ajax_referer('hvac_master_approvals', 'nonce');
 | |
|         
 | |
|         // Check permissions
 | |
|         if (!$this->can_manage_approvals()) {
 | |
|             wp_send_json_error(array('message' => 'Insufficient permissions.'));
 | |
|         }
 | |
|         
 | |
|         $user_ids = array_map('intval', $_POST['user_ids'] ?? array());
 | |
|         $action = sanitize_text_field($_POST['action'] ?? '');
 | |
|         $reason = sanitize_textarea_field($_POST['reason'] ?? '');
 | |
|         
 | |
|         if (empty($user_ids) || !in_array($action, array('approve', 'reject'))) {
 | |
|             wp_send_json_error(array('message' => 'Invalid parameters.'));
 | |
|         }
 | |
|         
 | |
|         $success_count = 0;
 | |
|         $failed_count = 0;
 | |
|         $results = array();
 | |
|         
 | |
|         foreach ($user_ids as $user_id) {
 | |
|             $trainer = get_userdata($user_id);
 | |
|             if (!$trainer) {
 | |
|                 $failed_count++;
 | |
|                 continue;
 | |
|             }
 | |
|             
 | |
|             $new_status = ($action === 'approve') ? HVAC_Trainer_Status::STATUS_APPROVED : HVAC_Trainer_Status::STATUS_DISABLED;
 | |
|             
 | |
|             if (HVAC_Trainer_Status::set_trainer_status($user_id, $new_status)) {
 | |
|                 $this->log_approval_action($user_id, $action === 'approve' ? 'approved' : 'rejected', $reason);
 | |
|                 $success_count++;
 | |
|                 $results[$user_id] = 'success';
 | |
|             } else {
 | |
|                 $failed_count++;
 | |
|                 $results[$user_id] = 'failed';
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         $message = sprintf(
 | |
|             'Bulk %s completed: %d successful, %d failed',
 | |
|             $action === 'approve' ? 'approval' : 'rejection',
 | |
|             $success_count,
 | |
|             $failed_count
 | |
|         );
 | |
|         
 | |
|         wp_send_json_success(array(
 | |
|             'message' => $message,
 | |
|             'success_count' => $success_count,
 | |
|             'failed_count' => $failed_count,
 | |
|             'results' => $results
 | |
|         ));
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * AJAX: Get trainer details
 | |
|      */
 | |
|     public function ajax_get_trainer_details() {
 | |
|         // Check nonce
 | |
|         check_ajax_referer('hvac_master_approvals', 'nonce');
 | |
|         
 | |
|         // Check permissions
 | |
|         if (!$this->can_manage_approvals()) {
 | |
|             wp_send_json_error(array('message' => 'Insufficient permissions.'));
 | |
|         }
 | |
|         
 | |
|         $user_id = intval($_POST['user_id'] ?? 0);
 | |
|         
 | |
|         if (!$user_id) {
 | |
|             wp_send_json_error(array('message' => 'Invalid user ID.'));
 | |
|         }
 | |
|         
 | |
|         $trainer = get_userdata($user_id);
 | |
|         if (!$trainer) {
 | |
|             wp_send_json_error(array('message' => 'Trainer not found.'));
 | |
|         }
 | |
|         
 | |
|         // Get trainer meta data
 | |
|         $trainer_data = array(
 | |
|             'display_name' => $trainer->display_name,
 | |
|             'user_email' => $trainer->user_email,
 | |
|             'user_registered' => date('F j, Y', strtotime($trainer->user_registered)),
 | |
|             'first_name' => get_user_meta($user_id, 'first_name', true),
 | |
|             'last_name' => get_user_meta($user_id, 'last_name', true),
 | |
|             'phone' => get_user_meta($user_id, 'phone', true),
 | |
|             'business_name' => get_user_meta($user_id, 'business_name', true),
 | |
|             'business_email' => get_user_meta($user_id, 'business_email', true),
 | |
|             'business_phone' => get_user_meta($user_id, 'business_phone', true),
 | |
|             'business_website' => get_user_meta($user_id, 'business_website', true),
 | |
|             'city' => get_user_meta($user_id, 'city', true),
 | |
|             'state' => get_user_meta($user_id, 'state', true),
 | |
|             'country' => get_user_meta($user_id, 'country', true),
 | |
|             'application_details' => get_user_meta($user_id, 'application_details', true),
 | |
|             'business_type' => get_user_meta($user_id, 'business_type', true),
 | |
|             'training_audience' => get_user_meta($user_id, 'training_audience', true),
 | |
|             'status' => HVAC_Trainer_Status::get_trainer_status($user_id),
 | |
|             'approval_log' => get_user_meta($user_id, 'hvac_approval_log', true)
 | |
|         );
 | |
|         
 | |
|         // Build HTML content
 | |
|         ob_start();
 | |
|         ?>
 | |
|         <div class="hvac-trainer-details">
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Personal Information</h3>
 | |
|                 <table class="hvac-details-table">
 | |
|                     <tr><td><strong>Name:</strong></td><td><?php echo esc_html($trainer_data['display_name']); ?></td></tr>
 | |
|                     <tr><td><strong>Email:</strong></td><td><?php echo esc_html($trainer_data['user_email']); ?></td></tr>
 | |
|                     <tr><td><strong>Phone:</strong></td><td><?php echo esc_html($trainer_data['phone'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Registration Date:</strong></td><td><?php echo esc_html($trainer_data['user_registered']); ?></td></tr>
 | |
|                 </table>
 | |
|             </div>
 | |
|             
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Business Information</h3>
 | |
|                 <table class="hvac-details-table">
 | |
|                     <tr><td><strong>Business Name:</strong></td><td><?php echo esc_html($trainer_data['business_name'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Business Email:</strong></td><td><?php echo esc_html($trainer_data['business_email'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Business Phone:</strong></td><td><?php echo esc_html($trainer_data['business_phone'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Website:</strong></td><td><?php echo esc_html($trainer_data['business_website'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Business Type:</strong></td><td><?php echo esc_html($trainer_data['business_type'] ?: 'Not provided'); ?></td></tr>
 | |
|                 </table>
 | |
|             </div>
 | |
|             
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Location</h3>
 | |
|                 <table class="hvac-details-table">
 | |
|                     <tr><td><strong>City:</strong></td><td><?php echo esc_html($trainer_data['city'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>State/Province:</strong></td><td><?php echo esc_html($trainer_data['state'] ?: 'Not provided'); ?></td></tr>
 | |
|                     <tr><td><strong>Country:</strong></td><td><?php echo esc_html($trainer_data['country'] ?: 'Not provided'); ?></td></tr>
 | |
|                 </table>
 | |
|             </div>
 | |
|             
 | |
|             <?php if (!empty($trainer_data['application_details'])): ?>
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Application Details</h3>
 | |
|                 <div class="hvac-application-details">
 | |
|                     <?php echo wp_kses_post(wpautop($trainer_data['application_details'])); ?>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <?php endif; ?>
 | |
|             
 | |
|             <?php if (!empty($trainer_data['approval_log']) && is_array($trainer_data['approval_log'])): ?>
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Approval History</h3>
 | |
|                 <div class="hvac-approval-log">
 | |
|                     <?php foreach ($trainer_data['approval_log'] as $log_entry): ?>
 | |
|                         <div class="hvac-log-entry">
 | |
|                             <strong><?php echo esc_html($log_entry['action']); ?></strong>
 | |
|                             by <?php echo esc_html($log_entry['user']); ?>
 | |
|                             on <?php echo esc_html($log_entry['date']); ?>
 | |
|                             <?php if (!empty($log_entry['reason'])): ?>
 | |
|                                 <br><em>Reason: <?php echo esc_html($log_entry['reason']); ?></em>
 | |
|                             <?php endif; ?>
 | |
|                         </div>
 | |
|                     <?php endforeach; ?>
 | |
|                 </div>
 | |
|             </div>
 | |
|             <?php endif; ?>
 | |
|             
 | |
|             <div class="hvac-details-section">
 | |
|                 <h3>Current Status</h3>
 | |
|                 <p><?php echo $this->get_status_badge($trainer_data['status']); ?></p>
 | |
|             </div>
 | |
|         </div>
 | |
|         <?php
 | |
|         
 | |
|         wp_send_json_success(array(
 | |
|             'html' => ob_get_clean(),
 | |
|             'data' => $trainer_data
 | |
|         ));
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Log approval action to user meta
 | |
|      */
 | |
|     private function log_approval_action($user_id, $action, $reason = '') {
 | |
|         $current_user = wp_get_current_user();
 | |
|         $log_entry = array(
 | |
|             'action' => ucfirst($action),
 | |
|             'user' => $current_user->display_name,
 | |
|             'user_id' => $current_user->ID,
 | |
|             'date' => current_time('mysql'),
 | |
|             'reason' => $reason
 | |
|         );
 | |
|         
 | |
|         // Get existing log
 | |
|         $approval_log = get_user_meta($user_id, 'hvac_approval_log', true);
 | |
|         if (!is_array($approval_log)) {
 | |
|             $approval_log = array();
 | |
|         }
 | |
|         
 | |
|         // Add new entry
 | |
|         $approval_log[] = $log_entry;
 | |
|         
 | |
|         // Store updated log
 | |
|         update_user_meta($user_id, 'hvac_approval_log', $approval_log);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // Initialize the class
 | |
| HVAC_Master_Pending_Approvals::instance();
 |