🚨 CRITICAL: Fixed deployment blockers by adding missing core directories: **Community System (CRITICAL)** - includes/community/ - Login_Handler and all community classes - templates/community/ - Community login forms **Certificate System (CRITICAL)** - includes/certificates/ - 8+ certificate classes and handlers - templates/certificates/ - Certificate reports and generation templates **Core Individual Classes (CRITICAL)** - includes/class-hvac-event-summary.php - includes/class-hvac-trainer-profile-manager.php - includes/class-hvac-master-dashboard-data.php - Plus 40+ other individual HVAC classes **Major Feature Systems (HIGH)** - includes/database/ - Training leads database tables - includes/find-trainer/ - Find trainer directory and MapGeo integration - includes/google-sheets/ - Google Sheets integration system - includes/zoho/ - Complete Zoho CRM integration - includes/communication/ - Communication templates system **Template Infrastructure** - templates/attendee/, templates/email-attendees/ - templates/event-summary/, templates/status/ - templates/template-parts/ - Shared template components **Impact:** - 70+ files added covering 10+ missing directories - Resolves ALL deployment blockers and feature breakdowns - Plugin activation should now work correctly - Multi-machine deployment fully supported 🔧 Generated with Claude Code Co-Authored-By: Ben Reed <ben@tealmaker.com>
		
			
				
	
	
		
			470 lines
		
	
	
		
			No EOL
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			470 lines
		
	
	
		
			No EOL
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Community Events - Email Attendees Data Class
 | |
|  *
 | |
|  * Handles retrieving attendee data and sending emails for the Email Attendees functionality.
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @subpackage Community
 | |
|  */
 | |
| 
 | |
| // Exit if accessed directly
 | |
| if ( ! defined( 'ABSPATH' ) ) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Class HVAC_Email_Attendees_Data
 | |
|  *
 | |
|  * Handles data operations for the Email Attendees functionality.
 | |
|  */
 | |
