🚨 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>
519 lines
No EOL
18 KiB
PHP
519 lines
No EOL
18 KiB
PHP
<?php
|
|
/**
|
|
* HVAC Community Events - Communication Trigger Engine
|
|
*
|
|
* Handles automation logic, recipient management, and email execution.
|
|
* Processes trigger conditions and manages communication delivery.
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @subpackage Communication
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Class HVAC_Communication_Trigger_Engine
|
|
*
|
|
* Manages trigger processing and communication execution.
|
|
*/
|
|
class HVAC_Communication_Trigger_Engine {
|
|
|
|
/**
|
|
* Constructor
|
|
*/
|
|
public function __construct() {
|
|
// Initialize any required hooks or filters
|
|
}
|
|
|
|
/**
|
|
* Calculate trigger time based on event date and schedule configuration
|
|
*
|
|
* @param string $event_date Event start date (MySQL format)
|
|
* @param array $schedule Schedule configuration
|
|
* @return string|null MySQL datetime string for trigger time
|
|
*/
|
|
public function calculate_trigger_time( $event_date, $schedule ) {
|
|
if ( empty( $event_date ) || empty( $schedule['trigger_type'] ) ) {
|
|
return null;
|
|
}
|
|
|
|
$event_timestamp = strtotime( $event_date );
|
|
if ( ! $event_timestamp ) {
|
|
return null;
|
|
}
|
|
|
|
$trigger_value = intval( $schedule['trigger_value'] );
|
|
$trigger_unit = $schedule['trigger_unit'];
|
|
|
|
// Convert trigger unit to seconds
|
|
$seconds_multiplier = $this->get_unit_multiplier( $trigger_unit );
|
|
if ( ! $seconds_multiplier ) {
|
|
return null;
|
|
}
|
|
|
|
$offset_seconds = $trigger_value * $seconds_multiplier;
|
|
|
|
switch ( $schedule['trigger_type'] ) {
|
|
case 'before_event':
|
|
$trigger_timestamp = $event_timestamp - $offset_seconds;
|
|
break;
|
|
case 'after_event':
|
|
// Use event end date if available, otherwise start date
|
|
$event_end_date = get_post_meta( $schedule['event_id'], '_EventEndDate', true );
|
|
$end_timestamp = $event_end_date ? strtotime( $event_end_date ) : $event_timestamp;
|
|
$trigger_timestamp = $end_timestamp + $offset_seconds;
|
|
break;
|
|
case 'on_registration':
|
|
// Immediate trigger - return current time
|
|
return current_time( 'mysql' );
|
|
case 'custom_date':
|
|
// Custom date should be provided in schedule data
|
|
return isset( $schedule['custom_date'] ) ? $schedule['custom_date'] : null;
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
// Ensure trigger time is in the future
|
|
if ( $trigger_timestamp <= time() ) {
|
|
return null;
|
|
}
|
|
|
|
return date( 'Y-m-d H:i:s', $trigger_timestamp );
|
|
}
|
|
|
|
/**
|
|
* Get unit multiplier for converting to seconds
|
|
*
|
|
* @param string $unit Time unit
|
|
* @return int|false Multiplier or false if invalid
|
|
*/
|
|
private function get_unit_multiplier( $unit ) {
|
|
$multipliers = array(
|
|
'minutes' => MINUTE_IN_SECONDS,
|
|
'hours' => HOUR_IN_SECONDS,
|
|
'days' => DAY_IN_SECONDS,
|
|
'weeks' => WEEK_IN_SECONDS
|
|
);
|
|
|
|
return isset( $multipliers[$unit] ) ? $multipliers[$unit] : false;
|
|
}
|
|
|
|
/**
|
|
* Get recipients for a schedule based on target audience settings
|
|
*
|
|
* @param array $schedule Schedule configuration
|
|
* @return array Array of recipient data
|
|
*/
|
|
public function get_schedule_recipients( $schedule ) {
|
|
$recipients = array();
|
|
|
|
switch ( $schedule['target_audience'] ) {
|
|
case 'all_attendees':
|
|
$recipients = $this->get_all_event_attendees( $schedule['event_id'] );
|
|
break;
|
|
case 'confirmed_attendees':
|
|
$recipients = $this->get_confirmed_attendees( $schedule['event_id'] );
|
|
break;
|
|
case 'pending_attendees':
|
|
$recipients = $this->get_pending_attendees( $schedule['event_id'] );
|
|
break;
|
|
case 'custom_list':
|
|
$recipients = $this->parse_custom_recipient_list( $schedule['custom_recipient_list'] );
|
|
break;
|
|
}
|
|
|
|
// Apply additional conditions if specified
|
|
if ( ! empty( $schedule['conditions'] ) ) {
|
|
$recipients = $this->apply_recipient_conditions( $recipients, $schedule['conditions'] );
|
|
}
|
|
|
|
return $recipients;
|
|
}
|
|
|
|
/**
|
|
* Get all attendees for an event
|
|
*
|
|
* @param int $event_id Event ID
|
|
* @return array Array of attendee data
|
|
*/
|
|
private function get_all_event_attendees( $event_id ) {
|
|
if ( empty( $event_id ) ) {
|
|
return array();
|
|
}
|
|
|
|
// Use the Email Attendees Data class for consistent attendee retrieval
|
|
$email_data = new HVAC_Email_Attendees_Data( $event_id );
|
|
$attendees = $email_data->get_attendees();
|
|
|
|
$recipients = array();
|
|
foreach ( $attendees as $attendee ) {
|
|
$recipients[] = array(
|
|
'email' => $attendee['email'],
|
|
'name' => $attendee['name'],
|
|
'attendee_id' => $attendee['attendee_id'],
|
|
'ticket_name' => $attendee['ticket_name'],
|
|
'status' => 'confirmed' // Default status
|
|
);
|
|
}
|
|
|
|
return $recipients;
|
|
}
|
|
|
|
/**
|
|
* Get confirmed attendees only
|
|
*
|
|
* @param int $event_id Event ID
|
|
* @return array Array of confirmed attendee data
|
|
*/
|
|
private function get_confirmed_attendees( $event_id ) {
|
|
$all_attendees = $this->get_all_event_attendees( $event_id );
|
|
|
|
// For now, treat all attendees as confirmed
|
|
// This can be enhanced later based on ticket status if needed
|
|
return array_filter( $all_attendees, function( $attendee ) {
|
|
return $attendee['status'] === 'confirmed';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get pending attendees only
|
|
*
|
|
* @param int $event_id Event ID
|
|
* @return array Array of pending attendee data
|
|
*/
|
|
private function get_pending_attendees( $event_id ) {
|
|
$all_attendees = $this->get_all_event_attendees( $event_id );
|
|
|
|
return array_filter( $all_attendees, function( $attendee ) {
|
|
return $attendee['status'] === 'pending';
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Parse custom recipient list from text input
|
|
*
|
|
* @param string $recipient_list Comma or line-separated email list
|
|
* @return array Array of recipient data
|
|
*/
|
|
private function parse_custom_recipient_list( $recipient_list ) {
|
|
if ( empty( $recipient_list ) ) {
|
|
return array();
|
|
}
|
|
|
|
$recipients = array();
|
|
$lines = preg_split( '/[\r\n,]+/', $recipient_list );
|
|
|
|
foreach ( $lines as $line ) {
|
|
$line = trim( $line );
|
|
|
|
if ( empty( $line ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Check if line contains both name and email
|
|
if ( preg_match( '/(.+?)\s*<(.+?)>/', $line, $matches ) ) {
|
|
$name = trim( $matches[1] );
|
|
$email = trim( $matches[2] );
|
|
} else {
|
|
// Just email address
|
|
$email = $line;
|
|
$name = '';
|
|
}
|
|
|
|
if ( is_email( $email ) ) {
|
|
$recipients[] = array(
|
|
'email' => $email,
|
|
'name' => $name,
|
|
'attendee_id' => 0,
|
|
'ticket_name' => '',
|
|
'status' => 'custom'
|
|
);
|
|
}
|
|
}
|
|
|
|
return $recipients;
|
|
}
|
|
|
|
/**
|
|
* Apply additional conditions to filter recipients
|
|
*
|
|
* @param array $recipients Current recipient list
|
|
* @param array $conditions Filter conditions
|
|
* @return array Filtered recipients
|
|
*/
|
|
private function apply_recipient_conditions( $recipients, $conditions ) {
|
|
if ( empty( $conditions ) ) {
|
|
return $recipients;
|
|
}
|
|
|
|
foreach ( $conditions as $condition ) {
|
|
switch ( $condition['type'] ) {
|
|
case 'ticket_type':
|
|
$recipients = array_filter( $recipients, function( $recipient ) use ( $condition ) {
|
|
return $recipient['ticket_name'] === $condition['value'];
|
|
});
|
|
break;
|
|
case 'exclude_emails':
|
|
$exclude_list = array_map( 'trim', explode( ',', $condition['value'] ) );
|
|
$recipients = array_filter( $recipients, function( $recipient ) use ( $exclude_list ) {
|
|
return ! in_array( $recipient['email'], $exclude_list );
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $recipients;
|
|
}
|
|
|
|
/**
|
|
* Execute communication for a schedule
|
|
*
|
|
* @param array $schedule Schedule configuration
|
|
* @param array $recipients Recipients to send to
|
|
* @return bool Success status
|
|
*/
|
|
public function execute_communication( $schedule, $recipients ) {
|
|
if ( empty( $recipients ) || empty( $schedule['template_id'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Get the email template
|
|
$template = get_post( $schedule['template_id'] );
|
|
if ( ! $template || $template->post_type !== 'hvac_email_template' ) {
|
|
return false;
|
|
}
|
|
|
|
$subject = $template->post_title;
|
|
$message = $template->post_content;
|
|
|
|
// Get event details for placeholder replacement
|
|
$event_details = null;
|
|
if ( ! empty( $schedule['event_id'] ) ) {
|
|
$email_data = new HVAC_Email_Attendees_Data( $schedule['event_id'] );
|
|
$event_details = $email_data->get_event_details();
|
|
}
|
|
|
|
$success_count = 0;
|
|
$total_count = count( $recipients );
|
|
|
|
foreach ( $recipients as $recipient ) {
|
|
// Replace placeholders in subject and message
|
|
$personalized_subject = $this->replace_placeholders( $subject, $recipient, $event_details );
|
|
$personalized_message = $this->replace_placeholders( $message, $recipient, $event_details );
|
|
|
|
// Send email
|
|
$headers = array(
|
|
'Content-Type: text/html; charset=UTF-8'
|
|
);
|
|
|
|
// Add sender information
|
|
$trainer = get_user_by( 'id', $schedule['trainer_id'] );
|
|
if ( $trainer ) {
|
|
$from_name = $trainer->display_name;
|
|
$from_email = $trainer->user_email;
|
|
|
|
// Check for trainer business name
|
|
$business_name = get_user_meta( $trainer->ID, 'business_name', true );
|
|
if ( ! empty( $business_name ) ) {
|
|
$from_name = $business_name;
|
|
}
|
|
|
|
$headers[] = 'From: ' . $from_name . ' <' . $from_email . '>';
|
|
}
|
|
|
|
$mail_sent = wp_mail( $recipient['email'], $personalized_subject, wpautop( $personalized_message ), $headers );
|
|
|
|
if ( $mail_sent ) {
|
|
$success_count++;
|
|
}
|
|
|
|
// Log individual send attempt if logger is available
|
|
if ( class_exists( 'HVAC_Logger' ) ) {
|
|
$status = $mail_sent ? 'sent' : 'failed';
|
|
HVAC_Logger::info( "Email {$status} to {$recipient['email']} for schedule {$schedule['schedule_id']}", 'Communication Engine' );
|
|
}
|
|
}
|
|
|
|
return $success_count === $total_count;
|
|
}
|
|
|
|
/**
|
|
* Replace placeholders in email content
|
|
*
|
|
* @param string $content Email subject or content
|
|
* @param array $recipient Recipient data
|
|
* @param array|null $event_details Event details for placeholders
|
|
* @return string Content with placeholders replaced
|
|
*/
|
|
private function replace_placeholders( $content, $recipient, $event_details = null ) {
|
|
$placeholders = array(
|
|
'{attendee_name}' => $recipient['name'],
|
|
'{attendee_email}' => $recipient['email'],
|
|
'{ticket_type}' => $recipient['ticket_name']
|
|
);
|
|
|
|
if ( $event_details ) {
|
|
$placeholders['{event_title}'] = $event_details['title'];
|
|
$placeholders['{event_date}'] = $event_details['start_date'];
|
|
$placeholders['{event_time}'] = $event_details['start_time'];
|
|
$placeholders['{event_start_date}'] = $event_details['start_date'];
|
|
$placeholders['{event_start_time}'] = $event_details['start_time'];
|
|
$placeholders['{event_end_date}'] = $event_details['end_date'];
|
|
$placeholders['{event_end_time}'] = $event_details['end_time'];
|
|
}
|
|
|
|
// Add current date/time placeholders
|
|
$placeholders['{current_date}'] = date( 'F j, Y' );
|
|
$placeholders['{current_time}'] = date( 'g:i a' );
|
|
$placeholders['{current_year}'] = date( 'Y' );
|
|
|
|
return str_replace( array_keys( $placeholders ), array_values( $placeholders ), $content );
|
|
}
|
|
|
|
/**
|
|
* Process registration-triggered communications
|
|
*
|
|
* @param int $attendee_id Attendee ID
|
|
* @param int $event_id Event ID
|
|
*/
|
|
public function process_registration_triggers( $attendee_id, $event_id ) {
|
|
global $wpdb;
|
|
|
|
// Get all active schedules with registration triggers for this event
|
|
$schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
|
|
|
|
$schedules = $wpdb->get_results( $wpdb->prepare(
|
|
"SELECT * FROM {$schedules_table}
|
|
WHERE event_id = %d
|
|
AND trigger_type = 'on_registration'
|
|
AND status = 'active'",
|
|
$event_id
|
|
), ARRAY_A );
|
|
|
|
foreach ( $schedules as $schedule ) {
|
|
// Get attendee details
|
|
$attendee_post = get_post( $attendee_id );
|
|
if ( ! $attendee_post ) {
|
|
continue;
|
|
}
|
|
|
|
$attendee_email = get_post_meta( $attendee_id, '_tribe_tickets_email', true );
|
|
if ( empty( $attendee_email ) ) {
|
|
$attendee_email = get_post_meta( $attendee_id, '_tribe_tpp_email', true );
|
|
}
|
|
|
|
$attendee_name = get_post_meta( $attendee_id, '_tribe_tickets_full_name', true );
|
|
if ( empty( $attendee_name ) ) {
|
|
$attendee_name = get_post_meta( $attendee_id, '_tribe_tpp_full_name', true );
|
|
}
|
|
|
|
if ( empty( $attendee_email ) || ! is_email( $attendee_email ) ) {
|
|
continue;
|
|
}
|
|
|
|
// Create recipient array
|
|
$recipients = array(
|
|
array(
|
|
'email' => $attendee_email,
|
|
'name' => $attendee_name,
|
|
'attendee_id' => $attendee_id,
|
|
'ticket_name' => '',
|
|
'status' => 'confirmed'
|
|
)
|
|
);
|
|
|
|
// Execute communication
|
|
$this->execute_communication( $schedule, $recipients );
|
|
|
|
// Update schedule run tracking
|
|
$schedule_manager = new HVAC_Communication_Schedule_Manager();
|
|
$schedule_manager->update_schedule_run_tracking( $schedule['schedule_id'] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process event date changes and update affected schedules
|
|
*/
|
|
public function process_event_date_changes() {
|
|
global $wpdb;
|
|
|
|
// This would be called when event dates are updated
|
|
// For now, it's a placeholder for future implementation
|
|
|
|
if ( class_exists( 'HVAC_Logger' ) ) {
|
|
HVAC_Logger::info( 'Processing event date changes', 'Communication Engine' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate recipients against event attendees
|
|
*
|
|
* @param array $recipients Recipients to validate
|
|
* @param int $event_id Event ID
|
|
* @return array Valid recipients only
|
|
*/
|
|
public function validate_recipients( $recipients, $event_id = null ) {
|
|
if ( empty( $recipients ) ) {
|
|
return array();
|
|
}
|
|
|
|
$valid_recipients = array();
|
|
|
|
foreach ( $recipients as $recipient ) {
|
|
// Basic email validation
|
|
if ( empty( $recipient['email'] ) || ! is_email( $recipient['email'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
// If event ID provided, verify recipient is actually an attendee
|
|
if ( $event_id ) {
|
|
$all_attendees = $this->get_all_event_attendees( $event_id );
|
|
$is_attendee = false;
|
|
|
|
foreach ( $all_attendees as $attendee ) {
|
|
if ( $attendee['email'] === $recipient['email'] ) {
|
|
$is_attendee = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( ! $is_attendee && $recipient['status'] !== 'custom' ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$valid_recipients[] = $recipient;
|
|
}
|
|
|
|
return $valid_recipients;
|
|
}
|
|
|
|
/**
|
|
* Get communication statistics for a schedule
|
|
*
|
|
* @param int $schedule_id Schedule ID
|
|
* @return array Statistics array
|
|
*/
|
|
public function get_schedule_statistics( $schedule_id ) {
|
|
global $wpdb;
|
|
|
|
$logs_table = $wpdb->prefix . 'hvac_communication_logs';
|
|
|
|
$stats = $wpdb->get_row( $wpdb->prepare(
|
|
"SELECT
|
|
COUNT(*) as total_sends,
|
|
COUNT(CASE WHEN status = 'sent' THEN 1 END) as successful_sends,
|
|
COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_sends,
|
|
MAX(sent_date) as last_sent
|
|
FROM {$logs_table}
|
|
WHERE schedule_id = %d",
|
|
$schedule_id
|
|
), ARRAY_A );
|
|
|
|
return $stats;
|
|
}
|
|
} |