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