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; } }