| class HVAC_Email_Attendees_Data {
 | |
| 
 | |
|     /**
 | |
|      * The event ID.
 | |
|      *
 | |
|      * @var int
 | |
|      */
 | |
|     private $event_id;
 | |
| 
 | |
|     /**
 | |
|      * Constructor.
 | |
|      *
 | |
|      * @param int $event_id The event ID.
 | |
|      */
 | |
|     public function __construct( $event_id = 0 ) {
 | |
|         $this->event_id = intval( $event_id );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the event is valid.
 | |
|      *
 | |
|      * @return bool Whether the event exists and is valid.
 | |
|      */
 | |
|     public function is_valid_event() {
 | |
|         if ( empty( $this->event_id ) ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $event = get_post( $this->event_id );
 | |
|         return ( $event && $event->post_type === 'tribe_events' );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Check if the current user can view and email attendees for this event.
 | |
|      *
 | |
|      * @return bool Whether the user can view and email attendees.
 | |
|      */
 | |
|     public function user_can_email_attendees() {
 | |
|         if ( ! is_user_logged_in() ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         $event = get_post( $this->event_id );
 | |
|         if ( ! $event ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // Allow event author or admins with edit_posts capability
 | |
|         return ( get_current_user_id() === (int) $event->post_author || current_user_can( 'edit_posts' ) );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get all attendees for the event.
 | |
|      *
 | |
|      * @return array Array of attendee data.
 | |
|      */
 | |
|     public function get_attendees() {
 | |
|         if ( ! $this->is_valid_event() ) {
 | |
|             return array();
 | |
|         }
 | |
| 
 | |
|         $processed_attendees = array();
 | |
| 
 | |
|         // First try using The Events Calendar's function
 | |
|         if (function_exists('tribe_tickets_get_attendees')) {
 | |
|             $attendees = tribe_tickets_get_attendees( $this->event_id );
 | |
|             
 | |
|             if ( ! empty( $attendees ) ) {
 | |
|                 foreach ( $attendees as $attendee ) {
 | |
|                     $email = isset( $attendee['holder_email'] ) ? $attendee['holder_email'] : '';
 | |
|                     if (empty($email) && isset($attendee['purchaser_email'])) {
 | |
|                         $email = $attendee['purchaser_email'];
 | |
|                     }
 | |
|                     
 | |
|                     $name = isset( $attendee['holder_name'] ) ? $attendee['holder_name'] : '';
 | |
|                     if (empty($name) && isset($attendee['purchaser_name'])) {
 | |
|                         $name = $attendee['purchaser_name'];
 | |
|                     }
 | |
|                     
 | |
|                     $ticket_name = isset( $attendee['ticket_name'] ) ? $attendee['ticket_name'] : '';
 | |
|                     
 | |
|                     // Only include attendees with valid emails
 | |
|                     if ( ! empty( $email ) && is_email( $email ) ) {
 | |
|                         $processed_attendees[] = array(
 | |
|                             'name' => $name,
 | |
|                             'email' => $email,
 | |
|                             'ticket_name' => $ticket_name,
 | |
|                             'attendee_id' => isset( $attendee['attendee_id'] ) ? $attendee['attendee_id'] : 0,
 | |
|                             'order_id' => isset( $attendee['order_id'] ) ? $attendee['order_id'] : 0,
 | |
|                         );
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         // If no attendees found or function doesn't exist, fall back to direct query
 | |
|         if (empty($processed_attendees)) {
 | |
|             $processed_attendees = $this->get_attendees_fallback();
 | |
|         }
 | |
| 
 | |
|         return $processed_attendees;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Fallback method to get attendees directly from the database
 | |
|      * 
 | |
|      * @return array Array of attendee data
 | |
|      */
 | |
|     private function get_attendees_fallback() {
 | |
|         $processed_attendees = array();
 | |
|         
 | |
|         // Query for attendees directly from the database
 | |
|         $attendees_query = new WP_Query([
 | |
|             'post_type' => 'tribe_tpp_attendees',
 | |
|             'posts_per_page' => -1,
 | |
|             'meta_query' => [
 | |
|                 [
 | |
|                     'key' => '_tribe_tpp_event',
 | |
|                     'value' => $this->event_id,
 | |
|                     'compare' => '=',
 | |
|                 ],
 | |
|             ],
 | |
|         ]);
 | |
|         
 | |
|         if ($attendees_query->have_posts()) {
 | |
|             while ($attendees_query->have_posts()) {
 | |
|                 $attendees_query->the_post();
 | |
|                 $attendee_id = get_the_ID();
 | |
|                 
 | |
|                 // Get associated ticket
 | |
|                 $ticket_id = get_post_meta($attendee_id, '_tribe_tpp_product', true);
 | |
|                 $ticket_name = $ticket_id ? get_the_title($ticket_id) : 'General Admission';
 | |
|                 
 | |
|                 // Get purchaser details
 | |
|                 $name = get_post_meta($attendee_id, '_tribe_tickets_full_name', true);
 | |
|                 if (empty($name)) {
 | |
|                     $name = get_post_meta($attendee_id, '_tribe_tpp_full_name', true);
 | |
|                 }
 | |
|                 if (empty($name)) {
 | |
|                     $name = get_the_title($attendee_id);
 | |
|                 }
 | |
|                 
 | |
|                 $email = get_post_meta($attendee_id, '_tribe_tickets_email', true);
 | |
|                 if (empty($email)) {
 | |
|                     $email = get_post_meta($attendee_id, '_tribe_tpp_email', true);
 | |
|                 }
 | |
|                 
 | |
|                 // Get order info
 | |
|                 $order_id = get_post_meta($attendee_id, '_tribe_tpp_order', true);
 | |
|                 
 | |
|                 // Only include attendees with valid emails
 | |
|                 if (!empty($email) && is_email($email)) {
 | |
|                     $processed_attendees[] = array(
 | |
|                         'name' => $name,
 | |
|                         'email' => $email,
 | |
|                         'ticket_name' => $ticket_name,
 | |
|                         'attendee_id' => $attendee_id,
 | |
|                         'order_id' => $order_id,
 | |
|                     );
 | |
|                 }
 | |
|             }
 | |
|             wp_reset_postdata();
 | |
|         }
 | |
|         
 | |
|         return $processed_attendees;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get attendees filtered by ticket type.
 | |
|      *
 | |
|      * @param string $ticket_type The ticket type to filter by.
 | |
|      * @return array Filtered attendees.
 | |
|      */
 | |
|     public function get_attendees_by_ticket_type( $ticket_type ) {
 | |
|         $attendees = $this->get_attendees();
 | |
|         
 | |
|         if ( empty( $ticket_type ) ) {
 | |
|             return $attendees;
 | |
|         }
 | |
| 
 | |
|         return array_filter( $attendees, function( $attendee ) use ( $ticket_type ) {
 | |
|             return $attendee['ticket_name'] === $ticket_type;
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get all ticket types for the event.
 | |
|      *
 | |
|      * @return array Array of ticket types.
 | |
|      */
 | |
|     public function get_ticket_types() {
 | |
|         $attendees = $this->get_attendees();
 | |
|         $ticket_types = array();
 | |
| 
 | |
|         foreach ( $attendees as $attendee ) {
 | |
|             if ( ! empty( $attendee['ticket_name'] ) && ! in_array( $attendee['ticket_name'], $ticket_types ) ) {
 | |
|                 $ticket_types[] = $attendee['ticket_name'];
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $ticket_types;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the event details.
 | |
|      *
 | |
|      * @return array Event details.
 | |
|      */
 | |
|     public function get_event_details() {
 | |
|         if ( ! $this->is_valid_event() ) {
 | |
|             return array();
 | |
|         }
 | |
| 
 | |
|         $event = get_post( $this->event_id );
 | |
| 
 | |
|         return array(
 | |
|             'id' => $this->event_id,
 | |
|             'title' => get_the_title( $event ),
 | |
|             'start_date' => tribe_get_start_date( $event, false, 'F j, Y' ),
 | |
|             'start_time' => tribe_get_start_date( $event, false, 'g:i a' ),
 | |
|             'end_date' => tribe_get_end_date( $event, false, 'F j, Y' ),
 | |
|             'end_time' => tribe_get_end_date( $event, false, 'g:i a' ),
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Send email to attendees.
 | |
|      *
 | |
|      * @param array  $recipients Array of recipient emails or attendee IDs.
 | |
|      * @param string $subject The email subject.
 | |
|      * @param string $message The email message.
 | |
|      * @param string $cc Optional CC email addresses.
 | |
|      * @return array Result with status and message.
 | |
|      */
 | |
|     public function send_email( $recipients, $subject, $message, $cc = '' ) {
 | |
|         // Start debug log
 | |
|         $debug_log = "=== Email Sending Debug ===\n";
 | |
|         
 | |
|         if ( empty( $recipients ) || empty( $subject ) || empty( $message ) ) {
 | |
|             $debug_log .= "Error: Missing required fields\n";
 | |
|             if (class_exists('HVAC_Logger')) {
 | |
|                 HVAC_Logger::error('Email sending failed: Missing required fields', 'Email System');
 | |
|             }
 | |
|             return array(
 | |
|                 'success' => false,
 | |
|                 'message' => 'Missing required fields (recipients, subject, or message).',
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         if ( ! $this->is_valid_event() || ! $this->user_can_email_attendees() ) {
 | |
|             $debug_log .= "Error: Permission denied\n";
 | |
|             if (class_exists('HVAC_Logger')) {
 | |
|                 HVAC_Logger::error('Email sending failed: Permission denied', 'Email System');
 | |
|             }
 | |
|             return array(
 | |
|                 'success' => false,
 | |
|                 'message' => 'You do not have permission to email attendees for this event.',
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         $headers = array('Content-Type: text/html; charset=UTF-8');
 | |
|         $event_details = $this->get_event_details();
 | |
|         $event_title = $event_details['title'];
 | |
|         $debug_log .= "Event: {$event_title} (ID: {$this->event_id})\n";
 | |
|         
 | |
|         // Add CC if provided
 | |
|         if ( ! empty( $cc ) ) {
 | |
|             $cc_emails = explode( ',', $cc );
 | |
|             foreach ( $cc_emails as $cc_email ) {
 | |
|                 $cc_email = trim( $cc_email );
 | |
|                 if ( is_email( $cc_email ) ) {
 | |
|                     $headers[] = 'Cc: ' . $cc_email;
 | |
|                     $debug_log .= "Added CC: {$cc_email}\n";
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Add sender information from the logged-in trainer
 | |
|         $current_user = wp_get_current_user();
 | |
|         
 | |
|         // Get trainer profile data if available
 | |
|         $trainer_name = $current_user->display_name;
 | |
|         $trainer_email = $current_user->user_email;
 | |
|         
 | |
|         // Check if user is a trainer and has profile data
 | |
|         if (in_array('hvac_trainer', $current_user->roles)) {
 | |
|             // Try to get trainer business name first
 | |
|             $business_name = get_user_meta($current_user->ID, 'business_name', true);
 | |
|             if (!empty($business_name)) {
 | |
|                 $trainer_name = $business_name;
 | |
|             }
 | |
|             
 | |
|             // Try to get trainer contact email if different
 | |
|             $contact_email = get_user_meta($current_user->ID, 'contact_email', true);
 | |
|             if (!empty($contact_email) && is_email($contact_email)) {
 | |
|                 $trainer_email = $contact_email;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         $from_name = $trainer_name;
 | |
|         $from_email = $trainer_email;
 | |
|         $headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
 | |
|         $debug_log .= "From: {$from_name} <{$from_email}>\n";
 | |
|         $debug_log .= "User role: " . implode(', ', $current_user->roles) . "\n";
 | |
| 
 | |
|         // Process recipients
 | |
|         $all_attendees = $this->get_attendees();
 | |
|         $debug_log .= "Total attendees found: " . count($all_attendees) . "\n";
 | |
|         
 | |
|         $attendee_emails = array();
 | |
|         $sent_count = 0;
 | |
|         $error_count = 0;
 | |
| 
 | |
|         $debug_log .= "Recipients provided: " . count($recipients) . "\n";
 | |
|         
 | |
|         // Handle numeric IDs or email addresses
 | |
|         foreach ( $recipients as $recipient ) {
 | |
|             $debug_log .= "Processing recipient: {$recipient}\n";
 | |
|             
 | |
|             if ( is_numeric( $recipient ) ) {
 | |
|                 $debug_log .= "Recipient is numeric ID\n";
 | |
|                 // Find attendee by ID
 | |
|                 foreach ( $all_attendees as $attendee ) {
 | |
|                     if ( $attendee['attendee_id'] == $recipient ) {
 | |
|                         $attendee_emails[$attendee['email']] = $attendee['name'];
 | |
|                         $debug_log .= "Matched with attendee: {$attendee['name']} <{$attendee['email']}>\n";
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             } elseif ( is_email( $recipient ) ) {
 | |
|                 $debug_log .= "Recipient is email address\n";
 | |
|                 // Add directly if it's an email
 | |
|                 $attendee_name = '';
 | |
|                 foreach ( $all_attendees as $attendee ) {
 | |
|                     if ( $attendee['email'] === $recipient ) {
 | |
|                         $attendee_name = $attendee['name'];
 | |
|                         $debug_log .= "Matched with attendee name: {$attendee_name}\n";
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 $attendee_emails[$recipient] = $attendee_name;
 | |
|             } else {
 | |
|                 $debug_log .= "Invalid recipient format\n";
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $debug_log .= "Recipients to email: " . count($attendee_emails) . "\n";
 | |
|         
 | |
|         if (empty($attendee_emails)) {
 | |
|             $debug_log .= "No valid recipients found! Using fallback to direct send.\n";
 | |
|             
 | |
|             // Fallback - directly use the first selected email
 | |
|             foreach ($recipients as $recipient) {
 | |
|                 if (is_email($recipient)) {
 | |
|                     $attendee_emails[$recipient] = '';
 | |
|                     $debug_log .= "Added direct recipient: {$recipient}\n";
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Subject with event title
 | |
|         $email_subject = sprintf( '[%s] %s', $event_title, $subject );
 | |
|         $debug_log .= "Email subject: {$email_subject}\n";
 | |
| 
 | |
|         // Send to each recipient individually for personalization
 | |
|         foreach ( $attendee_emails as $email => $name ) {
 | |
|             $debug_log .= "Sending to: {$email}\n";
 | |
|             
 | |
|             // Personalize message with attendee name if available
 | |
|             $personalized_message = $message;
 | |
|             if ( ! empty( $name ) ) {
 | |
|                 $personalized_message = "Hello " . $name . ",\n\n" . $message;
 | |
|                 $debug_log .= "Personalized with name: {$name}\n";
 | |
|             }
 | |
| 
 | |
|             // Log complete mail params for debugging
 | |
|             $debug_log .= "Mail parameters:\n";
 | |
|             $debug_log .= "To: {$email}\n";
 | |
|             $debug_log .= "Subject: {$email_subject}\n";
 | |
|             $debug_log .= "Headers: " . print_r($headers, true) . "\n";
 | |
|             
 | |
|             // Note: consolidated error logging is added below
 | |
|             
 | |
|             // Add detailed logging
 | |
|             $debug_log .= "Headers: " . print_r($headers, true) . "\n";
 | |
|             $debug_log .= "Sending mail with wp_mail()\n";
 | |
|             
 | |
|             // Add robust error logging
 | |
|             add_action('wp_mail_failed', function($wp_error) use (&$debug_log) {
 | |
|                 $debug_log .= "Mail error: " . $wp_error->get_error_message() . "\n";
 | |
|                 $debug_log .= "Error data: " . print_r($wp_error->get_error_data(), true) . "\n";
 | |
|                 if (class_exists('HVAC_Logger')) {
 | |
|                     HVAC_Logger::error('WordPress Mail Error: ' . $wp_error->get_error_message() . ' - ' . print_r($wp_error->get_error_data(), true), 'Email System');
 | |
|                 }
 | |
|             });
 | |
|             
 | |
|             // Try to log environment information
 | |
|             $debug_log .= "Mail environment:\n";
 | |
|             $debug_log .= "WordPress version: " . get_bloginfo('version') . "\n";
 | |
|             if (function_exists('phpversion')) {
 | |
|                 $debug_log .= "PHP version: " . phpversion() . "\n";
 | |
|             }
 | |
|             
 | |
|             // Check if WP Mail SMTP is active
 | |
|             $active_plugins = get_option('active_plugins', array());
 | |
|             $wp_mail_smtp_active = false;
 | |
|             foreach ($active_plugins as $plugin) {
 | |
|                 if (strpos($plugin, 'wp-mail-smtp') !== false) {
 | |
|                     $wp_mail_smtp_active = true;
 | |
|                     $debug_log .= "WP Mail SMTP plugin is active\n";
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // Send with standard wp_mail
 | |
|             $mail_sent = wp_mail($email, $email_subject, wpautop($personalized_message), $headers);
 | |
|             
 | |
|             $debug_log .= "wp_mail result: " . ($mail_sent ? 'Success' : 'Failed') . "\n";
 | |
|             
 | |
|             if ( $mail_sent ) {
 | |
|                 $sent_count++;
 | |
|             } else {
 | |
|                 $error_count++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Log the complete debug information
 | |
|         if (class_exists('HVAC_Logger')) {
 | |
|             HVAC_Logger::info($debug_log, 'Email System');
 | |
|         }
 | |
|         
 | |
|         // Return results
 | |
|         if ( $error_count > 0 ) {
 | |
|             return array(
 | |
|                 'success' => $sent_count > 0,
 | |
|                 'message' => sprintf( 
 | |
|                     'Email sent to %d recipients. Failed to send to %d recipients.', 
 | |
|                     $sent_count, 
 | |
|                     $error_count 
 | |
|                 ),
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         return array(
 | |
|             'success' => true,
 | |
|             'message' => sprintf( 'Email successfully sent to %d recipients.', $sent_count ),
 | |
|         );
 | |
|     }
 | |
| } |