table_name = $wpdb->prefix . 'wpfmj_error_log'; } /** * Log an error. */ public function log($automation_id, $form_entry_id, $error_type, $error_message, $retry_count = 0) { global $wpdb; return $wpdb->insert( $this->table_name, array( 'automation_id' => intval($automation_id), 'form_entry_id' => intval($form_entry_id), 'error_type' => sanitize_text_field($error_type), 'error_message' => sanitize_textarea_field($error_message), 'retry_count' => intval($retry_count), 'resolved' => 0 ), array('%d', '%d', '%s', '%s', '%d', '%d') ); } /** * Get errors for an automation. * * IMPORTANT: Results contain user-generated error messages. * Always escape output when displaying: esc_html(), esc_attr(), etc. */ public function get_errors($automation_id, $limit = 50, $resolved = null) { global $wpdb; // Build query with proper parameter binding if ($resolved !== null) { $sql = $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE automation_id = %d AND resolved = %d ORDER BY created_at DESC LIMIT %d", $automation_id, $resolved ? 1 : 0, $limit ); } else { $sql = $wpdb->prepare( "SELECT * FROM {$this->table_name} WHERE automation_id = %d ORDER BY created_at DESC LIMIT %d", $automation_id, $limit ); } $results = $wpdb->get_results($sql, ARRAY_A); // Sanitize error messages for safe output if (is_array($results)) { foreach ($results as &$result) { $result['error_message'] = sanitize_text_field($result['error_message']); $result['error_type'] = sanitize_text_field($result['error_type']); } } return $results; } /** * Get error count for an automation. */ public function get_error_count($automation_id, $resolved = null) { global $wpdb; // Build query with proper parameter binding if ($resolved !== null) { $sql = $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE automation_id = %d AND resolved = %d", $automation_id, $resolved ? 1 : 0 ); } else { $sql = $wpdb->prepare( "SELECT COUNT(*) FROM {$this->table_name} WHERE automation_id = %d", $automation_id ); } return (int) $wpdb->get_var($sql); } /** * Get error counts for multiple automations in a single query. * Prevents N+1 query problems when displaying dashboard. * * @param array $automation_ids Array of automation IDs * @param bool|null $resolved Filter by resolved status (null = all) * @return array Associative array of automation_id => count */ public function get_error_counts_bulk($automation_ids, $resolved = null) { global $wpdb; if (empty($automation_ids)) { return array(); } // Sanitize all IDs $automation_ids = array_map('absint', $automation_ids); $placeholders = implode(',', array_fill(0, count($automation_ids), '%d')); // Build query if ($resolved !== null) { $sql = $wpdb->prepare( "SELECT automation_id, COUNT(*) as error_count FROM {$this->table_name} WHERE automation_id IN ($placeholders) AND resolved = %d GROUP BY automation_id", array_merge($automation_ids, array($resolved ? 1 : 0)) ); } else { $sql = $wpdb->prepare( "SELECT automation_id, COUNT(*) as error_count FROM {$this->table_name} WHERE automation_id IN ($placeholders) GROUP BY automation_id", $automation_ids ); } $results = $wpdb->get_results($sql, ARRAY_A); // Convert to associative array $counts = array(); foreach ($results as $row) { $counts[(int)$row['automation_id']] = (int)$row['error_count']; } return $counts; } /** * Mark error as resolved. */ public function mark_resolved($error_id) { global $wpdb; return $wpdb->update( $this->table_name, array('resolved' => 1), array('id' => $error_id), array('%d'), array('%d') ); } /** * Clean up old error logs. * * @param int $days Number of days to retain resolved errors. Default 90 days. * Can be filtered using 'wpfmj_error_log_retention_days'. */ public function cleanup_old_logs($days = null) { global $wpdb; // Allow filtering of retention period if ($days === null) { $days = apply_filters('wpfmj_error_log_retention_days', 90); } // Ensure positive integer $days = absint($days); if ($days < 1) { $days = 90; // Fallback to default } $date = gmdate('Y-m-d H:i:s', strtotime("-{$days} days")); $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM {$this->table_name} WHERE created_at < %s AND resolved = 1", $date ) ); // Log cleanup activity if ($deleted > 0) { error_log("WPFMJ: Cleaned up {$deleted} error log entries older than {$days} days"); } return $deleted; } /** * Get recent errors across all automations. */ public function get_recent_errors($limit = 20) { global $wpdb; $sql = "SELECT * FROM {$this->table_name} ORDER BY created_at DESC LIMIT %d"; return $wpdb->get_results($wpdb->prepare($sql, $limit), ARRAY_A); } } // Register cleanup cron job handler add_action('wpfmj_cleanup_error_logs', function() { $logger = new WPFMJ_Error_Logger(); // Retention period can be customized via filter $logger->cleanup_old_logs(); });