maybe_create_logs_table(); } } public function display_quota_admin_notices() { if (!current_user_can('manage_options')) { return; } $today = current_time('Y-m-d'); $accounts = (array) Helper::get_option('smtp_accounts', []); if (empty($accounts)) { return; } $total_accounts = 0; $exhausted_accounts = []; foreach ($accounts as $idx => $acc) { if (empty($acc['username']) || empty($acc['password'])) { continue; } $total_accounts++; $limit = intval($acc['daily_limit'] ?? 0); $sent_today = intval(get_option('wpex_smtp_sent_' . $idx . '_' . $today, 0)); $title = !empty($acc['title']) ? $acc['title'] : ($acc['username'] ?? 'Account #' . ($idx + 1)); if ($limit > 0 && $sent_today >= $limit) { $exhausted_accounts[] = [ 'title' => $title, 'sent' => $sent_today, 'limit' => $limit ]; } } if (empty($exhausted_accounts)) { return; } if (count($exhausted_accounts) >= $total_accounts && $total_accounts > 0) { ?>

%s (%d/%d)', esc_html($a['title']), $a['sent'], $a['limit']); }, $exhausted_accounts)); ?>

prefix . 'wpex_email_logs'; if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) { $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE $table_name ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, to_email VARCHAR(255) NOT NULL, subject VARCHAR(255) DEFAULT '', message LONGTEXT DEFAULT '', headers TEXT DEFAULT '', status VARCHAR(20) NOT NULL DEFAULT 'success', error_message TEXT DEFAULT '', mailer_account VARCHAR(100) DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY status (status), KEY to_email (to_email(191)), KEY created_at (created_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); } } public function process_mail($phpmailer) { $today = current_time('Y-m-d'); $accounts = (array) Helper::get_option('smtp_accounts', []); // Fallback for legacy single account settings if repeater is empty if (empty($accounts) && Helper::get_option('smtp_username') && Helper::get_option('smtp_password')) { $accounts = [ [ 'title' => 'Main Account', 'host' => Helper::get_option('smtp') ? 'smtp.gmail.com' : Helper::get_option('smtp_host', 'smtp.gmail.com'), 'port' => Helper::get_option('smtp') ? 465 : intval(Helper::get_option('smtp_port', 465)), 'encryption' => Helper::get_option('smtp') ? 'ssl' : Helper::get_option('smtp_encryption', 'ssl'), 'username' => Helper::get_option('smtp_username'), 'password' => Helper::get_option('smtp_password'), 'daily_limit'=> 0 ] ]; } if (empty($accounts)) { self::$active_account_name = 'WP Mail (PHP)'; return $phpmailer; } $selected_account = null; $selected_idx = null; // Find the first account that hasn't exceeded its daily quota foreach ($accounts as $idx => $acc) { if (empty($acc['username']) || empty($acc['password'])) { continue; } $limit = intval($acc['daily_limit'] ?? 0); $sent_today = intval(get_option('wpex_smtp_sent_' . $idx . '_' . $today, 0)); if ($limit === 0 || $sent_today < $limit) { $selected_account = $acc; $selected_idx = $idx; break; } } // If all accounts reached their limit, fallback to the first active account if ($selected_account === null) { foreach ($accounts as $idx => $acc) { if (!empty($acc['username']) && !empty($acc['password'])) { $selected_account = $acc; $selected_idx = $idx; break; } } } if ($selected_account) { $providers = [ 'gmail' => ['host' => 'smtp.gmail.com', 'port' => 587, 'encryption' => 'tls'], 'mailgun' => ['host' => 'smtp.mailgun.org', 'port' => 587, 'encryption' => 'tls'], 'outlook' => ['host' => 'smtp.office365.com', 'port' => 587, 'encryption' => 'tls'], 'yahoo' => ['host' => 'smtp.mail.yahoo.com', 'port' => 587, 'encryption' => 'tls'], 'aws_ses' => ['host' => 'email-smtp.us-east-1.amazonaws.com', 'port' => 587, 'encryption' => 'tls'], 'zoho' => ['host' => 'smtp.zoho.com', 'port' => 587, 'encryption' => 'tls'], 'sendgrid' => ['host' => 'smtp.sendgrid.net', 'port' => 587, 'encryption' => 'tls'], 'sendinblue' => ['host' => 'smtp-relay.brevo.com', 'port' => 587, 'encryption' => 'tls'], ]; $prov_key = $selected_account['provider'] ?? 'gmail'; $preset = $providers[$prov_key] ?? null; self::$active_account_key = $selected_idx; self::$active_account_name = !empty($selected_account['title']) ? $selected_account['title'] : (!empty($selected_account['username']) ? $selected_account['username'] : 'SMTP Account #' . ($selected_idx + 1)); $host = !empty($selected_account['host']) ? $selected_account['host'] : ($preset['host'] ?? 'smtp.gmail.com'); $port = !empty($selected_account['port']) ? intval($selected_account['port']) : ($preset['port'] ?? 465); $encryption = !empty($selected_account['encryption']) && $selected_account['encryption'] !== 'none' ? $selected_account['encryption'] : ($preset['encryption'] ?? 'ssl'); $phpmailer->Host = $host; $phpmailer->Port = $port; $phpmailer->SMTPSecure = ($encryption !== 'none') ? $encryption : ''; $phpmailer->SMTPAuth = true; $phpmailer->Username = $selected_account['username']; $raw_pass = $selected_account['password'] ?? ''; $decoded = base64_decode($raw_pass, true); $phpmailer->Password = ($decoded !== false && base64_encode($decoded) === $raw_pass) ? $decoded : $raw_pass; $global_from_email = Helper::get_option('from_email'); $global_from_name = Helper::get_option('from_name'); $phpmailer->From = !empty($global_from_email) ? sanitize_email($global_from_email) : sanitize_email($selected_account['username']); if (!empty($global_from_name)) { $phpmailer->FromName = $global_from_name; } } $smtp_options = (array) Helper::get_option('smtp_options', []); if (in_array('noverifyssl', $smtp_options, true)) { $phpmailer->SMTPOptions = [ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true ] ]; } $phpmailer->IsSMTP(); return $phpmailer; } public function capture_mail_data($mail_args) { self::$current_mail_data = $mail_args; return $mail_args; } public function on_mail_succeeded() { $today = current_time('Y-m-d'); // Increment daily sent counter if (self::$active_account_key !== null) { $opt_name = 'wpex_smtp_sent_' . self::$active_account_key . '_' . $today; $count = intval(get_option($opt_name, 0)); update_option($opt_name, $count + 1, false); } // Record log if enabled if (Helper::get_option('email_log') && !empty(self::$current_mail_data)) { $this->save_email_log('success'); } } public function on_mail_failed($wp_error) { if (Helper::get_option('email_log') && !empty(self::$current_mail_data)) { $error_msg = ($wp_error instanceof \WP_Error) ? $wp_error->get_error_message() : ''; $this->save_email_log('failed', $error_msg); } } private function save_email_log($status = 'success', $error_msg = '') { global $wpdb; $table_name = $wpdb->prefix . 'wpex_email_logs'; $data = self::$current_mail_data; if (empty($data)) { return; } $to = is_array($data['to']) ? implode(', ', $data['to']) : (string)$data['to']; $subject = isset($data['subject']) ? (string)$data['subject'] : ''; $message = isset($data['message']) ? (string)$data['message'] : ''; $headers = isset($data['headers']) ? (is_array($data['headers']) ? maybe_serialize($data['headers']) : (string)$data['headers']) : ''; $wpdb->insert( $table_name, [ 'to_email' => $to, 'subject' => $subject, 'message' => $message, 'headers' => $headers, 'status' => $status, 'error_message' => $error_msg, 'mailer_account' => self::$active_account_name, 'created_at' => current_time('mysql'), ], ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'] ); // Auto purge old logs based on retention days $retention = intval(Helper::get_option('email_log_retention', 30)); if ($retention > 0 && rand(1, 20) === 1) { $wpdb->query($wpdb->prepare( "DELETE FROM $table_name WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $retention )); } } public function is_valid_email_domain($login, $email, $errors) { $valid_email_domains_string = Helper::get_option('email_domain', ''); $valid_email_domains = array_filter(array_map('trim', explode("\n", $valid_email_domains_string))); if (empty($valid_email_domains)) { return; } $email_domain = substr(strrchr($email, "@"), 1); if (!in_array($email_domain, $valid_email_domains, true)) { $errors->add('domain_whitelist_error', __('ERROR: you can only register using allowed email domains', 'wp-extra')); } } protected $features = [ 'smtp_options', 'no_emails', ]; public function smtp_options() { $options = (array) Helper::get_option('smtp_options', []); if (in_array('antispam', $options, true)) { add_action('wp_enqueue_scripts', [$this, 'smtpmail_scripts'], 99); } } public function smtpmail_scripts() { $anti_spam_form = in_array('antispam', (array) Helper::get_option('smtp_options', []), true) ? 1 : 0; wp_enqueue_script('security', plugins_url('/assets/js/security.min.js', WPEX_FILE), ['jquery'], defined('WPEX_VERSION') ? WPEX_VERSION : null, true); wp_localize_script('security', 'security_setting', ['anti_spam_form' => $anti_spam_form]); } public function no_emails() { $no_emails = (array) Helper::get_option('no_emails', []); if (in_array('remove_admin', $no_emails, true)) { add_filter('admin_email_check_interval', '__return_false'); } if (in_array('auto_update', $no_emails, true)) { add_filter('send_core_update_notification_email', '__return_false'); add_filter('auto_plugin_update_send_email', '__return_false'); add_filter('auto_theme_update_send_email', '__return_false'); } if (in_array('new_user', $no_emails, true)) { add_filter('wp_send_new_user_notification_to_admin', '__return_false'); } if (in_array('password_reset', $no_emails, true)) { remove_action('after_password_reset', 'wp_password_change_notification'); add_filter('send_password_change_email', '__return_false'); add_filter('woocommerce_disable_password_change_notification', '__return_false'); } } }