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