From 97e7c319d656a5f05489ca996e249e7359303d4d Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 31 May 2026 22:42:33 +0000
Subject: [PATCH] =Jakevan edits done?
---
inc/managers/ErrorHandler.php | 365 ++++++++++++++++++++++++++++++---------------------
1 files changed, 215 insertions(+), 150 deletions(-)
diff --git a/inc/managers/ErrorHandler.php b/inc/managers/ErrorHandler.php
index add4eda..a3b1f33 100644
--- a/inc/managers/ErrorHandler.php
+++ b/inc/managers/ErrorHandler.php
@@ -12,8 +12,6 @@
class ErrorHandler
{
- protected object $wpdb;
- protected string $tableName;
protected int $notification_threshold = 5; // Critical errors within 1 hour
protected array $error_levels = [
@@ -24,11 +22,14 @@
'critical' => 4
];
+ protected CustomTable $table;
+
public function __construct()
{
- global $wpdb;
- $this->wpdb = $wpdb;
- $this->tableName = $wpdb->prefix . BASE . 'error_log';
+ $this->defineTables();
+// global $wpdb;
+// $this->wpdb = $wpdb;
+// $this->tableName = $wpdb->prefix . BASE . 'error_log';
add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
@@ -39,6 +40,35 @@
// add_filter(BASE.'admin_action_filter', [$this, 'adminActionFilter'], 10, 3);
}
+ public function defineTables():void
+ {
+ $table = CustomTable::for('error_log');
+ $table->setColumns([
+ 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+ 'error_type' => 'varchar(50) NOT NULL',
+ 'component' => 'varchar(100) NOT NULL',
+ 'method' => 'varchar(100) DEFAULT NULL',
+ 'page_url' => 'varchar(255) DEFAULT NULL',
+ 'message' => 'text NOT NULL',
+ 'context' => 'JSON',
+ 'severity' => 'ENUM(\'high\',\'normal\',\'low\') DEFAULT \'normal\'',
+ 'user_id' => $table->getUserIDType().' DEFAULT NULL',
+ 'user_was_logged_in' => 'tinyint(1) NOT NULL',
+ 'source' => 'ENUM(\'frontend\', \'backend\') NOT NULL',
+ 'created_at' => 'timestamp DEFAULT CURRENT_TIMESTAMP',
+ ]);
+
+ $table->setKeys([
+ ['key' => 'PRIMARY', 'value' => 'id'],
+ '`created_at` (`created_at`)',
+ '`component_severity_date` (`component`, `severity`, `created_at`)',
+ '`error_type_date` (`error_type`, `created_at`)',
+ '`severity_date` (`severity`, `created_at`)'
+ ]);
+
+ $table->defineTable();
+ $this->table = $table;
+ }
public function registerAdminAction():void
{
$admin = JVB()->admin();
@@ -68,7 +98,7 @@
}
try {
- $table = $this->tableName;
+
// Extract error data
$component = sanitize_text_field($data['component'] ?? '');
$message = sanitize_textarea_field($data['message'] ?? '');
@@ -94,8 +124,7 @@
}
// Insert into database
- $result = $this->wpdb->insert(
- $table,
+ $result = $this->table->insert(
[
'error_type' => $error_type,
'component' => $component,
@@ -104,24 +133,15 @@
'severity' => $severity,
'user_id' => get_current_user_id(),
'created_at' => current_time('mysql')
- ],
- [
- '%s', // error_type
- '%s', // component
- '%s', // message
- '%s', // context (JSON)
- '%s', // severity
- '%d', // user_id
- '%s' // created_at
]
);
- if ($result === false) {
+ if (!$result) {
// If insert fails, log to PHP error log as fallback
- error_log("[ErrorHandler] Database insert failed: " . $this->wpdb->last_error);
+ error_log("[ErrorHandler] Database insert failed: " . $this->table->getLastError());
return [
'success' => false,
- 'message' => "[ErrorHandler] Database insert failed: " . $this->wpdb->last_error
+ 'message' => "[ErrorHandler] Database insert failed: " . $this->table->getLastError()
];
}
@@ -155,19 +175,15 @@
protected function checkErrorThreshold(string $error_type, string $component)
{
// Get count of similar critical errors in the last hour
- $count = $this->wpdb->get_var($this->wpdb->prepare(
- "SELECT COUNT(*)
- FROM {$this->tableName}
- WHERE error_type = %s
- AND component = %s
- AND severity = 'critical'
- AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)",
- $error_type,
- $component
- ));
+ $count = $this->table->count([
+ 'error_type' => $error_type,
+ 'component' => $component,
+ 'severity' => $this->error_levels[$error_type],
+ 'created_at' => ['>','DATE_SUB(NOW(), INTERVAL 1 HOUR)']
+ ]);
// If threshold reached, take additional actions (e.g., notify developers)
- if ((int)$count >= $this->notification_threshold) {
+ if ($count >= $this->notification_threshold) {
// You could send an urgent notification, Slack message, etc.
$admin_email = get_option('admin_email');
$subject = "[URGENT] Error Threshold Exceeded for {$component}";
@@ -186,33 +202,56 @@
*
* @return bool Whether it gets logged successfully
*/
- public function log(string $component, string $message, array $context = [], string $severity = 'error'):bool
- {
- try {
- // Normal queue-based logging
- JVB()->queue()->queueOperation(
- 'error_log',
- get_current_user_id(),
- [
- 'component' => $component,
- 'message' => $message,
- 'context' => $context,
- 'severity' => $severity
- ],
- ['priority' => 'high']
- );
+ public function log(string $component, string $message, array $context = [], string $severity = 'error'): array
+ {
+ try {
+ // Validate severity
+ if (!array_key_exists($severity, $this->error_levels)) {
+ $severity = 'error';
+ }
+ // Extract info
+ $error_type = sanitize_text_field($context['error_type'] ?? $component);
+ $method = isset($context['method']) ? sanitize_text_field($context['method']) : null;
+ $page_url = isset($context['url']) ? esc_url_raw($context['url']) : null;
+ $user_id = get_current_user_id();
+ $user_was_logged_in = $user_id > 0 || (!empty($context['isLoggedIn']));
- // Immediate notification for critical errors
- if ($severity === 'critical') {
- $this->notifyAdmin($component, $message, $context);
- }
- return true;
- } catch (Exception $e) {
- error_log("[edmonton.ink Error] Failed to log error: " . $e->getMessage());
- return false;
- }
- }
+ // Determine source from context
+ $source = isset($context['source']) ? $context['source'] :
+ (isset($context['url']) ? 'frontend' : 'backend');
+
+ $result = $this->table->insert(
+ [
+ 'error_type' => $error_type,
+ 'component' => $component,
+ 'method' => $method,
+ 'page_url' => $page_url,
+ 'message' => sanitize_textarea_field($message),
+ 'context' => json_encode($context),
+ 'severity' => $severity,
+ 'user_id' => $user_id ?: null,
+ 'user_was_logged_in' => $user_was_logged_in ? 1 : 0,
+ 'source' => $source,
+ 'created_at' => current_time('mysql')
+ ]);
+
+ if (!$result) {
+ error_log("[ErrorHandler] Database insert failed: " . $this->table->getLastError());
+ return ['success' => false, 'message' => $this->table->getLastError()];
+ }
+
+ if ($severity === 'critical') {
+ $this->checkErrorThreshold($error_type, $component);
+ }
+
+ return ['success' => true, 'id' => $result];
+
+ } catch (Exception $e) {
+ error_log("[ErrorHandler Exception] " . $e->getMessage());
+ return ['success' => false, 'message' => $e->getMessage()];
+ }
+ }
/**
* @param string $component What class or function logs the error
@@ -221,126 +260,150 @@
*
* @return bool Whether the notification is sent successfully
*/
- protected function notifyAdmin(string $component, string $message, array $context):bool
- {
- $admin_email = get_option('admin_email');
- $subject = "[edmonton.ink Critical Error] {$component}";
- $body = "Error: {$message}\n\nContext: " . print_r($context, true);
+ protected function notifyAdmin(string $component, string $message, array $context):bool
+ {
+ $admin_email = get_option('admin_email');
+ $subject = "[" . get_bloginfo('name') . " Critical Error] {$component}";
- return jvbMail($admin_email, $subject, $body);
- }
+ $body = JVB()->email()->alert(
+ 'A critical error has occurred and requires immediate attention',
+ 'error'
+ );
+
+ $body .= JVB()->email()->h2('Error Details');
+ $body .= JVB()->email()->card(
+ '<p><strong>Component:</strong> ' . esc_html($component) . '</p>' .
+ '<p><strong>Message:</strong></p>' .
+ JVB()->email()->codeBlock($message),
+ 'Error Information'
+ );
+
+ if (!empty($context)) {
+ $body .= JVB()->email()->h3('Additional Context');
+ $body .= JVB()->email()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
+ }
+
+ return JVB()->email()->sendEmail($admin_email, $subject, $body, 'CRITICAL ERROR');
+ }
/**
* Gather summary of the most important errors
+ * @param ?string $start_date Defaults to today
+ * @param ?string $end_date Defaults to today
* @return array
*/
- protected function gatherErrorSummary():array
- {
- $yesterday = date('Y-m-d H:i:s', strtotime('-24 hours'));
+ public function gatherErrorSummary(?string $start_date = null, ?string $end_date = null): array
+ {
+ if (!$start_date) {
+ $start_date = gmdate('Y-m-d 00:00:00', strtotime('-1 day'));
+ }
+ if (!$end_date) {
+ $end_date = gmdate('Y-m-d 23:59:59');
+ }
- // Get most frequent errors
- $frequent_errors = $this->wpdb->get_results($this->wpdb->prepare(
- "SELECT error_type, component, message, COUNT(*) as count
- FROM {$this->tableName}
- WHERE created_at > %s
- GROUP BY error_type, component, message
- ORDER BY count DESC
- LIMIT 20",
- $yesterday
- ));
+ // Most frequent error patterns (deduplicated by component/method/message)
+ $frequent = $this->table->getMany([
+ 'where' => [
+ 'created_at' => ['BETWEEN', "{$start_date} AND {$end_date}"]
+ ]
+ ]);
- // Get most recent critical errors
- $critical_errors = $this->wpdb->get_results($this->wpdb->prepare(
- "SELECT * FROM {$this->tableName}
- WHERE severity = 'critical' AND created_at > %s
- ORDER BY created_at DESC
- LIMIT 5",
- $yesterday
- ));
-
- return [
- 'frequent' => $frequent_errors,
- 'critical' => $critical_errors
- ];
- }
+ return [
+ 'errors' => $frequent,
+ 'date_range' => ['start' => $start_date, 'end' => $end_date]
+ ];
+ }
/**
* Send daily error summary email to administrator
* @return bool Whether email is sent
*/
- public function sendErrorSummary():bool
- {
- // Get summary data
- $summary = $this->gatherErrorSummary();
+ public function sendErrorSummary():bool
+ {
+ $summary = $this->gatherErrorSummary();
- // Only send if there are errors
- if (empty($summary['frequent']) && empty($summary['critical'])) {
- return false;
- }
+ if (empty($summary['frequent']) && empty($summary['critical'])) {
+ return false;
+ }
- $admin_email = get_option('admin_email');
- $site_name = get_bloginfo('name');
- $today = date('Y-m-d');
- $yesterday = date('Y-m-d', strtotime('-1 day'));
+ $admin_email = get_option('admin_email');
+ $site_name = get_bloginfo('name');
+ $yesterday = date('Y-m-d', strtotime('-1 day'));
+ $subject = "[{$site_name}] Daily Error Summary - " . date('Y-m-d');
- $subject = "[{$site_name}] Daily Error Summary - {$today}";
+ // Header with alert
+ $body = JVB()->email()->h1('Daily Error Summary');
+ $body .= sprintf('<p>Error summary for <strong>%s</strong></p>', $yesterday);
- // Build email body
- $body = "= Error Summary for {$yesterday} =\n\n";
+ // Summary stats in a grid
+ if (!empty($summary['stats'])) {
+ $stats = [
+ JVB()->email()->stat($summary['stats']->total_errors, 'Total Errors'),
+ JVB()->email()->stat($summary['stats']->critical_count, 'Critical', 'Requires attention'),
+ JVB()->email()->stat($summary['stats']->error_count, 'Errors'),
+ JVB()->email()->stat($summary['stats']->warning_count, 'Warnings')
+ ];
+ $body .= JVB()->email()->grid($stats, 4);
+ }
- // Add frequent errors section
- if (!empty($summary['frequent'])) {
- $body .= "== Most Frequent Errors ==\n\n";
+ // Alert if critical errors exist
+ if (!empty($summary['critical'])) {
+ $body .= JVB()->email()->alert(
+ sprintf('Found %d critical errors that need immediate attention', count($summary['critical'])),
+ 'error'
+ );
+ }
- foreach ($summary['frequent'] as $index => $error) {
- $body .= ($index + 1) . ". [{$error->component}] {$error->error_type}\n";
- $body .= " Message: " . wp_trim_words($error->message, 20, '...') . "\n";
- $body .= " Count: {$error->count}\n\n";
- }
- }
+ $body .= JVB()->email()->spacer(20);
- // Add critical errors section
- if (!empty($summary['critical'])) {
- $body .= "== Recent Critical Errors ==\n\n";
+ // Frequent errors section
+ if (!empty($summary['frequent'])) {
+ $body .= JVB()->email()->h2('Most Frequent Errors');
- foreach ($summary['critical'] as $index => $error) {
- $body .= ($index + 1) . ". [{$error->component}] {$error->error_type}\n";
- $body .= " Time: {$error->created_at}\n";
- $body .= " Message: " . $error->message . "\n\n";
+ foreach ($summary['frequent'] as $error) {
+ $cardContent = JVB()->email()->badge($error->count . 'x', 'warning') . ' ';
+ $cardContent .= '<strong>' . esc_html($error->error_type) . '</strong>';
+ $cardContent .= '<p style="margin:10px 0 5px 0;font-size:13px;">' . esc_html(wp_trim_words($error->message, 15)) . '</p>';
+ $cardContent .= '<p style="margin:0;font-size:12px;color:' . JVB()->email()->colours['dark-200'] . ';">
+ Source: ' . esc_html($error->source) . ' |
+ Logged in: ' . $error->logged_in_count . ' |
+ Logged out: ' . $error->logged_out_count . '
+ </p>';
- // Include context for critical errors if available
- if (!empty($error->context)) {
- $context = json_decode($error->context, true);
- if (is_array($context)) {
- $body .= " Context:\n";
- foreach ($context as $key => $value) {
- if (is_array($value) || is_object($value)) {
- $value = json_encode($value);
- }
- $body .= " - {$key}: {$value}\n";
- }
- }
- $body .= "\n";
- }
- }
- }
+ $body .= JVB()->email()->card($cardContent, $error->component);
+ }
+ }
- // Add dashboard link if available
- $admin_url = admin_url('admin.php?page=jvb-error-logs');
- $body .= "View detailed error logs in the dashboard: {$admin_url}\n\n";
+ // Critical errors section
+ if (!empty($summary['critical'])) {
+ $body .= JVB()->email()->spacer(30);
+ $body .= JVB()->email()->h2('Recent Critical Errors');
- // Send the email
- $sent = jvbMail($admin_email, $subject, $body, 'ERROR SUMMARY');
+ foreach ($summary['critical'] as $error) {
+ $cardContent = '<p><strong>Time:</strong> ' . esc_html($error->created_at) . '</p>';
+ $cardContent .= '<p><strong>Message:</strong></p>';
+ $cardContent .= JVB()->email()->codeBlock($error->message);
- // Log that summary was sent
- if ($sent) {
- error_log("[ErrorHandler] Daily error summary sent to {$admin_email}");
- } else {
- error_log("[ErrorHandler] Daily error summary was not sent.");
- }
+ // Include context if available
+ if (!empty($error->context)) {
+ $context = json_decode($error->context, true);
+ if (is_array($context)) {
+ $cardContent .= '<p><strong>Context:</strong></p>';
+ $cardContent .= JVB()->email()->codeBlock(json_encode($context, JSON_PRETTY_PRINT));
+ }
+ }
- return $sent;
- }
+ $body .= JVB()->email()->card($cardContent, $error->component . ': ' . $error->error_type);
+ }
+ }
+
+ // Dashboard link
+ $admin_url = admin_url('admin.php?page=jvb-error-logs');
+ $body .= JVB()->email()->spacer(30);
+ $body .= JVB()->email()->button($admin_url, 'View Detailed Logs');
+
+ return JVB()->email()->sendEmail($admin_email, $subject, $body, 'ERROR SUMMARY');
+ }
/**
* Get HTML version of the error summary for nicer emails
@@ -426,6 +489,8 @@
}
+
+
protected function buildParams(WP_REST_Request $request):array {
$allowedSeverity = [
'all',
--
Gitblit v1.10.0