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