🚨 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 ),
|
|
);
|
|
}
|
|
} |