- Implement singleton pattern for HVAC_Enhanced_Settings to prevent duplicate initialization - Fix jQuery selector error by checking for valid hash selectors before using $(href) - Add default email templates with professional copy for trainer notifications - Update plugin version to 1.0.1 for cache busting - Remove duplicate Enhanced Settings initialization from HVAC_Community_Events - Add force cache refresh suffix to admin scripts This resolves the duplicate content issue on email templates page and fixes JavaScript errors in the admin interface. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
413 lines
No EOL
15 KiB
PHP
413 lines
No EOL
15 KiB
PHP
<?php
|
|
/**
|
|
* Attendee Profile Handler
|
|
*
|
|
* Handles the display and data retrieval for attendee profile pages
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Attendee Profile class
|
|
*/
|
|
class HVAC_Attendee_Profile {
|
|
|
|
/**
|
|
* Instance of this class
|
|
*
|
|
* @var HVAC_Attendee_Profile
|
|
*/
|
|
protected static $instance = null;
|
|
|
|
/**
|
|
* Get instance
|
|
*/
|
|
public static function instance() {
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
add_action('init', array($this, 'register_shortcode'));
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
|
add_action('wp_ajax_hvac_get_attendee_timeline', array($this, 'ajax_get_timeline'));
|
|
add_action('wp_ajax_nopriv_hvac_get_attendee_timeline', array($this, 'ajax_get_timeline'));
|
|
}
|
|
|
|
/**
|
|
* Register shortcode
|
|
*/
|
|
public function register_shortcode() {
|
|
add_shortcode('hvac_attendee_profile', array($this, 'render_attendee_profile'));
|
|
}
|
|
|
|
/**
|
|
* Enqueue scripts and styles
|
|
*/
|
|
public function enqueue_scripts() {
|
|
if (!is_page('attendee-profile')) {
|
|
return;
|
|
}
|
|
|
|
// Enqueue custom styles and scripts
|
|
wp_enqueue_style(
|
|
'hvac-attendee-profile',
|
|
HVAC_PLUGIN_URL . 'assets/css/hvac-attendee-profile.css',
|
|
array(),
|
|
HVAC_PLUGIN_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'hvac-attendee-profile',
|
|
HVAC_PLUGIN_URL . 'assets/js/hvac-attendee-profile.js',
|
|
array('jquery'),
|
|
HVAC_PLUGIN_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script('hvac-attendee-profile', 'hvac_attendee', array(
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
'nonce' => wp_create_nonce('hvac_attendee_nonce')
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Render attendee profile
|
|
*/
|
|
public function render_attendee_profile($atts) {
|
|
// Check permissions - trainers and admins only
|
|
if (!current_user_can('manage_hvac_events') && !current_user_can('manage_options')) {
|
|
return '<p>You do not have permission to view attendee profiles.</p>';
|
|
}
|
|
|
|
// Get attendee ID from URL parameter
|
|
$attendee_id = isset($_GET['attendee_id']) ? intval($_GET['attendee_id']) : 0;
|
|
|
|
if (!$attendee_id) {
|
|
return '<p>No attendee specified. Please provide an attendee ID.</p>';
|
|
}
|
|
|
|
// Get attendee data
|
|
$attendee_data = $this->get_attendee_data($attendee_id);
|
|
|
|
if (!$attendee_data) {
|
|
return '<p>Attendee not found.</p>';
|
|
}
|
|
|
|
ob_start();
|
|
include HVAC_PLUGIN_DIR . 'templates/attendee/template-attendee-profile.php';
|
|
return ob_get_clean();
|
|
}
|
|
|
|
/**
|
|
* Get comprehensive attendee data
|
|
*/
|
|
public function get_attendee_data($attendee_id) {
|
|
global $wpdb;
|
|
|
|
// First, try to get attendee as a user
|
|
$user = null;
|
|
$attendee_email = '';
|
|
|
|
// Check if this is an attendee post ID
|
|
$attendee_post = get_post($attendee_id);
|
|
if ($attendee_post && in_array($attendee_post->post_type, array('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees'))) {
|
|
// Get email from attendee meta
|
|
$email_keys = array(
|
|
'_tec_tickets_commerce_email',
|
|
'_tribe_tpp_email',
|
|
'_tribe_tickets_email',
|
|
'_tribe_tpp_attendee_email'
|
|
);
|
|
|
|
foreach ($email_keys as $key) {
|
|
$email = get_post_meta($attendee_id, $key, true);
|
|
if ($email && is_email($email)) {
|
|
$attendee_email = $email;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Try to find user by email
|
|
if ($attendee_email) {
|
|
$user = get_user_by('email', $attendee_email);
|
|
}
|
|
} else {
|
|
// Maybe this is a user ID
|
|
$user = get_user_by('id', $attendee_id);
|
|
if ($user) {
|
|
$attendee_email = $user->user_email;
|
|
}
|
|
}
|
|
|
|
if (!$attendee_email) {
|
|
return false;
|
|
}
|
|
|
|
// Get profile information
|
|
$profile_data = $this->get_profile_info($attendee_email, $user);
|
|
|
|
// Get statistics
|
|
$stats = $this->get_attendee_statistics($attendee_email);
|
|
|
|
// Get timeline data
|
|
$timeline = $this->get_attendee_timeline($attendee_email);
|
|
|
|
return array(
|
|
'profile' => $profile_data,
|
|
'stats' => $stats,
|
|
'timeline' => $timeline,
|
|
'attendee_id' => $attendee_id,
|
|
'user_id' => $user ? $user->ID : 0
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get profile information
|
|
*/
|
|
private function get_profile_info($email, $user = null) {
|
|
global $wpdb;
|
|
|
|
$profile = array(
|
|
'name' => '',
|
|
'email' => $email,
|
|
'phone' => '',
|
|
'company' => '',
|
|
'state' => '',
|
|
'avatar_url' => ''
|
|
);
|
|
|
|
// If we have a user, get their data
|
|
if ($user) {
|
|
$profile['name'] = $user->display_name;
|
|
$profile['phone'] = get_user_meta($user->ID, 'phone', true);
|
|
$profile['company'] = get_user_meta($user->ID, 'company', true);
|
|
$profile['state'] = get_user_meta($user->ID, 'state', true);
|
|
$profile['avatar_url'] = get_avatar_url($user->ID, array('size' => 150));
|
|
}
|
|
|
|
// Try to get name from attendee records if not set
|
|
if (empty($profile['name'])) {
|
|
$name = $wpdb->get_var($wpdb->prepare("
|
|
SELECT COALESCE(
|
|
MAX(CASE WHEN pm.meta_key = '_tec_tickets_commerce_full_name' THEN pm.meta_value END),
|
|
MAX(CASE WHEN pm.meta_key = '_tribe_tpp_full_name' THEN pm.meta_value END),
|
|
MAX(CASE WHEN pm.meta_key = '_tribe_tickets_full_name' THEN pm.meta_value END)
|
|
)
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->postmeta} pm_email ON p.ID = pm_email.post_id
|
|
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE p.post_type IN ('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees')
|
|
AND pm_email.meta_value = %s
|
|
AND pm_email.meta_key IN ('_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email')
|
|
AND pm.meta_key IN ('_tec_tickets_commerce_full_name', '_tribe_tpp_full_name', '_tribe_tickets_full_name')
|
|
LIMIT 1
|
|
", $email));
|
|
|
|
if ($name) {
|
|
$profile['name'] = $name;
|
|
}
|
|
}
|
|
|
|
// If still no name, use email prefix
|
|
if (empty($profile['name'])) {
|
|
$email_parts = explode('@', $email);
|
|
$profile['name'] = ucwords(str_replace(array('.', '_', '-'), ' ', $email_parts[0]));
|
|
}
|
|
|
|
return $profile;
|
|
}
|
|
|
|
/**
|
|
* Get attendee statistics
|
|
*/
|
|
private function get_attendee_statistics($email) {
|
|
global $wpdb;
|
|
|
|
// Get total purchases (unique events registered)
|
|
$total_purchases = $wpdb->get_var($wpdb->prepare("
|
|
SELECT COUNT(DISTINCT p.post_parent)
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE p.post_type IN ('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees')
|
|
AND pm.meta_key IN ('_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email', '_tribe_tpp_attendee_email')
|
|
AND pm.meta_value = %s
|
|
AND p.post_status = 'publish'
|
|
", $email));
|
|
|
|
// Get events registered for
|
|
$events_registered = $total_purchases; // Same as purchases for now
|
|
|
|
// Get events checked in
|
|
$events_checked_in = $wpdb->get_var($wpdb->prepare("
|
|
SELECT COUNT(DISTINCT p.post_parent)
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->postmeta} pm_email ON p.ID = pm_email.post_id
|
|
JOIN {$wpdb->postmeta} pm_checkin ON p.ID = pm_checkin.post_id
|
|
WHERE p.post_type IN ('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees')
|
|
AND pm_email.meta_key IN ('_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email', '_tribe_tpp_attendee_email')
|
|
AND pm_email.meta_value = %s
|
|
AND pm_checkin.meta_key = '_tribe_tickets_attendee_checked_in'
|
|
AND pm_checkin.meta_value = '1'
|
|
AND p.post_status = 'publish'
|
|
", $email));
|
|
|
|
// Get certificates earned
|
|
$certificates_earned = 0;
|
|
if (class_exists('HVAC_Certificate_Manager')) {
|
|
$certificate_manager = HVAC_Certificate_Manager::instance();
|
|
|
|
// Get all attendee IDs for this email
|
|
$attendee_ids = $wpdb->get_col($wpdb->prepare("
|
|
SELECT p.ID
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
WHERE p.post_type IN ('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees')
|
|
AND pm.meta_key IN ('_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email', '_tribe_tpp_attendee_email')
|
|
AND pm.meta_value = %s
|
|
AND p.post_status = 'publish'
|
|
", $email));
|
|
|
|
// Count certificates for these attendees
|
|
foreach ($attendee_ids as $att_id) {
|
|
$certs = $wpdb->get_var($wpdb->prepare("
|
|
SELECT COUNT(*) FROM {$wpdb->prefix}hvac_certificates
|
|
WHERE attendee_id = %d
|
|
", $att_id));
|
|
$certificates_earned += $certs;
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'total_purchases' => intval($total_purchases),
|
|
'events_registered' => intval($events_registered),
|
|
'events_checked_in' => intval($events_checked_in),
|
|
'certificates_earned' => intval($certificates_earned)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get attendee timeline data
|
|
*/
|
|
private function get_attendee_timeline($email) {
|
|
global $wpdb;
|
|
|
|
$timeline_events = array();
|
|
|
|
// Get all attendee records for this email
|
|
$attendee_records = $wpdb->get_results($wpdb->prepare("
|
|
SELECT
|
|
p.ID as attendee_id,
|
|
p.post_parent as event_id,
|
|
p.post_date as registration_date,
|
|
event.post_title as event_title,
|
|
event_date.meta_value as event_date,
|
|
checkin.meta_value as checked_in
|
|
FROM {$wpdb->posts} p
|
|
JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
|
|
JOIN {$wpdb->posts} event ON p.post_parent = event.ID
|
|
LEFT JOIN {$wpdb->postmeta} event_date ON event.ID = event_date.post_id AND event_date.meta_key = '_EventStartDate'
|
|
LEFT JOIN {$wpdb->postmeta} checkin ON p.ID = checkin.post_id AND checkin.meta_key = '_tribe_tickets_attendee_checked_in'
|
|
WHERE p.post_type IN ('tribe_tpp_attendees', 'tec_tc_attendee', 'tribe_rsvp_attendees')
|
|
AND pm.meta_key IN ('_tec_tickets_commerce_email', '_tribe_tpp_email', '_tribe_tickets_email', '_tribe_tpp_attendee_email')
|
|
AND pm.meta_value = %s
|
|
AND p.post_status = 'publish'
|
|
ORDER BY p.post_date DESC
|
|
", $email));
|
|
|
|
foreach ($attendee_records as $record) {
|
|
// Registration event
|
|
$timeline_events[] = array(
|
|
'type' => 'registration',
|
|
'title' => 'Registered for ' . $record->event_title,
|
|
'date' => $record->registration_date,
|
|
'event_id' => $record->event_id,
|
|
'icon' => 'fas fa-ticket-alt',
|
|
'color' => '#007cba'
|
|
);
|
|
|
|
// Event attendance (if event date has passed)
|
|
if ($record->event_date && strtotime($record->event_date) < current_time('timestamp')) {
|
|
$timeline_events[] = array(
|
|
'type' => 'event',
|
|
'title' => 'Attended ' . $record->event_title,
|
|
'date' => $record->event_date,
|
|
'event_id' => $record->event_id,
|
|
'checked_in' => $record->checked_in == '1',
|
|
'icon' => 'fas fa-calendar-check',
|
|
'color' => $record->checked_in == '1' ? '#28a745' : '#6c757d'
|
|
);
|
|
}
|
|
|
|
// Certificate earned
|
|
if (class_exists('HVAC_Certificate_Manager')) {
|
|
$cert = $wpdb->get_row($wpdb->prepare("
|
|
SELECT certificate_number, date_generated
|
|
FROM {$wpdb->prefix}hvac_certificates
|
|
WHERE attendee_id = %d
|
|
LIMIT 1
|
|
", $record->attendee_id));
|
|
|
|
if ($cert) {
|
|
$timeline_events[] = array(
|
|
'type' => 'certificate',
|
|
'title' => 'Certificate earned for ' . $record->event_title,
|
|
'date' => $cert->date_generated,
|
|
'event_id' => $record->event_id,
|
|
'certificate_number' => $cert->certificate_number,
|
|
'icon' => 'fas fa-certificate',
|
|
'color' => '#ffc107'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort timeline by date
|
|
usort($timeline_events, function($a, $b) {
|
|
return strtotime($b['date']) - strtotime($a['date']);
|
|
});
|
|
|
|
return $timeline_events;
|
|
}
|
|
|
|
/**
|
|
* AJAX handler for timeline data
|
|
*/
|
|
public function ajax_get_timeline() {
|
|
check_ajax_referer('hvac_attendee_nonce', 'nonce');
|
|
|
|
if (!current_user_can('manage_hvac_events') && !current_user_can('manage_options')) {
|
|
wp_die('Unauthorized');
|
|
}
|
|
|
|
$attendee_id = isset($_POST['attendee_id']) ? intval($_POST['attendee_id']) : 0;
|
|
|
|
if (!$attendee_id) {
|
|
wp_send_json_error('Invalid attendee ID');
|
|
}
|
|
|
|
$attendee_data = $this->get_attendee_data($attendee_id);
|
|
|
|
if (!$attendee_data) {
|
|
wp_send_json_error('Attendee not found');
|
|
}
|
|
|
|
wp_send_json_success(array(
|
|
'timeline' => $attendee_data['timeline']
|
|
));
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
HVAC_Attendee_Profile::instance(); |