Jake Vanderwerf
2025-12-21 3aada9949d51024a92a8b5c6cb70d12f9c3cac16
inc/managers/ErrorHandler.php
@@ -186,33 +186,61 @@
     *
     * @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 {
         $table = $this->wpdb->prefix . BASE . 'error_log';
         // Validate severity
         if (!array_key_exists($severity, $this->error_levels)) {
            $severity = 'error';
         }
            // 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;
        }
    }
         // 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']));
         // Determine source from context
         $source = isset($context['source']) ? $context['source'] :
            (isset($context['url']) ? 'frontend' : 'backend');
         $result = $this->wpdb->insert(
            $table,
            [
               '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')
            ],
            ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s']
         );
         if ($result === false) {
            error_log("[ErrorHandler] Database insert failed: " . $this->wpdb->last_error);
            return ['success' => false, 'message' => $this->wpdb->last_error];
         }
         if ($severity === 'critical') {
            $this->checkErrorThreshold($error_type, $component);
         }
         return ['success' => true, 'id' => $this->wpdb->insert_id];
      } 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
@@ -227,42 +255,96 @@
        $subject = "[edmonton.ink Critical Error] {$component}";
        $body = "Error: {$message}\n\nContext: " . print_r($context, true);
        return jvbMail($admin_email, $subject, $body);
        return JVB()->email()->sendEmail($admin_email, $subject, $body);
    }
    /**
     * 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
   {
      $table = $this->wpdb->prefix . BASE . 'error_log';
        // 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
        ));
      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 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
        ));
      // Most frequent error patterns (deduplicated by component/method/message)
      $frequent = $this->wpdb->get_results($this->wpdb->prepare(
         "SELECT
            component,
            method,
            error_type,
            message,
            severity,
            source,
            COUNT(*) as count,
            SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_count,
            SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_count,
            MIN(created_at) as first_seen,
            MAX(created_at) as last_seen
         FROM {$table}
         WHERE created_at BETWEEN %s AND %s
         GROUP BY component, method, error_type, message, severity, source
         ORDER BY count DESC, severity DESC
         LIMIT 10",
         $start_date,
         $end_date
      ));
        return [
            'frequent' => $frequent_errors,
            'critical' => $critical_errors
        ];
    }
      // Critical errors
      $critical = $this->wpdb->get_results($this->wpdb->prepare(
         "SELECT
            component,
            method,
            error_type,
            message,
            source,
            COUNT(*) as count,
            SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_count,
            SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_count,
            MIN(created_at) as first_seen,
            MAX(created_at) as last_seen
         FROM {$table}
         WHERE created_at BETWEEN %s AND %s AND severity = 'critical'
         GROUP BY component, method, error_type, message, source
         ORDER BY count DESC
         LIMIT 5",
         $start_date,
         $end_date
      ));
      // Overall stats
      $stats = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT
            COUNT(*) as total_errors,
            COUNT(DISTINCT CONCAT(component, '-', COALESCE(method, ''), '-', error_type)) as unique_error_types,
            SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_errors,
            SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_errors,
            SUM(CASE WHEN source = 'frontend' THEN 1 ELSE 0 END) as frontend_errors,
            SUM(CASE WHEN source = 'backend' THEN 1 ELSE 0 END) as backend_errors,
            SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count,
            SUM(CASE WHEN severity = 'error' THEN 1 ELSE 0 END) as error_count,
            SUM(CASE WHEN severity = 'warning' THEN 1 ELSE 0 END) as warning_count
         FROM {$table}
         WHERE created_at BETWEEN %s AND %s",
         $start_date,
         $end_date
      ));
      return [
         'frequent' => $frequent,
         'critical' => $critical,
         'stats' => $stats,
         'date_range' => ['start' => $start_date, 'end' => $end_date]
      ];
   }
    /**
     * Send daily error summary email to administrator
@@ -330,7 +412,7 @@
        $body .= "View detailed error logs in the dashboard: {$admin_url}\n\n";
        // Send the email
        $sent = jvbMail($admin_email, $subject, $body, 'ERROR SUMMARY');
        $sent = JVB()->email()->sendEmail($admin_email, $subject, $body, 'ERROR SUMMARY');
        // Log that summary was sent
        if ($sent) {
@@ -426,6 +508,8 @@
    }
    protected function buildParams(WP_REST_Request $request):array {
        $allowedSeverity = [
            'all',