PluginProbe
WP EXtra – One Click Optimize / 8.7.0
WP EXtra – One Click Optimize v8.7.0
8.7.1 8.6.8 8.7.0 trunk 5.9 8.0 8.5.0 8.5.4 8.5.5 8.6.0 8.6.1 8.6.2 8.6.3 8.6.5
wp-extra / src / Modules / Common / SMTP.php

SMTP.php in WP EXtra – One Click Optimize 8.7.0, at src/Modules/Common/SMTP.php

374 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPEXtra\Modules\Common;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8 use WPEXtra\Settings;
9 use WPEXtra\Helper;
10 use WPEXtra\Base;
11
12 class SMTP extends Base {
13
14 private static $active_account_key = null;
15 private static $active_account_name = 'Default';
16 private static $current_mail_data = null;
17
18 public function __construct() {
19 parent::__construct();
20
21 $from_email = Helper::get_option('from_email');
22 if (!empty($from_email)) {
23 add_filter('wp_mail_from', function($email) use ($from_email) {
24 return sanitize_email($from_email);
25 });
26 }
27
28 $from_name = Helper::get_option('from_name');
29 if (!empty($from_name)) {
30 add_filter('wp_mail_from_name', function($name) use ($from_name) {
31 return $from_name;
32 });
33 }
34
35 add_action('phpmailer_init', [$this, 'process_mail']);
36 add_filter('wp_mail', [$this, 'capture_mail_data']);
37 add_action('wp_mail_succeeded', [$this, 'on_mail_succeeded']);
38 add_action('wp_mail_failed', [$this, 'on_mail_failed']);
39
40 if (Helper::get_option('email_domain')) {
41 add_action('register_post', [$this, 'is_valid_email_domain'], 10, 3);
42 }
43
44 if (is_admin()) {
45 add_action('admin_notices', [$this, 'display_quota_admin_notices']);
46 }
47
48 // Initialize table if logging is enabled
49 if (Helper::get_option('email_log')) {
50 $this->maybe_create_logs_table();
51 }
52 }
53
54 public function display_quota_admin_notices() {
55 if (!current_user_can('manage_options')) {
56 return;
57 }
58
59 $today = current_time('Y-m-d');
60 $accounts = (array) Helper::get_option('smtp_accounts', []);
61 if (empty($accounts)) {
62 return;
63 }
64
65 $total_accounts = 0;
66 $exhausted_accounts = [];
67
68 foreach ($accounts as $idx => $acc) {
69 if (empty($acc['username']) || empty($acc['password'])) {
70 continue;
71 }
72 $total_accounts++;
73 $limit = intval($acc['daily_limit'] ?? 0);
74 $sent_today = intval(get_option('wpex_smtp_sent_' . $idx . '_' . $today, 0));
75 $title = !empty($acc['title']) ? $acc['title'] : ($acc['username'] ?? 'Account #' . ($idx + 1));
76
77 if ($limit > 0 && $sent_today >= $limit) {
78 $exhausted_accounts[] = [
79 'title' => $title,
80 'sent' => $sent_today,
81 'limit' => $limit
82 ];
83 }
84 }
85
86 if (empty($exhausted_accounts)) {
87 return;
88 }
89
90 if (count($exhausted_accounts) >= $total_accounts && $total_accounts > 0) {
91 ?>
92 <div class="notice notice-error is-dismissible">
93 <p>
94 <strong><?php esc_html_e('WP EXtra - SMTP Quota Alert:', 'wp-extra'); ?></strong>
95 <?php printf(
96 esc_html__('All %d configured SMTP accounts have reached their daily sending limit for today (%s). Outgoing WordPress emails may fail or be delayed.', 'wp-extra'),
97 $total_accounts,
98 esc_html($today)
99 ); ?>
100 <a href="<?php echo esc_url(admin_url('admin.php?page=wp-extra')); ?>"><?php esc_html_e('Manage SMTP Settings &rarr;', 'wp-extra'); ?></a>
101 </p>
102 </div>
103 <?php
104 } else {
105 $names = implode(', ', array_map(function($a) {
106 return sprintf('<strong>%s</strong> (%d/%d)', esc_html($a['title']), $a['sent'], $a['limit']);
107 }, $exhausted_accounts));
108 ?>
109 <div class="notice notice-warning is-dismissible">
110 <p>
111 <strong><?php esc_html_e('WP EXtra - SMTP Limit Reached:', 'wp-extra'); ?></strong>
112 <?php printf(
113 esc_html__('The following account(s) reached their daily quota today: %s. Outgoing emails are automatically routed to your next available SMTP account.', 'wp-extra'),
114 $names
115 ); ?>
116 </p>
117 </div>
118 <?php
119 }
120 }
121
122 public function maybe_create_logs_table() {
123 global $wpdb;
124 $table_name = $wpdb->prefix . 'wpex_email_logs';
125 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
126 $charset_collate = $wpdb->get_charset_collate();
127 $sql = "CREATE TABLE $table_name (
128 id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
129 to_email VARCHAR(255) NOT NULL,
130 subject VARCHAR(255) DEFAULT '',
131 message LONGTEXT DEFAULT '',
132 headers TEXT DEFAULT '',
133 status VARCHAR(20) NOT NULL DEFAULT 'success',
134 error_message TEXT DEFAULT '',
135 mailer_account VARCHAR(100) DEFAULT '',
136 created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
137 PRIMARY KEY (id),
138 KEY status (status),
139 KEY to_email (to_email(191)),
140 KEY created_at (created_at)
141 ) $charset_collate;";
142
143 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
144 dbDelta($sql);
145 }
146 }
147
148 public function process_mail($phpmailer) {
149 $today = current_time('Y-m-d');
150 $accounts = (array) Helper::get_option('smtp_accounts', []);
151
152 // Fallback for legacy single account settings if repeater is empty
153 if (empty($accounts) && Helper::get_option('smtp_username') && Helper::get_option('smtp_password')) {
154 $accounts = [
155 [
156 'title' => 'Main Account',
157 'host' => Helper::get_option('smtp') ? 'smtp.gmail.com' : Helper::get_option('smtp_host', 'smtp.gmail.com'),
158 'port' => Helper::get_option('smtp') ? 465 : intval(Helper::get_option('smtp_port', 465)),
159 'encryption' => Helper::get_option('smtp') ? 'ssl' : Helper::get_option('smtp_encryption', 'ssl'),
160 'username' => Helper::get_option('smtp_username'),
161 'password' => Helper::get_option('smtp_password'),
162 'daily_limit'=> 0
163 ]
164 ];
165 }
166
167 if (empty($accounts)) {
168 self::$active_account_name = 'WP Mail (PHP)';
169 return $phpmailer;
170 }
171
172 $selected_account = null;
173 $selected_idx = null;
174
175 // Find the first account that hasn't exceeded its daily quota
176 foreach ($accounts as $idx => $acc) {
177 if (empty($acc['username']) || empty($acc['password'])) {
178 continue;
179 }
180 $limit = intval($acc['daily_limit'] ?? 0);
181 $sent_today = intval(get_option('wpex_smtp_sent_' . $idx . '_' . $today, 0));
182
183 if ($limit === 0 || $sent_today < $limit) {
184 $selected_account = $acc;
185 $selected_idx = $idx;
186 break;
187 }
188 }
189
190 // If all accounts reached their limit, fallback to the first active account
191 if ($selected_account === null) {
192 foreach ($accounts as $idx => $acc) {
193 if (!empty($acc['username']) && !empty($acc['password'])) {
194 $selected_account = $acc;
195 $selected_idx = $idx;
196 break;
197 }
198 }
199 }
200
201 if ($selected_account) {
202 $providers = [
203 'gmail' => ['host' => 'smtp.gmail.com', 'port' => 587, 'encryption' => 'tls'],
204 'mailgun' => ['host' => 'smtp.mailgun.org', 'port' => 587, 'encryption' => 'tls'],
205 'outlook' => ['host' => 'smtp.office365.com', 'port' => 587, 'encryption' => 'tls'],
206 'yahoo' => ['host' => 'smtp.mail.yahoo.com', 'port' => 587, 'encryption' => 'tls'],
207 'aws_ses' => ['host' => 'email-smtp.us-east-1.amazonaws.com', 'port' => 587, 'encryption' => 'tls'],
208 'zoho' => ['host' => 'smtp.zoho.com', 'port' => 587, 'encryption' => 'tls'],
209 'sendgrid' => ['host' => 'smtp.sendgrid.net', 'port' => 587, 'encryption' => 'tls'],
210 'sendinblue' => ['host' => 'smtp-relay.brevo.com', 'port' => 587, 'encryption' => 'tls'],
211 ];
212
213 $prov_key = $selected_account['provider'] ?? 'gmail';
214 $preset = $providers[$prov_key] ?? null;
215
216 self::$active_account_key = $selected_idx;
217 self::$active_account_name = !empty($selected_account['title']) ? $selected_account['title'] : (!empty($selected_account['username']) ? $selected_account['username'] : 'SMTP Account #' . ($selected_idx + 1));
218
219 $host = !empty($selected_account['host']) ? $selected_account['host'] : ($preset['host'] ?? 'smtp.gmail.com');
220 $port = !empty($selected_account['port']) ? intval($selected_account['port']) : ($preset['port'] ?? 465);
221 $encryption = !empty($selected_account['encryption']) && $selected_account['encryption'] !== 'none' ? $selected_account['encryption'] : ($preset['encryption'] ?? 'ssl');
222
223 $phpmailer->Host = $host;
224 $phpmailer->Port = $port;
225 $phpmailer->SMTPSecure = ($encryption !== 'none') ? $encryption : '';
226 $phpmailer->SMTPAuth = true;
227 $phpmailer->Username = $selected_account['username'];
228
229 $raw_pass = $selected_account['password'] ?? '';
230 $decoded = base64_decode($raw_pass, true);
231 $phpmailer->Password = ($decoded !== false && base64_encode($decoded) === $raw_pass) ? $decoded : $raw_pass;
232
233 $global_from_email = Helper::get_option('from_email');
234 $global_from_name = Helper::get_option('from_name');
235
236 $phpmailer->From = !empty($global_from_email) ? sanitize_email($global_from_email) : sanitize_email($selected_account['username']);
237 if (!empty($global_from_name)) {
238 $phpmailer->FromName = $global_from_name;
239 }
240 }
241
242 $smtp_options = (array) Helper::get_option('smtp_options', []);
243 if (in_array('noverifyssl', $smtp_options, true)) {
244 $phpmailer->SMTPOptions = [
245 'ssl' => [
246 'verify_peer' => false,
247 'verify_peer_name' => false,
248 'allow_self_signed' => true
249 ]
250 ];
251 }
252
253 $phpmailer->IsSMTP();
254 return $phpmailer;
255 }
256
257 public function capture_mail_data($mail_args) {
258 self::$current_mail_data = $mail_args;
259 return $mail_args;
260 }
261
262 public function on_mail_succeeded() {
263 $today = current_time('Y-m-d');
264
265 // Increment daily sent counter
266 if (self::$active_account_key !== null) {
267 $opt_name = 'wpex_smtp_sent_' . self::$active_account_key . '_' . $today;
268 $count = intval(get_option($opt_name, 0));
269 update_option($opt_name, $count + 1, false);
270 }
271
272 // Record log if enabled
273 if (Helper::get_option('email_log') && !empty(self::$current_mail_data)) {
274 $this->save_email_log('success');
275 }
276 }
277
278 public function on_mail_failed($wp_error) {
279 if (Helper::get_option('email_log') && !empty(self::$current_mail_data)) {
280 $error_msg = ($wp_error instanceof \WP_Error) ? $wp_error->get_error_message() : '';
281 $this->save_email_log('failed', $error_msg);
282 }
283 }
284
285 private function save_email_log($status = 'success', $error_msg = '') {
286 global $wpdb;
287 $table_name = $wpdb->prefix . 'wpex_email_logs';
288
289 $data = self::$current_mail_data;
290 if (empty($data)) {
291 return;
292 }
293
294 $to = is_array($data['to']) ? implode(', ', $data['to']) : (string)$data['to'];
295 $subject = isset($data['subject']) ? (string)$data['subject'] : '';
296 $message = isset($data['message']) ? (string)$data['message'] : '';
297 $headers = isset($data['headers']) ? (is_array($data['headers']) ? maybe_serialize($data['headers']) : (string)$data['headers']) : '';
298
299 $wpdb->insert(
300 $table_name,
301 [
302 'to_email' => $to,
303 'subject' => $subject,
304 'message' => $message,
305 'headers' => $headers,
306 'status' => $status,
307 'error_message' => $error_msg,
308 'mailer_account' => self::$active_account_name,
309 'created_at' => current_time('mysql'),
310 ],
311 ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
312 );
313
314 // Auto purge old logs based on retention days
315 $retention = intval(Helper::get_option('email_log_retention', 30));
316 if ($retention > 0 && rand(1, 20) === 1) {
317 $wpdb->query($wpdb->prepare(
318 "DELETE FROM $table_name WHERE created_at < DATE_SUB(NOW(), INTERVAL %d DAY)",
319 $retention
320 ));
321 }
322 }
323
324 public function is_valid_email_domain($login, $email, $errors) {
325 $valid_email_domains_string = Helper::get_option('email_domain', '');
326 $valid_email_domains = array_filter(array_map('trim', explode("\n", $valid_email_domains_string)));
327 if (empty($valid_email_domains)) {
328 return;
329 }
330 $email_domain = substr(strrchr($email, "@"), 1);
331 if (!in_array($email_domain, $valid_email_domains, true)) {
332 $errors->add('domain_whitelist_error', __('<strong>ERROR</strong>: you can only register using allowed email domains', 'wp-extra'));
333 }
334 }
335
336 protected $features = [
337 'smtp_options',
338 'no_emails',
339 ];
340
341 public function smtp_options() {
342 $options = (array) Helper::get_option('smtp_options', []);
343 if (in_array('antispam', $options, true)) {
344 add_action('wp_enqueue_scripts', [$this, 'smtpmail_scripts'], 99);
345 }
346 }
347
348 public function smtpmail_scripts() {
349 $anti_spam_form = in_array('antispam', (array) Helper::get_option('smtp_options', []), true) ? 1 : 0;
350 wp_enqueue_script('security', plugins_url('/assets/js/security.min.js', WPEX_FILE), ['jquery'], defined('WPEX_VERSION') ? WPEX_VERSION : null, true);
351 wp_localize_script('security', 'security_setting', ['anti_spam_form' => $anti_spam_form]);
352 }
353
354 public function no_emails() {
355 $no_emails = (array) Helper::get_option('no_emails', []);
356 if (in_array('remove_admin', $no_emails, true)) {
357 add_filter('admin_email_check_interval', '__return_false');
358 }
359 if (in_array('auto_update', $no_emails, true)) {
360 add_filter('send_core_update_notification_email', '__return_false');
361 add_filter('auto_plugin_update_send_email', '__return_false');
362 add_filter('auto_theme_update_send_email', '__return_false');
363 }
364 if (in_array('new_user', $no_emails, true)) {
365 add_filter('wp_send_new_user_notification_to_admin', '__return_false');
366 }
367 if (in_array('password_reset', $no_emails, true)) {
368 remove_action('after_password_reset', 'wp_password_change_notification');
369 add_filter('send_password_change_email', '__return_false');
370 add_filter('woocommerce_disable_password_change_notification', '__return_false');
371 }
372 }
373
374 }