upskill-event-manager/includes/communication/class-communication-logger.php
bengizmo 37f4180e1c feat: Add massive missing plugin infrastructure to repository
🚨 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>
2025-08-11 13:30:11 -03:00

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