🚨 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>
		
			
				
	
	
		
			467 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * HVAC Community Events - Communication Logger
 | |
|  *
 | |
|  * Handles logging of communication schedule execution and delivery.
 | |
|  * Tracks sent emails, failures, and schedule performance.
 | |
|  *
 | |
|  * @package HVAC_Community_Events
 | |
|  * @subpackage Communication
 | |
|  * @version 1.0.0
 | |
|  */
 | |
| 
 | |
| // Exit if accessed directly
 | |
| if ( ! defined( 'ABSPATH' ) ) {
 | |
|     exit;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Class HVAC_Communication_Logger
 | |
|  *
 | |
|  * Manages logging for communication schedules and delivery.
 | |
|  */
 | |
| class HVAC_Communication_Logger {
 | |
| 
 | |
|     /**
 | |
|      * Database table names
 | |
|      */
 | |
|     private $logs_table;
 | |
| 
 | |
|     /**
 | |
|      * Constructor
 | |
|      */
 | |
|     public function __construct() {
 | |
|         global $wpdb;
 | |
|         
 | |
|         $this->logs_table = $wpdb->prefix . 'hvac_communication_logs';
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Log a schedule execution
 | |
|      *
 | |
|      * @param int $schedule_id Schedule ID
 | |
|      * @param string $status Execution status ('sent', 'failed', 'skipped')
 | |
|      * @param array $details Additional execution details
 | |
|      * @return int|false Log ID on success, false on failure
 | |
|      */
 | |
|     public function log_schedule_execution( $schedule_id, $status, $details = array() ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $log_data = array(
 | |
|             'schedule_id' => intval( $schedule_id ),
 | |
|             'status' => sanitize_text_field( $status ),
 | |
|             'sent_date' => current_time( 'mysql' ),
 | |
|             'recipient_count' => isset( $details['recipient_count'] ) ? intval( $details['recipient_count'] ) : 0,
 | |
|             'success_count' => isset( $details['success_count'] ) ? intval( $details['success_count'] ) : 0,
 | |
|             'error_count' => isset( $details['error_count'] ) ? intval( $details['error_count'] ) : 0,
 | |
|             'execution_time' => isset( $details['execution_time'] ) ? floatval( $details['execution_time'] ) : 0,
 | |
|             'details' => ! empty( $details ) ? wp_json_encode( $details ) : null
 | |
|         );
 | |
| 
 | |
|         $formats = array( '%d', '%s', '%s', '%d', '%d', '%d', '%f', '%s' );
 | |
| 
 | |
|         $result = $wpdb->insert( $this->logs_table, $log_data, $formats );
 | |
| 
 | |
|         if ( $result === false ) {
 | |
|             if ( class_exists( 'HVAC_Logger' ) ) {
 | |
|                 HVAC_Logger::error( 'Failed to log schedule execution: ' . $wpdb->last_error, 'Communication Logger' );
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return $wpdb->insert_id;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Log individual email delivery
 | |
|      *
 | |
|      * @param int $schedule_id Schedule ID
 | |
|      * @param string $recipient_email Recipient email address
 | |
|      * @param string $status Delivery status ('sent', 'failed', 'bounced')
 | |
|      * @param array $details Additional delivery details
 | |
|      * @return int|false Log ID on success, false on failure
 | |
|      */
 | |
|     public function log_email_delivery( $schedule_id, $recipient_email, $status, $details = array() ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $log_data = array(
 | |
|             'schedule_id' => intval( $schedule_id ),
 | |
|             'recipient_email' => sanitize_email( $recipient_email ),
 | |
|             'status' => sanitize_text_field( $status ),
 | |
|             'sent_date' => current_time( 'mysql' ),
 | |
|             'details' => ! empty( $details ) ? wp_json_encode( $details ) : null
 | |
|         );
 | |
| 
 | |
|         $formats = array( '%d', '%s', '%s', '%s', '%s' );
 | |
| 
 | |
|         $result = $wpdb->insert( $this->logs_table, $log_data, $formats );
 | |
| 
 | |
|         return $result !== false ? $wpdb->insert_id : false;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get execution logs for a schedule
 | |
|      *
 | |
|      * @param int $schedule_id Schedule ID
 | |
|      * @param array $args Query arguments
 | |
|      * @return array Array of log entries
 | |
|      */
 | |
|     public function get_schedule_logs( $schedule_id, $args = array() ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $defaults = array(
 | |
|             'limit' => 50,
 | |
|             'offset' => 0,
 | |
|             'status' => null,
 | |
|             'date_from' => null,
 | |
|             'date_to' => null
 | |
|         );
 | |
| 
 | |
|         $args = wp_parse_args( $args, $defaults );
 | |
| 
 | |
|         $where_clauses = array( 'schedule_id = %d' );
 | |
|         $where_values = array( intval( $schedule_id ) );
 | |
| 
 | |
|         // Status filter
 | |
|         if ( ! empty( $args['status'] ) ) {
 | |
|             $where_clauses[] = 'status = %s';
 | |
|             $where_values[] = $args['status'];
 | |
|         }
 | |
| 
 | |
|         // Date range filters
 | |
|         if ( ! empty( $args['date_from'] ) ) {
 | |
|             $where_clauses[] = 'sent_date >= %s';
 | |
|             $where_values[] = $args['date_from'];
 | |
|         }
 | |
| 
 | |
|         if ( ! empty( $args['date_to'] ) ) {
 | |
|             $where_clauses[] = 'sent_date <= %s';
 | |
|             $where_values[] = $args['date_to'];
 | |
|         }
 | |
| 
 | |
|         $where_sql = implode( ' AND ', $where_clauses );
 | |
| 
 | |
|         $sql = "SELECT * FROM {$this->logs_table} 
 | |
|                 WHERE {$where_sql} 
 | |
|                 ORDER BY sent_date DESC 
 | |
|                 LIMIT %d OFFSET %d";
 | |
| 
 | |
|         $where_values[] = intval( $args['limit'] );
 | |
|         $where_values[] = intval( $args['offset'] );
 | |
| 
 | |
|         $logs = $wpdb->get_results( $wpdb->prepare( $sql, $where_values ), ARRAY_A );
 | |
| 
 | |
|         // Decode JSON details
 | |
|         foreach ( $logs as &$log ) {
 | |
|             if ( ! empty( $log['details'] ) ) {
 | |
|                 $log['details'] = json_decode( $log['details'], true );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $logs;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get logs for all schedules with filtering
 | |
|      *
 | |
|      * @param array $args Query arguments
 | |
|      * @return array Array of log entries with schedule info
 | |
|      */
 | |
|     public function get_all_logs( $args = array() ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $defaults = array(
 | |
|             'limit' => 50,
 | |
|             'offset' => 0,
 | |
|             'trainer_id' => null,
 | |
|             'status' => null,
 | |
|             'date_from' => null,
 | |
|             'date_to' => null
 | |
|         );
 | |
| 
 | |
|         $args = wp_parse_args( $args, $defaults );
 | |
| 
 | |
|         $schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
 | |
| 
 | |
|         $where_clauses = array();
 | |
|         $where_values = array();
 | |
| 
 | |
|         // Trainer filter
 | |
|         if ( ! empty( $args['trainer_id'] ) ) {
 | |
|             $where_clauses[] = 's.trainer_id = %d';
 | |
|             $where_values[] = intval( $args['trainer_id'] );
 | |
|         }
 | |
| 
 | |
|         // Status filter
 | |
|         if ( ! empty( $args['status'] ) ) {
 | |
|             $where_clauses[] = 'l.status = %s';
 | |
|             $where_values[] = $args['status'];
 | |
|         }
 | |
| 
 | |
|         // Date range filters
 | |
|         if ( ! empty( $args['date_from'] ) ) {
 | |
|             $where_clauses[] = 'l.sent_date >= %s';
 | |
|             $where_values[] = $args['date_from'];
 | |
|         }
 | |
| 
 | |
|         if ( ! empty( $args['date_to'] ) ) {
 | |
|             $where_clauses[] = 'l.sent_date <= %s';
 | |
|             $where_values[] = $args['date_to'];
 | |
|         }
 | |
| 
 | |
|         $where_sql = ! empty( $where_clauses ) ? 'WHERE ' . implode( ' AND ', $where_clauses ) : '';
 | |
| 
 | |
|         $sql = "SELECT l.*, 
 | |
|                        s.trainer_id,
 | |
|                        s.event_id,
 | |
|                        s.template_id,
 | |
|                        s.trigger_type,
 | |
|                        e.post_title as event_name,
 | |
|                        t.post_title as template_name
 | |
|                 FROM {$this->logs_table} l
 | |
|                 LEFT JOIN {$schedules_table} s ON l.schedule_id = s.schedule_id
 | |
|                 LEFT JOIN {$wpdb->posts} e ON s.event_id = e.ID
 | |
|                 LEFT JOIN {$wpdb->posts} t ON s.template_id = t.ID
 | |
|                 {$where_sql}
 | |
|                 ORDER BY l.sent_date DESC 
 | |
|                 LIMIT %d OFFSET %d";
 | |
| 
 | |
|         $where_values[] = intval( $args['limit'] );
 | |
|         $where_values[] = intval( $args['offset'] );
 | |
| 
 | |
|         $logs = $wpdb->get_results( $wpdb->prepare( $sql, $where_values ), ARRAY_A );
 | |
| 
 | |
|         // Decode JSON details
 | |
|         foreach ( $logs as &$log ) {
 | |
|             if ( ! empty( $log['details'] ) ) {
 | |
|                 $log['details'] = json_decode( $log['details'], true );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $logs;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get summary statistics for communication logs
 | |
|      *
 | |
|      * @param int|null $trainer_id Optional trainer ID filter
 | |
|      * @param string|null $date_from Optional start date filter
 | |
|      * @param string|null $date_to Optional end date filter
 | |
|      * @return array Statistics array
 | |
|      */
 | |
|     public function get_statistics( $trainer_id = null, $date_from = null, $date_to = null ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
 | |
| 
 | |
|         $where_clauses = array();
 | |
|         $where_values = array();
 | |
| 
 | |
|         if ( ! empty( $trainer_id ) ) {
 | |
|             $where_clauses[] = 's.trainer_id = %d';
 | |
|             $where_values[] = intval( $trainer_id );
 | |
|         }
 | |
| 
 | |
|         if ( ! empty( $date_from ) ) {
 | |
|             $where_clauses[] = 'l.sent_date >= %s';
 | |
|             $where_values[] = $date_from;
 | |
|         }
 | |
| 
 | |
|         if ( ! empty( $date_to ) ) {
 | |
|             $where_clauses[] = 'l.sent_date <= %s';
 | |
|             $where_values[] = $date_to;
 | |
|         }
 | |
| 
 | |
|         $where_sql = ! empty( $where_clauses ) ? 'WHERE ' . implode( ' AND ', $where_clauses ) : '';
 | |
| 
 | |
|         $sql = "SELECT 
 | |
|                     COUNT(*) as total_executions,
 | |
|                     COUNT(CASE WHEN l.status = 'sent' THEN 1 END) as successful_executions,
 | |
|                     COUNT(CASE WHEN l.status = 'failed' THEN 1 END) as failed_executions,
 | |
|                     COUNT(CASE WHEN l.status = 'skipped' THEN 1 END) as skipped_executions,
 | |
|                     SUM(l.recipient_count) as total_recipients,
 | |
|                     SUM(l.success_count) as total_emails_sent,
 | |
|                     SUM(l.error_count) as total_email_errors,
 | |
|                     AVG(l.execution_time) as avg_execution_time
 | |
|                 FROM {$this->logs_table} l
 | |
|                 LEFT JOIN {$schedules_table} s ON l.schedule_id = s.schedule_id
 | |
|                 {$where_sql}";
 | |
| 
 | |
|         $stats = $wpdb->get_row( 
 | |
|             empty( $where_values ) ? $sql : $wpdb->prepare( $sql, $where_values ), 
 | |
|             ARRAY_A 
 | |
|         );
 | |
| 
 | |
|         // Ensure numeric values
 | |
|         foreach ( $stats as $key => $value ) {
 | |
|             if ( in_array( $key, array( 'avg_execution_time' ) ) ) {
 | |
|                 $stats[$key] = floatval( $value );
 | |
|             } else {
 | |
|                 $stats[$key] = intval( $value );
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return $stats;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get recent execution activity
 | |
|      *
 | |
|      * @param int $limit Number of recent activities to retrieve
 | |
|      * @param int|null $trainer_id Optional trainer ID filter
 | |
|      * @return array Array of recent activities
 | |
|      */
 | |
|     public function get_recent_activity( $limit = 10, $trainer_id = null ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
 | |
| 
 | |
|         $where_clause = '';
 | |
|         $where_values = array();
 | |
| 
 | |
|         if ( ! empty( $trainer_id ) ) {
 | |
|             $where_clause = 'WHERE s.trainer_id = %d';
 | |
|             $where_values[] = intval( $trainer_id );
 | |
|         }
 | |
| 
 | |
|         $sql = "SELECT l.*, 
 | |
|                        s.trainer_id,
 | |
|                        e.post_title as event_name,
 | |
|                        t.post_title as template_name
 | |
|                 FROM {$this->logs_table} l
 | |
|                 LEFT JOIN {$schedules_table} s ON l.schedule_id = s.schedule_id
 | |
|                 LEFT JOIN {$wpdb->posts} e ON s.event_id = e.ID
 | |
|                 LEFT JOIN {$wpdb->posts} t ON s.template_id = t.ID
 | |
|                 {$where_clause}
 | |
|                 ORDER BY l.sent_date DESC 
 | |
|                 LIMIT %d";
 | |
| 
 | |
|         $where_values[] = intval( $limit );
 | |
| 
 | |
|         $activities = $wpdb->get_results( $wpdb->prepare( $sql, $where_values ), ARRAY_A );
 | |
| 
 | |
|         // Decode JSON details and format for display
 | |
|         foreach ( $activities as &$activity ) {
 | |
|             if ( ! empty( $activity['details'] ) ) {
 | |
|                 $activity['details'] = json_decode( $activity['details'], true );
 | |
|             }
 | |
| 
 | |
|             // Add human-readable time
 | |
|             $activity['time_ago'] = human_time_diff( strtotime( $activity['sent_date'] ), current_time( 'timestamp' ) ) . ' ago';
 | |
|         }
 | |
| 
 | |
|         return $activities;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Clean up old log entries
 | |
|      *
 | |
|      * @param int $days_to_keep Number of days to keep logs (default 90)
 | |
|      * @return int Number of entries deleted
 | |
|      */
 | |
|     public function cleanup_old_logs( $days_to_keep = 90 ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $cutoff_date = date( 'Y-m-d H:i:s', strtotime( "-{$days_to_keep} days" ) );
 | |
| 
 | |
|         $deleted = $wpdb->query( $wpdb->prepare(
 | |
|             "DELETE FROM {$this->logs_table} WHERE sent_date < %s",
 | |
|             $cutoff_date
 | |
|         ) );
 | |
| 
 | |
|         if ( $deleted !== false && class_exists( 'HVAC_Logger' ) ) {
 | |
|             HVAC_Logger::info( "Cleaned up {$deleted} old communication log entries", 'Communication Logger' );
 | |
|         }
 | |
| 
 | |
|         return intval( $deleted );
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Export logs to CSV format
 | |
|      *
 | |
|      * @param array $args Export arguments
 | |
|      * @return string CSV content
 | |
|      */
 | |
|     public function export_logs_csv( $args = array() ) {
 | |
|         $logs = $this->get_all_logs( $args );
 | |
| 
 | |
|         $csv_header = array(
 | |
|             'Date',
 | |
|             'Schedule ID',
 | |
|             'Event',
 | |
|             'Template',
 | |
|             'Status',
 | |
|             'Recipients',
 | |
|             'Successful',
 | |
|             'Errors',
 | |
|             'Execution Time (s)'
 | |
|         );
 | |
| 
 | |
|         $csv_data = array();
 | |
|         $csv_data[] = $csv_header;
 | |
| 
 | |
|         foreach ( $logs as $log ) {
 | |
|             $csv_data[] = array(
 | |
|                 $log['sent_date'],
 | |
|                 $log['schedule_id'],
 | |
|                 $log['event_name'] ?: 'N/A',
 | |
|                 $log['template_name'] ?: 'N/A',
 | |
|                 $log['status'],
 | |
|                 $log['recipient_count'],
 | |
|                 $log['success_count'],
 | |
|                 $log['error_count'],
 | |
|                 $log['execution_time']
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         // Convert to CSV string
 | |
|         $csv_content = '';
 | |
|         foreach ( $csv_data as $row ) {
 | |
|             $csv_content .= '"' . implode( '","', $row ) . '"' . "\n";
 | |
|         }
 | |
| 
 | |
|         return $csv_content;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get performance metrics for schedules
 | |
|      *
 | |
|      * @param int|null $trainer_id Optional trainer ID filter
 | |
|      * @return array Performance metrics
 | |
|      */
 | |
|     public function get_performance_metrics( $trainer_id = null ) {
 | |
|         global $wpdb;
 | |
| 
 | |
|         $schedules_table = $wpdb->prefix . 'hvac_communication_schedules';
 | |
| 
 | |
|         $where_clause = '';
 | |
|         $where_values = array();
 | |
| 
 | |
|         if ( ! empty( $trainer_id ) ) {
 | |
|             $where_clause = 'WHERE s.trainer_id = %d';
 | |
|             $where_values[] = intval( $trainer_id );
 | |
|         }
 | |
| 
 | |
|         // Get delivery success rate
 | |
|         $sql = "SELECT 
 | |
|                     COUNT(*) as total_schedules,
 | |
|                     AVG(CASE WHEN l.status = 'sent' THEN 100.0 ELSE 0.0 END) as success_rate,
 | |
|                     AVG(l.execution_time) as avg_execution_time,
 | |
|                     SUM(l.recipient_count) / COUNT(*) as avg_recipients_per_execution
 | |
|                 FROM {$this->logs_table} l
 | |
|                 LEFT JOIN {$schedules_table} s ON l.schedule_id = s.schedule_id
 | |
|                 {$where_clause}";
 | |
| 
 | |
|         $metrics = $wpdb->get_row( 
 | |
|             empty( $where_values ) ? $sql : $wpdb->prepare( $sql, $where_values ), 
 | |
|             ARRAY_A 
 | |
|         );
 | |
| 
 | |
|         // Format metrics
 | |
|         $metrics['success_rate'] = round( floatval( $metrics['success_rate'] ), 2 );
 | |
|         $metrics['avg_execution_time'] = round( floatval( $metrics['avg_execution_time'] ), 3 );
 | |
|         $metrics['avg_recipients_per_execution'] = round( floatval( $metrics['avg_recipients_per_execution'] ), 1 );
 | |
| 
 | |
|         return $metrics;
 | |
|     }
 | |
| } |