upskill-event-manager/includes/class-hvac-training-leads.php
Ben 3ca11601e1 feat: Major architecture overhaul and critical fixes
CRITICAL FIXES:
- Fix browser-crashing CSS system (reduced 686 to 47 files)
- Remove segfault-causing monitoring components (7 classes)
- Eliminate code duplication (removed 5 duplicate class versions)
- Implement security framework and fix vulnerabilities
- Remove theme-specific code (now theme-agnostic)
- Consolidate event management (8 implementations to 1)
- Overhaul template system (45 templates to 10)
- Replace SSH passwords with key authentication

PERFORMANCE:
- 93% reduction in CSS files
- 85% fewer HTTP requests
- No more Safari crashes
- Memory-efficient event management

SECURITY:
- Created HVAC_Security_Helpers framework
- Fixed authorization bypasses
- Added input sanitization
- Implemented SSH key deployment

COMPLIANCE:
- 100% WordPress guidelines compliant
- Theme-independent architecture
- Ready for WordPress.org submission

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-20 19:35:22 -03:00

738 lines
No EOL
25 KiB
PHP

<?php
/**
* Training Leads Management
*
* @package HVAC_Community_Events
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* HVAC_Training_Leads class
*/
class HVAC_Training_Leads {
/**
* Instance of this class
*
* @var HVAC_Training_Leads
*/
private static $instance = null;
/**
* Get instance of this class
*
* @return HVAC_Training_Leads
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Constructor
*/
public function __construct() {
add_action('init', array($this, 'init_hooks'));
}
/**
* Initialize hooks
*/
public function init_hooks() {
// Register shortcode
add_shortcode('hvac_trainer_training_leads', array($this, 'render_training_leads_page'));
// AJAX handlers
add_action('wp_ajax_hvac_update_lead_status', array($this, 'ajax_update_lead_status'));
add_action('wp_ajax_hvac_mark_lead_replied', array($this, 'ajax_mark_lead_replied'));
}
/**
* Render the Training Leads page
*/
public function render_training_leads_page($atts = array()) {
if (!is_user_logged_in()) {
return '<p>Please log in to view your training leads.</p>';
}
// Check user capabilities
$user = wp_get_current_user();
if (!in_array('hvac_trainer', $user->roles) && !in_array('hvac_master_trainer', $user->roles) && !current_user_can('manage_options')) {
return '<p>You do not have permission to view this page.</p>';
}
// Get current user
$current_user = wp_get_current_user();
// Get submissions for this trainer
$submissions = $this->get_trainer_submissions($current_user->ID);
ob_start();
?>
<div class="hvac-training-leads-wrapper">
<div class="hvac-page-header">
<h1>Training Leads</h1>
<p class="hvac-page-description">Manage contact requests from potential training clients</p>
</div>
<?php if (!empty($submissions)) : ?>
<div class="hvac-leads-table-wrapper">
<table class="hvac-leads-table">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Location</th>
<th>Message</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($submissions as $submission) : ?>
<tr class="hvac-lead-row" data-lead-id="<?php echo esc_attr($submission->id); ?>">
<td class="hvac-lead-date">
<?php echo esc_html(date('M j, Y', strtotime($submission->submission_date))); ?>
</td>
<td class="hvac-lead-name">
<?php echo esc_html($submission->first_name . ' ' . $submission->last_name); ?>
</td>
<td class="hvac-lead-email">
<a href="mailto:<?php echo esc_attr($submission->email); ?>">
<?php echo esc_html($submission->email); ?>
</a>
</td>
<td class="hvac-lead-phone">
<?php if ($submission->phone) : ?>
<a href="tel:<?php echo esc_attr($submission->phone); ?>">
<?php echo esc_html($submission->phone); ?>
</a>
<?php else : ?>
<span class="hvac-no-data">—</span>
<?php endif; ?>
</td>
<td class="hvac-lead-location">
<?php
$location_parts = array_filter([
$submission->city,
$submission->state_province
]);
if (!empty($location_parts)) {
echo esc_html(implode(', ', $location_parts));
} else {
echo '<span class="hvac-no-data">—</span>';
}
?>
</td>
<td class="hvac-lead-message">
<?php if ($submission->message) : ?>
<div class="hvac-message-preview" title="<?php echo esc_attr($submission->message); ?>">
<?php echo esc_html(wp_trim_words($submission->message, 8, '...')); ?>
</div>
<?php if (strlen($submission->message) > 50) : ?>
<button class="hvac-btn hvac-btn-small hvac-view-message"
data-message="<?php echo esc_attr($submission->message); ?>">
View Full
</button>
<?php endif; ?>
<?php else : ?>
<span class="hvac-no-data">—</span>
<?php endif; ?>
</td>
<td class="hvac-lead-status">
<span class="hvac-status-badge hvac-status-<?php echo esc_attr($submission->status); ?>">
<?php echo esc_html(ucfirst($submission->status)); ?>
</span>
</td>
<td class="hvac-lead-actions">
<div class="hvac-action-buttons">
<?php if ($submission->status === 'new') : ?>
<button class="hvac-btn hvac-btn-small hvac-mark-read"
data-lead-id="<?php echo esc_attr($submission->id); ?>">
Mark Read
</button>
<?php endif; ?>
<?php if ($submission->status !== 'replied') : ?>
<button class="hvac-btn hvac-btn-small hvac-btn-success hvac-mark-replied"
data-lead-id="<?php echo esc_attr($submission->id); ?>">
Mark Replied
</button>
<?php endif; ?>
<button class="hvac-btn hvac-btn-small hvac-btn-secondary hvac-archive-lead"
data-lead-id="<?php echo esc_attr($submission->id); ?>">
Archive
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else : ?>
<div class="hvac-no-leads">
<div class="hvac-empty-state">
<div class="hvac-empty-icon">
<span class="dashicons dashicons-email-alt"></span>
</div>
<h3>No inbound training requests</h3>
<p>When potential clients contact you through the "Find a Trainer" directory, their messages will appear here.</p>
</div>
</div>
<?php endif; ?>
<div class="hvac-leads-cta">
<div class="hvac-cta-card">
<h3>Want more training leads?</h3>
<p>Share your profile with the world to attract more potential clients!</p>
<a href="<?php echo esc_url(home_url('/trainer/profile/')); ?>"
class="hvac-btn hvac-btn-primary hvac-btn-large">
Share your profile with the world!
</a>
</div>
</div>
</div>
<!-- Message Modal -->
<div id="hvac-message-modal" class="hvac-modal" style="display: none;">
<div class="hvac-modal-content">
<div class="hvac-modal-header">
<h4>Full Message</h4>
<button class="hvac-modal-close">&times;</button>
</div>
<div class="hvac-modal-body">
<div id="hvac-full-message"></div>
</div>
<div class="hvac-modal-footer">
<button class="hvac-btn hvac-btn-secondary hvac-modal-close">Close</button>
</div>
</div>
</div>
<script type="text/javascript">
jQuery(document).ready(function($) {
// Handle status updates
$('.hvac-mark-read, .hvac-mark-replied, .hvac-archive-lead').on('click', function(e) {
e.preventDefault();
var $button = $(this);
var leadId = $button.data('lead-id');
var action = '';
var newStatus = '';
if ($button.hasClass('hvac-mark-read')) {
action = 'hvac_update_lead_status';
newStatus = 'read';
} else if ($button.hasClass('hvac-mark-replied')) {
action = 'hvac_mark_lead_replied';
newStatus = 'replied';
} else if ($button.hasClass('hvac-archive-lead')) {
action = 'hvac_update_lead_status';
newStatus = 'archived';
}
$button.prop('disabled', true).text('Processing...');
$.ajax({
url: hvac_ajax.url,
type: 'POST',
data: {
action: action,
lead_id: leadId,
status: newStatus,
nonce: hvac_ajax.nonce
},
success: function(response) {
if (response.success) {
location.reload(); // Refresh to show updated status
} else {
alert('Error: ' + (response.data.message || 'Unknown error'));
$button.prop('disabled', false).text($button.hasClass('hvac-mark-read') ? 'Mark Read' :
$button.hasClass('hvac-mark-replied') ? 'Mark Replied' : 'Archive');
}
},
error: function() {
alert('Network error. Please try again.');
$button.prop('disabled', false).text($button.hasClass('hvac-mark-read') ? 'Mark Read' :
$button.hasClass('hvac-mark-replied') ? 'Mark Replied' : 'Archive');
}
});
});
// Handle message modal
$('.hvac-view-message').on('click', function(e) {
e.preventDefault();
var message = $(this).data('message');
$('#hvac-full-message').html(message.replace(/\n/g, '<br>'));
$('#hvac-message-modal').fadeIn();
});
$('.hvac-modal-close').on('click', function(e) {
e.preventDefault();
$('#hvac-message-modal').fadeOut();
});
// Close modal on outside click
$('#hvac-message-modal').on('click', function(e) {
if (e.target === this) {
$(this).fadeOut();
}
});
});
</script>
<style>
.hvac-training-leads-wrapper {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.hvac-page-header {
margin-bottom: 30px;
}
.hvac-page-header h1 {
margin: 0 0 10px 0;
font-size: 28px;
color: #1d2327;
}
.hvac-page-description {
margin: 0;
color: #646970;
font-size: 16px;
}
.hvac-leads-table-wrapper {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 30px;
}
.hvac-leads-table {
width: 100%;
border-collapse: collapse;
}
.hvac-leads-table th,
.hvac-leads-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e5e5e5;
}
.hvac-leads-table th {
background: #f8f9fa;
font-weight: 600;
color: #1d2327;
font-size: 14px;
}
.hvac-leads-table tbody tr:hover {
background: #f8f9fa;
}
.hvac-lead-date,
.hvac-lead-phone {
white-space: nowrap;
}
.hvac-lead-email a,
.hvac-lead-phone a {
color: #0073aa;
text-decoration: none;
}
.hvac-lead-email a:hover,
.hvac-lead-phone a:hover {
text-decoration: underline;
}
.hvac-message-preview {
max-width: 200px;
word-wrap: break-word;
}
.hvac-no-data {
color: #999;
font-style: italic;
}
.hvac-status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
white-space: nowrap;
}
.hvac-status-new {
background: #fff3cd;
color: #856404;
}
.hvac-status-read {
background: #d1ecf1;
color: #0c5460;
}
.hvac-status-replied {
background: #d4edda;
color: #155724;
}
.hvac-status-archived {
background: #f8d7da;
color: #721c24;
}
.hvac-action-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.hvac-btn {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 14px;
font-weight: 500;
text-align: center;
transition: all 0.2s ease;
}
.hvac-btn-small {
padding: 4px 8px;
font-size: 12px;
}
.hvac-btn-large {
padding: 12px 24px;
font-size: 16px;
}
.hvac-btn-primary {
background: #0073aa;
color: #fff;
}
.hvac-btn-primary:hover {
background: #005a87;
color: #fff;
}
.hvac-btn-success {
background: #28a745;
color: #fff;
}
.hvac-btn-success:hover {
background: #218838;
color: #fff;
}
.hvac-btn-secondary {
background: #6c757d;
color: #fff;
}
.hvac-btn-secondary:hover {
background: #545b62;
color: #fff;
}
.hvac-no-leads {
text-align: center;
padding: 60px 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.hvac-empty-icon {
font-size: 48px;
color: #c3c4c7;
margin-bottom: 20px;
}
.hvac-empty-state h3 {
margin: 0 0 10px 0;
color: #1d2327;
font-size: 20px;
}
.hvac-empty-state p {
margin: 0;
color: #646970;
font-size: 16px;
}
.hvac-leads-cta {
text-align: center;
}
.hvac-cta-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 40px 30px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.hvac-cta-card h3 {
margin: 0 0 10px 0;
font-size: 24px;
}
.hvac-cta-card p {
margin: 0 0 20px 0;
font-size: 16px;
opacity: 0.9;
}
.hvac-cta-card .hvac-btn-primary {
background: #fff;
color: #667eea;
border: none;
}
.hvac-cta-card .hvac-btn-primary:hover {
background: #f8f9fa;
color: #667eea;
transform: translateY(-2px);
}
/* Modal styles */
.hvac-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.hvac-modal-content {
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow: hidden;
}
.hvac-modal-header {
padding: 20px 25px;
border-bottom: 1px solid #e5e5e5;
display: flex;
justify-content: space-between;
align-items: center;
}
.hvac-modal-header h4 {
margin: 0;
font-size: 18px;
color: #1d2327;
}
.hvac-modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #646970;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.hvac-modal-close:hover {
color: #1d2327;
}
.hvac-modal-body {
padding: 25px;
max-height: 400px;
overflow-y: auto;
}
.hvac-modal-footer {
padding: 20px 25px;
border-top: 1px solid #e5e5e5;
text-align: right;
}
/* Responsive design */
@media (max-width: 1200px) {
.hvac-training-leads-wrapper {
padding: 15px;
}
.hvac-leads-table-wrapper {
overflow-x: auto;
}
.hvac-leads-table {
min-width: 800px;
}
}
@media (max-width: 768px) {
.hvac-page-header h1 {
font-size: 24px;
}
.hvac-leads-table th,
.hvac-leads-table td {
padding: 8px 12px;
font-size: 14px;
}
.hvac-action-buttons {
flex-direction: column;
}
.hvac-cta-card {
padding: 30px 20px;
}
.hvac-cta-card h3 {
font-size: 20px;
}
}
</style>
<?php
return ob_get_clean();
}
/**
* Get contact submissions for a specific trainer
*/
private function get_trainer_submissions($trainer_id) {
if (!class_exists('HVAC_Contact_Submissions_Table')) {
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
}
return HVAC_Contact_Submissions_Table::get_submissions([
'trainer_id' => $trainer_id,
'limit' => 100,
'orderby' => 'submission_date',
'order' => 'DESC'
]);
}
/**
* AJAX handler to update lead status
*/
public function ajax_update_lead_status() {
check_ajax_referer('hvac_ajax_nonce', 'nonce');
$user = wp_get_current_user();
if (!is_user_logged_in() || (!in_array('hvac_trainer', $user->roles) && !in_array('hvac_master_trainer', $user->roles) && !current_user_can('manage_options'))) {
wp_send_json_error(['message' => 'Unauthorized']);
}
$lead_id = isset($_POST['lead_id']) ? absint($_POST['lead_id']) : 0;
$status = isset($_POST['status']) ? sanitize_text_field($_POST['status']) : '';
if (!$lead_id || !$status) {
wp_send_json_error(['message' => 'Invalid parameters']);
}
// Verify the lead belongs to the current user
if (!$this->verify_lead_ownership($lead_id, get_current_user_id())) {
wp_send_json_error(['message' => 'Access denied']);
}
if (!class_exists('HVAC_Contact_Submissions_Table')) {
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
}
$result = HVAC_Contact_Submissions_Table::update_status($lead_id, $status);
if ($result) {
wp_send_json_success(['message' => 'Status updated successfully']);
} else {
wp_send_json_error(['message' => 'Failed to update status']);
}
}
/**
* AJAX handler to mark lead as replied
*/
public function ajax_mark_lead_replied() {
check_ajax_referer('hvac_ajax_nonce', 'nonce');
$user = wp_get_current_user();
if (!is_user_logged_in() || (!in_array('hvac_trainer', $user->roles) && !in_array('hvac_master_trainer', $user->roles) && !current_user_can('manage_options'))) {
wp_send_json_error(['message' => 'Unauthorized']);
}
$lead_id = isset($_POST['lead_id']) ? absint($_POST['lead_id']) : 0;
if (!$lead_id) {
wp_send_json_error(['message' => 'Invalid lead ID']);
}
// Verify the lead belongs to the current user
if (!$this->verify_lead_ownership($lead_id, get_current_user_id())) {
wp_send_json_error(['message' => 'Access denied']);
}
if (!class_exists('HVAC_Contact_Submissions_Table')) {
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
}
$result = HVAC_Contact_Submissions_Table::update_status($lead_id, 'replied');
if ($result) {
wp_send_json_success(['message' => 'Lead marked as replied']);
} else {
wp_send_json_error(['message' => 'Failed to update status']);
}
}
/**
* Verify that a lead belongs to the current user
*/
private function verify_lead_ownership($lead_id, $user_id) {
if (!class_exists('HVAC_Contact_Submissions_Table')) {
require_once HVAC_PLUGIN_DIR . 'includes/database/class-hvac-contact-submissions-table.php';
}
$submission = HVAC_Contact_Submissions_Table::get_submission($lead_id);
return $submission && $submission->trainer_id == $user_id;
}
}
// Initialize the class
HVAC_Training_Leads::get_instance();