ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-15 15:09:33
Editing: ErrorStats.php
Cancel
Save Changes
<?php /** * Error statistics tracking. * * Handles tracking of email sending errors for usage stats. * * @since 4.8.0 */ namespace WPMailSMTP\UsageTracking; use WPMailSMTP\Helpers\Helpers; use WPMailSMTP\MailCatcherInterface; use WPMailSMTP\Options; /** * Class ErrorStats. * * Tracks email sending errors in memory during a request, * then flushes to the database on shutdown if any errors were recorded. * * @since 4.8.0 */ class ErrorStats { /** * Option name to store error stats. * * @since 4.8.0 * * @var string */ const OPTION_NAME = 'wp_mail_smtp_email_sending_errors_stat'; /** * Max length for error code string. * * @since 4.8.0 * * @var int */ const MAX_ERROR_CODE_LENGTH = 50; /** * Error stats accumulated during the current request. * * @since 4.8.0 * * @var array */ private $pending_stats = []; /** * Whether the shutdown flush hook has been registered. * * @since 4.8.0 * * @var bool */ private $shutdown_registered = false; /** * Register hooks. * * @since 4.8.0 */ public function hooks() { // Track email sending errors. add_action( 'wp_mail_smtp_mailcatcher_send_failed', [ $this, 'track_send_failed' ], 10, 7 ); // Track email delivery failures (webhooks, delivery verification). add_action( 'wp_mail_smtp_email_delivery_failed', [ $this, 'track_delivery_failed' ], 10, 3 ); // Add data to usage tracking. add_filter( 'wp_mail_smtp_usage_tracking_get_data', [ $this, 'add_usage_stats' ] ); } /** * Track email sending error from MailCatcher. * * @since 4.8.0 * @since 4.9.0 Added $response_code, $connection, and $error_key params. * * @param string $error_message Error message. * @param MailCatcherInterface $mailcatcher The MailCatcher object. * @param string $mailer_slug Current mailer name. * @param string $error_code Error code. * @param int $response_code HTTP response code. * @param ConnectionInterface $connection Connection object or null. * @param string $error_key Pre-built composite error key. */ public function track_send_failed( $error_message, $mailcatcher, $mailer_slug, $error_code = '', $response_code = 0, $connection = null, $error_key = '' ) { $mailer_slug = $this->resolve_mailer_slug( $mailer_slug ); $this->track_error( $mailer_slug, $error_key ); } /** * Track email delivery failure from webhooks or delivery verification. * * @since 4.8.0 * * @param string $mailer_slug Current mailer name. * @param string $error_code Error code. * @param string $error_message Error message. */ public function track_delivery_failed( $mailer_slug, $error_code, $error_message = '' ) { $error_key = $this->build_error_key( 'delivery', (string) $error_code, (string) $error_message ); $this->track_error( $mailer_slug, $error_key ); } /** * Track email sending error. * * Accumulates error in memory. All accumulated stats are flushed * to the database on shutdown. * * @since 4.8.0 * * @param string $mailer_slug Current mailer name. * @param string $error_key Normalized error key. */ private function track_error( $mailer_slug, $error_key ) { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks if ( ! isset( $this->pending_stats[ $mailer_slug ] ) ) { $this->pending_stats[ $mailer_slug ] = []; } if ( ! isset( $this->pending_stats[ $mailer_slug ][ $error_key ] ) ) { $this->pending_stats[ $mailer_slug ][ $error_key ] = 0; } $this->pending_stats[ $mailer_slug ][ $error_key ]++; // Register shutdown flush lazily on first tracked error. if ( ! $this->shutdown_registered ) { add_action( 'shutdown', [ $this, 'flush' ] ); $this->shutdown_registered = true; } } /** * Build error key in format: {prefix}:{error_code}:{sanitized_message}. * * Uses "-" for missing parts to keep the format parseable. * * @since 4.8.0 * * @param string $prefix First segment — HTTP response code or category (e.g. "401", "delivery"). * @param string $error_code API-specific error code. * @param string $error_message Error message text. * * @return string Normalized error key. */ private function build_error_key( $prefix, $error_code, $error_message ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $part_response = ! empty( $prefix ) ? $prefix : '-'; $part_code = ! empty( $error_code ) && $error_code !== 'unknown' && (string) $error_code !== $part_response ? $error_code : '-'; $part_message = ! empty( $error_message ) ? $this->sanitize_message( $error_message ) : '-'; // Remove response code and error code from message to save space. if ( $part_response !== '-' ) { $part_message = str_replace( sanitize_title( $part_response ), '', $part_message ); } if ( $part_code !== '-' ) { $part_message = str_replace( sanitize_title( $part_code ), '', $part_message ); } $part_message = preg_replace( '/-{2,}/', '-', $part_message ); $part_message = trim( $part_message, '-' ); if ( empty( $part_message ) ) { $part_message = '-'; } $key = $part_response . ':' . $part_code . ':' . $part_message; if ( ! function_exists( 'mb_strlen' ) ) { Helpers::include_mbstring_polyfill(); } if ( mb_strlen( $key ) > self::MAX_ERROR_CODE_LENGTH ) { $key = mb_substr( $key, 0, self::MAX_ERROR_CODE_LENGTH ); // Cut at last hyphen to avoid partial words. $last_hyphen = strrpos( $key, '-' ); if ( $last_hyphen !== false && $last_hyphen > strrpos( $key, ':' ) ) { $key = substr( $key, 0, $last_hyphen ); } } return $key; } /** * Sanitize error message into a short aggregatable slug. * * Strips dynamic content (emails, domains, URLs, UUIDs, quoted strings), * takes first ~5 words, and slugifies. * * @since 4.8.0 * * @param string $message Error message. * * @return string Sanitized message slug. */ private function sanitize_message( $message ) { // Strip HTML entities. $message = html_entity_decode( $message, ENT_QUOTES, 'UTF-8' ); // Strip emails. $message = preg_replace( '/\S+@\S+\.\S+/', '', $message ); // Strip URLs. $message = preg_replace( '#https?://\S+#i', '', $message ); // Strip dot-separated strings (domains, namespaces, etc.). $message = preg_replace( '/\b\S+\.\S+\b/', '', $message ); // Strip UUIDs. $message = preg_replace( '/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i', '', $message ); // Strip quoted strings. $message = preg_replace( '/["\'][^"\']*["\']/', '', $message ); // Collapse whitespace and trim. $message = trim( preg_replace( '/\s+/', ' ', $message ) ); return sanitize_title( $message ); } /** * Resolve mailer slug, distinguishing one-click setups. * * @since 4.8.0 * * @param string $mailer_slug Original mailer slug. * * @return string Resolved mailer slug. */ private function resolve_mailer_slug( $mailer_slug ) { if ( $mailer_slug === 'gmail' && Options::init()->get( 'gmail', 'one_click_setup_enabled' ) ) { return 'gmail_one_click'; } if ( $mailer_slug === 'outlook' && Options::init()->get( 'outlook', 'one_click_setup_enabled' ) ) { return 'outlook_one_click'; } return $mailer_slug; } /** * Flush accumulated stats to the database. * * Reads current stored stats, merges with pending stats, and saves. * Bails early if no errors were tracked during this request. * * @since 4.8.0 */ public function flush() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh if ( empty( $this->pending_stats ) ) { return; } $stored = get_option( self::OPTION_NAME, [] ); if ( ! is_array( $stored ) ) { $stored = []; } foreach ( $this->pending_stats as $mailer_slug => $error_codes ) { if ( ! isset( $stored[ $mailer_slug ] ) ) { $stored[ $mailer_slug ] = []; } foreach ( $error_codes as $error_code => $count ) { // Apply overflow limit against the merged state. if ( count( $stored[ $mailer_slug ] ) >= 50 && ! isset( $stored[ $mailer_slug ][ $error_code ] ) ) { $error_code = 'overflow'; } if ( ! isset( $stored[ $mailer_slug ][ $error_code ] ) ) { $stored[ $mailer_slug ][ $error_code ] = 0; } $stored[ $mailer_slug ][ $error_code ] += $count; } } update_option( self::OPTION_NAME, $stored, false ); $this->pending_stats = []; } /** * Add usage stats data. * * @since 4.8.0 * * @param array $data Usage data. * * @return array */ public function add_usage_stats( $data ) { // Flush any pending stats before reading. $this->flush(); $stats = get_option( self::OPTION_NAME, [] ); $data['email_sending_errors_stat'] = is_array( $stats ) ? $stats : []; // Reset after collecting. delete_option( self::OPTION_NAME ); return $data; } }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete