Changeset 3482419
- Timestamp:
- 03/14/2026 09:03:24 AM (3 weeks ago)
- Location:
- mesh-smtp
- Files:
-
- 2 added
- 12 edited
- 1 copied
-
tags/1.2.4 (copied) (copied from mesh-smtp/trunk)
-
tags/1.2.4/admin/settings-page.php (modified) (14 diffs)
-
tags/1.2.4/assets/admin.css (modified) (10 diffs)
-
tags/1.2.4/assets/admin.js (modified) (4 diffs)
-
tags/1.2.4/mesh-smtp.php (modified) (4 diffs)
-
tags/1.2.4/readme.txt (modified) (5 diffs)
-
tags/1.2.4/screenshots (added)
-
tags/1.2.4/uninstall.php (modified) (1 diff)
-
trunk/admin/settings-page.php (modified) (14 diffs)
-
trunk/assets/admin.css (modified) (10 diffs)
-
trunk/assets/admin.js (modified) (4 diffs)
-
trunk/mesh-smtp.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (5 diffs)
-
trunk/screenshots (added)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
mesh-smtp/tags/1.2.4/admin/settings-page.php
r3481868 r3482419 10 10 add_action('admin_post_meshsmtp_send_test_email', 'meshsmtp_handle_test_email'); 11 11 add_action('wp_mail_failed', 'meshsmtp_capture_mail_error'); 12 add_action('wp_ajax_meshsmtp_detect_provider_hint', 'meshsmtp_ajax_detect_provider_hint'); 12 13 13 14 /** … … 65 66 true 66 67 ); 68 69 wp_localize_script( 70 'meshsmtp-admin-script', 71 'meshsmtpAdmin', 72 array( 73 'ajaxUrl' => admin_url('admin-ajax.php'), 74 'detectNonce' => wp_create_nonce('meshsmtp_detect_provider'), 75 ) 76 ); 67 77 } 68 78 … … 81 91 } 82 92 83 $host = '';93 $host = (string) $existing['host']; 84 94 if (isset($input['host'])) { 85 95 $host = sanitize_text_field(wp_unslash((string) $input['host'])); … … 87 97 } 88 98 89 $port = isset($input['port']) ? absint($input['port']) : (int) $ defaults['port'];99 $port = isset($input['port']) ? absint($input['port']) : (int) $existing['port']; 90 100 if ($port < 1 || $port > 65535) { 91 101 $port = (int) $defaults['port']; 92 102 } 93 103 94 $username = '';104 $username = (string) $existing['username']; 95 105 if (isset($input['username'])) { 96 106 $username = sanitize_text_field(wp_unslash((string) $input['username'])); … … 104 114 } 105 115 106 $from_email = '';116 $from_email = (string) $existing['from_email']; 107 117 if (isset($input['from_email'])) { 108 118 $from_email = sanitize_email(wp_unslash((string) $input['from_email'])); … … 112 122 } 113 123 114 $from_name = '';124 $from_name = (string) $existing['from_name']; 115 125 if (isset($input['from_name'])) { 116 126 $from_name = sanitize_text_field(wp_unslash((string) $input['from_name'])); … … 120 130 } 121 131 122 $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $ defaults['secure'];132 $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $existing['secure']; 123 133 if (!in_array($secure, array('none', 'ssl', 'tls'), true)) { 124 134 $secure = (string) $defaults['secure']; … … 138 148 139 149 /** 150 * Map raw mail errors into clearer hints. 151 * 152 * @param string $error_message Raw mail error message. 153 * @return string 154 */ 155 function meshsmtp_get_friendly_error_hint($error_message) { 156 $message = strtolower((string) $error_message); 157 158 if (false !== strpos($message, 'authenticate') || false !== strpos($message, 'username') || false !== strpos($message, 'password')) { 159 return __('Authentication failed. Recheck the SMTP username and password or app password.', 'mesh-smtp'); 160 } 161 162 if (false !== strpos($message, 'connect') || false !== strpos($message, 'timed out') || false !== strpos($message, 'connection refused')) { 163 return __('Could not connect to the SMTP server. Recheck the host, port, firewall, or hosting restrictions.', 'mesh-smtp'); 164 } 165 166 if (false !== strpos($message, 'tls') || false !== strpos($message, 'ssl') || false !== strpos($message, 'certificate')) { 167 return __('Encryption handshake failed. Recheck the selected TLS or SSL option and matching port.', 'mesh-smtp'); 168 } 169 170 if (false !== strpos($message, 'from') || false !== strpos($message, 'sender')) { 171 return __('The sender address may be rejected. Recheck the From Email value and mailbox permissions.', 'mesh-smtp'); 172 } 173 174 return __('Email could not be sent. Recheck the host, port, credentials, and firewall settings.', 'mesh-smtp'); 175 } 176 177 /** 178 * Extract email domain safely. 179 * 180 * @param string $email Email address. 181 * @return string 182 */ 183 function meshsmtp_get_email_domain($email) { 184 $email = sanitize_email((string) $email); 185 if (!$email || false === strpos($email, '@')) { 186 return ''; 187 } 188 189 $parts = explode('@', $email); 190 return strtolower((string) end($parts)); 191 } 192 193 /** 194 * Get normalized site domain for the current install. 195 * 196 * @return string 197 */ 198 function meshsmtp_get_site_domain() { 199 $host = wp_parse_url(home_url(), PHP_URL_HOST); 200 if (!is_string($host) || '' === $host) { 201 return ''; 202 } 203 204 $host = strtolower($host); 205 if (0 === strpos($host, 'www.')) { 206 $host = substr($host, 4); 207 } 208 209 return sanitize_text_field($host); 210 } 211 212 /** 213 * Detect a supported provider from MX hosts. 214 * 215 * @param array<int, string> $mx_hosts MX hosts. 216 * @return array{provider: string, match: string} 217 */ 218 function meshsmtp_detect_provider_from_mx_hosts($mx_hosts) { 219 if (!is_array($mx_hosts)) { 220 return array( 221 'provider' => '', 222 'match' => '', 223 ); 224 } 225 226 foreach ($mx_hosts as $mx_host) { 227 $mx_host = strtolower(sanitize_text_field((string) $mx_host)); 228 if ('' === $mx_host) { 229 continue; 230 } 231 232 if (false !== strpos($mx_host, 'hostinger')) { 233 return array( 234 'provider' => 'hostinger', 235 'match' => $mx_host, 236 ); 237 } 238 239 if (false !== strpos($mx_host, 'google.com') || false !== strpos($mx_host, 'googlemail.com')) { 240 return array( 241 'provider' => 'gmail', 242 'match' => $mx_host, 243 ); 244 } 245 246 if (false !== strpos($mx_host, 'outlook.com') || false !== strpos($mx_host, 'protection.outlook.com')) { 247 return array( 248 'provider' => 'outlook', 249 'match' => $mx_host, 250 ); 251 } 252 253 if (false !== strpos($mx_host, 'zoho')) { 254 return array( 255 'provider' => 'zoho', 256 'match' => $mx_host, 257 ); 258 } 259 } 260 261 return array( 262 'provider' => '', 263 'match' => '', 264 ); 265 } 266 267 /** 268 * AJAX endpoint for soft provider hints based on site-domain mail routing. 269 */ 270 function meshsmtp_ajax_detect_provider_hint() { 271 if (!current_user_can('manage_options')) { 272 wp_send_json_error(array('message' => __('You are not allowed to do that.', 'mesh-smtp')), 403); 273 } 274 275 check_ajax_referer('meshsmtp_detect_provider', 'nonce'); 276 277 $site_domain = meshsmtp_get_site_domain(); 278 if ('' === $site_domain) { 279 wp_send_json_success( 280 array( 281 'provider' => '', 282 'siteDomain' => '', 283 'source' => '', 284 'match' => '', 285 ) 286 ); 287 } 288 289 $mx_hosts = array(); 290 291 if (function_exists('getmxrr')) { 292 $resolved_hosts = array(); 293 $resolved = getmxrr($site_domain, $resolved_hosts); 294 if ($resolved && is_array($resolved_hosts)) { 295 $mx_hosts = $resolved_hosts; 296 } 297 } 298 299 if (empty($mx_hosts) && function_exists('dns_get_record')) { 300 $dns_records = dns_get_record($site_domain, DNS_MX); 301 if (is_array($dns_records)) { 302 foreach ($dns_records as $dns_record) { 303 if (!empty($dns_record['target']) && is_string($dns_record['target'])) { 304 $mx_hosts[] = $dns_record['target']; 305 } 306 } 307 } 308 } 309 310 $mx_match = meshsmtp_detect_provider_from_mx_hosts($mx_hosts); 311 312 wp_send_json_success( 313 array( 314 'provider' => $mx_match['provider'], 315 'siteDomain' => $site_domain, 316 'source' => !empty($mx_hosts) ? 'mx' : '', 317 'match' => $mx_match['match'], 318 ) 319 ); 320 } 321 322 /** 140 323 * Handle secure send-test-email action. 141 324 */ … … 156 339 } 157 340 158 update_option(MESHSMTP_LAST_TEST_OPTION, $test_to );341 update_option(MESHSMTP_LAST_TEST_OPTION, $test_to, false); 159 342 delete_transient(meshsmtp_mail_error_key()); 160 343 … … 167 350 $message = sprintf( 168 351 /* translators: %s: formatted date time */ 169 __("Success! If you received this message, your SMTP configuration is working.\n\nSent at: %s", 'mesh-smtp'), 352 __("Success! If you received this message, your SMTP configuration is working. 353 354 Sent at: %s", 'mesh-smtp'), 170 355 wp_date('Y-m-d H:i:s') 171 356 ); … … 178 363 ); 179 364 365 update_option(MESHSMTP_LAST_TEST_TIME_OPTION, time(), false); 366 180 367 if ($sent) { 368 update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'success', false); 181 369 meshsmtp_set_notice('success', esc_html__('Test email sent successfully.', 'mesh-smtp')); 182 370 } else { 371 update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'failed', false); 183 372 $error = get_transient(meshsmtp_mail_error_key()); 184 $text = esc_html__('Email could not be sent. Recheck host, port, credentials, and firewall settings.', 'mesh-smtp'); 185 if (!empty($error) && is_string($error)) { 186 $text .= ' ' . sprintf( 373 $text = !empty($error) && is_string($error) 374 ? meshsmtp_get_friendly_error_hint($error) . ' ' . sprintf( 187 375 /* translators: %s: wp_mail error message */ 188 376 esc_html__('Error: %s', 'mesh-smtp'), 189 377 sanitize_text_field($error) 190 ) ;191 }378 ) 379 : esc_html__('Email could not be sent. Recheck the host, port, credentials, and firewall settings.', 'mesh-smtp'); 192 380 meshsmtp_set_notice('error', $text); 193 381 } … … 265 453 */ 266 454 function meshsmtp_render_notice($type, $message) { 267 $class = ('success' === $type) ? 'notice notice-success is-dismissible' : 'notice notice-error is-dismissible'; 455 $class = ('success' === $type) 456 ? 'notice notice-success is-dismissible meshsmtp-notice' 457 : 'notice notice-error is-dismissible meshsmtp-notice'; 268 458 ?> 269 459 <div class="<?php echo esc_attr($class); ?>"> … … 281 471 } 282 472 283 $options = meshsmtp_get_settings(); 284 $last_test = get_option(MESHSMTP_LAST_TEST_OPTION, get_bloginfo('admin_email')); 285 $notice = meshsmtp_pop_notice(); 473 $options = meshsmtp_get_settings(); 474 $last_test = get_option(MESHSMTP_LAST_TEST_OPTION, get_bloginfo('admin_email')); 475 $last_test_status = get_option(MESHSMTP_LAST_TEST_STATUS_OPTION, ''); 476 $last_test_time = absint(get_option(MESHSMTP_LAST_TEST_TIME_OPTION, 0)); 477 $notice = meshsmtp_pop_notice(); 478 $username_domain = meshsmtp_get_email_domain((string) $options['username']); 479 $from_domain = meshsmtp_get_email_domain((string) $options['from_email']); 480 $status_text = ('1' === (string) $options['enabled']) ? esc_html__('Enabled', 'mesh-smtp') : esc_html__('Disabled', 'mesh-smtp'); 286 481 ?> 287 <div class="wrap meshsmtp-wrap"> 288 <div class="meshsmtp-hero"> 289 <div> 290 <h1><?php echo esc_html__('Mesh SMTP', 'mesh-smtp'); ?></h1> 291 <p><?php echo esc_html__('Secure SMTP controls with a cleaner, modern workflow.', 'mesh-smtp'); ?></p> 292 </div> 293 <div class="meshsmtp-badge"><?php echo esc_html('v' . MESHSMTP_PLUGIN_VERSION); ?></div> 482 <div class="wrap meshsmtp-wrap"> 483 <div class="meshsmtp-hero"> 484 <div> 485 <h1><?php echo esc_html__('Mesh SMTP', 'mesh-smtp'); ?></h1> 486 <p><?php echo esc_html__('Simple SMTP setup for reliable WordPress email delivery.', 'mesh-smtp'); ?></p> 294 487 </div> 488 <div class="meshsmtp-badge"><?php echo esc_html('v' . MESHSMTP_PLUGIN_VERSION); ?></div> 489 </div> 490 491 <div class="meshsmtp-statusbar"> 492 <div><strong><?php echo esc_html__('SMTP', 'mesh-smtp'); ?>:</strong> <?php echo esc_html($status_text); ?></div> 493 <div><strong><?php echo esc_html__('Host', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['host'] ?: '—'); ?></div> 494 <div><strong><?php echo esc_html__('Encryption', 'mesh-smtp'); ?>:</strong> <?php echo esc_html(strtoupper((string) $options['secure'])); ?></div> 495 <div><strong><?php echo esc_html__('Port', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['port']); ?></div> 496 <div><strong><?php echo esc_html__('From', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['from_email'] ?: '—'); ?></div> 497 </div> 295 498 296 499 <?php … … 301 504 meshsmtp_render_notice($notice['type'], $notice['message']); 302 505 } 506 if ($username_domain && $from_domain && $username_domain !== $from_domain) { 507 meshsmtp_render_notice('error', esc_html__('From Email domain does not match the SMTP login domain. This can affect deliverability.', 'mesh-smtp')); 508 } 303 509 ?> 304 510 305 <div class="meshsmtp-grid"> 306 <section class="meshsmtp-card"> 307 <h2><?php echo esc_html__('SMTP Configuration', 'mesh-smtp'); ?></h2> 308 <form method="post" action="options.php" autocomplete="off"> 309 <?php settings_fields('meshsmtp_settings_group'); ?> 310 311 <div class="meshsmtp-field meshsmtp-field-inline"> 312 <label for="meshsmtp-provider"><?php echo esc_html__('Provider Preset', 'mesh-smtp'); ?></label> 511 <div class="meshsmtp-grid"> 512 <section class="meshsmtp-card"> 513 <h2><?php echo esc_html__('SMTP Configuration', 'mesh-smtp'); ?></h2> 514 <form method="post" action="options.php" autocomplete="off"> 515 <?php settings_fields('meshsmtp_settings_group'); ?> 516 517 <div class="meshsmtp-field meshsmtp-field-inline meshsmtp-field-row"> 518 <label for="meshsmtp-provider"><?php echo esc_html__('Provider Preset', 'mesh-smtp'); ?></label> 519 <div class="meshsmtp-preset-row"> 313 520 <select id="meshsmtp-provider"> 314 <option value=""><?php echo esc_html__('Select a provider', 'mesh-smtp'); ?></option> 315 <option value="gmail">Gmail</option> 316 <option value="outlook">Outlook / Microsoft 365</option> 317 <option value="zoho">Zoho</option> 318 <option value="hostinger">Hostinger</option> 319 <option value="sendgrid">SendGrid</option> 320 <option value="mailgun">Mailgun</option> 321 <option value="custom">Custom</option> 322 </select> 323 </div> 324 325 <div class="meshsmtp-toggle"> 326 <label for="meshsmtp-enabled"><?php echo esc_html__('Enable SMTP override', 'mesh-smtp'); ?></label> 327 <input id="meshsmtp-enabled" type="checkbox" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="1" <?php checked('1', (string) $options['enabled']); ?> /> 521 <option value=""><?php echo esc_html__('Select a provider', 'mesh-smtp'); ?></option> 522 <option value="gmail">Gmail</option> 523 <option value="outlook">Outlook / Microsoft 365</option> 524 <option value="zoho">Zoho</option> 525 <option value="hostinger">Hostinger</option> 526 <option value="sendgrid">SendGrid</option> 527 <option value="mailgun">Mailgun</option> 528 <option value="custom">Custom</option> 529 </select> 530 <button type="button" class="button meshsmtp-password-toggle meshsmtp-tertiary" id="meshsmtp-auto-detect"><?php echo esc_html__('Auto Detect', 'mesh-smtp'); ?></button> 328 531 </div> 329 532 <p class="description"><?php echo esc_html__('Auto-detect uses your SMTP login, host, and mail routing hints.', 'mesh-smtp'); ?></p> 533 <p id="meshsmtp-provider-help" class="description"></p> 534 <p id="meshsmtp-provider-detect-result" class="description meshsmtp-provider-detect-result"></p> 535 </div> 536 537 <div class="meshsmtp-toggle"> 538 <label for="meshsmtp-enabled"><?php echo esc_html__('Enable SMTP override', 'mesh-smtp'); ?></label> 539 <input id="meshsmtp-enabled-hidden" type="hidden" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="0" /> 540 <input id="meshsmtp-enabled" type="checkbox" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="1" <?php checked('1', (string) $options['enabled']); ?> /> 541 </div> 542 543 <div class="meshsmtp-field meshsmtp-field-row"> 544 <label for="meshsmtp-host"><?php echo esc_html__('SMTP Host', 'mesh-smtp'); ?></label> 545 <input id="meshsmtp-host" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[host]" value="<?php echo esc_attr((string) $options['host']); ?>" placeholder="smtp.example.com" required /> 546 </div> 547 548 <div class="meshsmtp-dual"> 330 549 <div class="meshsmtp-field"> 331 <label for="meshsmtp-host"><?php echo esc_html__('SMTP Host', 'mesh-smtp'); ?></label> 332 <input id="meshsmtp-host" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[host]" value="<?php echo esc_attr((string) $options['host']); ?>" placeholder="smtp.example.com" required /> 550 <label for="meshsmtp-secure"><?php echo esc_html__('Encryption', 'mesh-smtp'); ?></label> 551 <select id="meshsmtp-secure" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[secure]"> 552 <option value="tls" <?php selected((string) $options['secure'], 'tls'); ?>>TLS</option> 553 <option value="ssl" <?php selected((string) $options['secure'], 'ssl'); ?>>SSL</option> 554 <option value="none" <?php selected((string) $options['secure'], 'none'); ?>>None</option> 555 </select> 333 556 </div> 334 335 <div class="meshsmtp-dual"> 336 <div class="meshsmtp-field"> 337 <label for="meshsmtp-secure"><?php echo esc_html__('Encryption', 'mesh-smtp'); ?></label> 338 <select id="meshsmtp-secure" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[secure]"> 339 <option value="tls" <?php selected((string) $options['secure'], 'tls'); ?>>TLS</option> 340 <option value="ssl" <?php selected((string) $options['secure'], 'ssl'); ?>>SSL</option> 341 <option value="none" <?php selected((string) $options['secure'], 'none'); ?>>None</option> 342 </select> 343 </div> 344 <div class="meshsmtp-field"> 345 <label for="meshsmtp-port"><?php echo esc_html__('Port', 'mesh-smtp'); ?></label> 346 <input id="meshsmtp-port" type="number" min="1" max="65535" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[port]" value="<?php echo esc_attr((string) $options['port']); ?>" required /> 347 </div> 557 <div class="meshsmtp-field"> 558 <label for="meshsmtp-port"><?php echo esc_html__('Port', 'mesh-smtp'); ?></label> 559 <input id="meshsmtp-port" type="number" min="1" max="65535" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[port]" value="<?php echo esc_attr((string) $options['port']); ?>" required /> 348 560 </div> 349 561 </div> 562 563 <div class="meshsmtp-field meshsmtp-field-row"> 564 <label for="meshsmtp-username"><?php echo esc_html__('Username / SMTP Login', 'mesh-smtp'); ?></label> 565 <input id="meshsmtp-username" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[username]" value="<?php echo esc_attr((string) $options['username']); ?>" autocomplete="off" required /> 566 </div> 567 568 <div class="meshsmtp-field meshsmtp-field-row"> 569 <label for="meshsmtp-password"><?php echo esc_html__('Password / App Password', 'mesh-smtp'); ?></label> 570 <div class="meshsmtp-password-wrap"> 571 <input id="meshsmtp-password" type="password" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[password]" value="" placeholder="<?php echo esc_attr__('Leave blank to keep current password', 'mesh-smtp'); ?>" autocomplete="new-password" /> 572 <button type="button" class="button meshsmtp-password-toggle" id="meshsmtp-password-toggle"><?php echo esc_html__('Show', 'mesh-smtp'); ?></button> 573 </div> 574 </div> 575 576 <div class="meshsmtp-dual"> 350 577 <div class="meshsmtp-field"> 351 <label for="meshsmtp- username"><?php echo esc_html__('Username / SMTP Login', 'mesh-smtp'); ?></label>352 <input id="meshsmtp- username" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[username]" value="<?php echo esc_attr((string) $options['username']); ?>" autocomplete="off" required />578 <label for="meshsmtp-from-email"><?php echo esc_html__('From Email', 'mesh-smtp'); ?></label> 579 <input id="meshsmtp-from-email" type="email" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_email]" value="<?php echo esc_attr((string) $options['from_email']); ?>" required /> 353 580 </div> 354 355 581 <div class="meshsmtp-field"> 356 <label for="meshsmtp- password"><?php echo esc_html__('Password / App Password', 'mesh-smtp'); ?></label>357 <input id="meshsmtp- password" type="password" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[password]" value="" placeholder="<?php echo esc_attr__('Leave blank to keep current password', 'mesh-smtp'); ?>" autocomplete="new-password"/>582 <label for="meshsmtp-from-name"><?php echo esc_html__('From Name', 'mesh-smtp'); ?></label> 583 <input id="meshsmtp-from-name" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_name]" value="<?php echo esc_attr((string) $options['from_name']); ?>" required /> 358 584 </div> 359 360 <div class="meshsmtp-dual"> 361 <div class="meshsmtp-field"> 362 <label for="meshsmtp-from-email"><?php echo esc_html__('From Email', 'mesh-smtp'); ?></label> 363 <input id="meshsmtp-from-email" type="email" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_email]" value="<?php echo esc_attr((string) $options['from_email']); ?>" required /> 364 </div> 365 <div class="meshsmtp-field"> 366 <label for="meshsmtp-from-name"><?php echo esc_html__('From Name', 'mesh-smtp'); ?></label> 367 <input id="meshsmtp-from-name" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_name]" value="<?php echo esc_attr((string) $options['from_name']); ?>" required /> 368 </div> 369 </div> 370 371 <p class="submit"> 372 <button type="submit" class="button button-primary meshsmtp-primary"><?php echo esc_html__('Save SMTP Settings', 'mesh-smtp'); ?></button> 373 </p> 374 </form> 375 </section> 376 377 <section class="meshsmtp-card meshsmtp-test-card"> 378 <h2><?php echo esc_html__('Connection Test', 'mesh-smtp'); ?></h2> 379 <p><?php echo esc_html__('Send a live test to verify your current SMTP setup.', 'mesh-smtp'); ?></p> 380 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 381 <?php wp_nonce_field('meshsmtp_send_test_email_action'); ?> 382 <input type="hidden" name="action" value="meshsmtp_send_test_email" /> 383 <div class="meshsmtp-field"> 384 <label for="meshsmtp-test-email"><?php echo esc_html__('Recipient Email', 'mesh-smtp'); ?></label> 385 <input id="meshsmtp-test-email" type="email" name="meshsmtp_test_email" value="<?php echo esc_attr((string) $last_test); ?>" required /> 386 </div> 387 <button type="submit" class="button meshsmtp-secondary"><?php echo esc_html__('Send Test Email', 'mesh-smtp'); ?></button> 388 </form> 585 </div> 586 587 <p class="submit"> 588 <button type="submit" class="button button-primary meshsmtp-primary"><?php echo esc_html__('Save SMTP Settings', 'mesh-smtp'); ?></button> 589 </p> 590 </form> 591 </section> 592 593 <section class="meshsmtp-card meshsmtp-test-card"> 594 <h2><?php echo esc_html__('Connection Test', 'mesh-smtp'); ?></h2> 595 <p><?php echo esc_html__('Send a live test to verify your current SMTP setup.', 'mesh-smtp'); ?></p> 596 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 597 <?php wp_nonce_field('meshsmtp_send_test_email_action'); ?> 598 <input type="hidden" name="action" value="meshsmtp_send_test_email" /> 599 <div class="meshsmtp-field meshsmtp-field-row"> 600 <label for="meshsmtp-test-email"><?php echo esc_html__('Recipient Email', 'mesh-smtp'); ?></label> 601 <input id="meshsmtp-test-email" type="email" name="meshsmtp_test_email" value="<?php echo esc_attr((string) $last_test); ?>" required /> 602 </div> 603 <button type="submit" class="button meshsmtp-secondary"><?php echo esc_html__('Send Test Email', 'mesh-smtp'); ?></button> 604 </form> 605 606 <hr /> 607 <h3><?php echo esc_html__('Last Test', 'mesh-smtp'); ?></h3> 608 <ul class="meshsmtp-meta-list"> 609 <li><strong><?php echo esc_html__('Recipient', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $last_test ?: '—'); ?></li> 610 <li><strong><?php echo esc_html__('Result', 'mesh-smtp'); ?>:</strong> <?php echo esc_html($last_test_status ? ucfirst((string) $last_test_status) : '—'); ?></li> 611 <li><strong><?php echo esc_html__('Time', 'mesh-smtp'); ?>:</strong> <?php echo $last_test_time ? esc_html(wp_date('Y-m-d H:i:s', $last_test_time)) : '—'; ?></li> 612 </ul> 389 613 390 614 <hr /> -
mesh-smtp/tags/1.2.4/assets/admin.css
r3481868 r3482419 16 16 17 17 .meshsmtp-hero { 18 align-items: center;18 align-items: flex-start; 19 19 background: linear-gradient(120deg, #0f172a, #0b4f6c 52%, #0d9488); 20 20 border-radius: 14px; … … 22 22 display: flex; 23 23 justify-content: space-between; 24 margin-bottom: 20px;25 padding: 1 8px 22px;24 margin-bottom: 16px; 25 padding: 14px 18px; 26 26 } 27 27 28 28 .meshsmtp-hero h1 { 29 29 color: #ffffff; 30 font-size: 2 4px;30 font-size: 22px; 31 31 line-height: 1.2; 32 32 margin: 0; … … 35 35 .meshsmtp-hero p { 36 36 color: #cbd5e1; 37 margin: 8px 0 0; 37 font-size: 13px; 38 line-height: 1.5; 39 margin: 4px 0 0; 38 40 } 39 41 … … 42 44 border: 1px solid rgba(255, 255, 255, 0.35); 43 45 border-radius: 999px; 44 font-weight: 600; 45 padding: 6px 12px; 46 font-size: 12px; 47 font-weight: 600; 48 padding: 5px 10px; 49 } 50 51 .meshsmtp-statusbar { 52 display: grid; 53 gap: 10px; 54 grid-template-columns: repeat(5, minmax(0, 1fr)); 55 background: rgba(255, 255, 255, 0.75); 56 border: 1px solid #dbe4ee; 57 border-radius: 12px; 58 margin-bottom: 16px; 59 padding: 12px 14px; 60 font-size: 13px; 61 color: #334155; 46 62 } 47 63 … … 71 87 72 88 .meshsmtp-card p, 73 .meshsmtp-card li { 89 .meshsmtp-card li, 90 .meshsmtp-field .description { 74 91 color: var(--meshsmtp-muted); 75 92 } 76 93 77 94 .meshsmtp-field { 78 margin-bottom: 1 4px;95 margin-bottom: 12px; 79 96 } 80 97 … … 103 120 } 104 121 122 .meshsmtp-field input:disabled, 123 .meshsmtp-field select:disabled { 124 background: #f8fafc; 125 color: #94a3b8; 126 cursor: not-allowed; 127 } 128 105 129 .meshsmtp-field-inline { 106 130 margin-bottom: 16px; 107 131 } 108 132 133 .meshsmtp-field-row .description { 134 margin-bottom: 0; 135 } 136 137 .meshsmtp-preset-row { 138 align-items: center; 139 display: flex; 140 gap: 10px; 141 } 142 143 .meshsmtp-preset-row select { 144 flex: 1 1 auto; 145 } 146 109 147 .meshsmtp-dual { 110 column-gap: 14px;111 148 display: grid; 112 grid-template-columns: repeat(2, minmax(0, 1fr)); 149 gap: 12px; 150 grid-template-columns: 1fr; 113 151 } 114 152 … … 129 167 } 130 168 169 .meshsmtp-password-wrap { 170 display: flex; 171 gap: 10px; 172 } 173 174 .meshsmtp-password-wrap input { 175 flex: 1 1 auto; 176 } 177 178 .meshsmtp-password-toggle { 179 border-radius: 10px; 180 min-height: 40px; 181 padding: 0 14px; 182 white-space: nowrap; 183 } 184 185 .meshsmtp-tertiary { 186 min-width: 116px; 187 } 188 189 .meshsmtp-provider-detect-result { 190 margin-top: 8px; 191 } 192 193 .meshsmtp-provider-detect-result.is-success { 194 color: #065f46; 195 font-weight: 600; 196 } 197 198 .meshsmtp-provider-detect-result.is-warning { 199 color: #92400e; 200 font-weight: 600; 201 } 202 131 203 .meshsmtp-primary, 132 204 .meshsmtp-secondary { … … 146 218 .meshsmtp-primary:focus { 147 219 background: linear-gradient(120deg, #0284c7, #0369a1); 220 border-color: #0369a1; 148 221 color: #ffffff; 149 222 } … … 174 247 } 175 248 249 .meshsmtp-meta-list { 250 list-style: none !important; 251 padding-left: 0 !important; 252 } 253 254 .meshsmtp-meta-list li { 255 margin-bottom: 8px; 256 } 257 258 .meshsmtp-wrap .notice p { 259 color: #0f172a !important; 260 font-weight: 600; 261 } 262 263 @media (min-width: 900px) { 264 .meshsmtp-field-row { 265 align-items: center; 266 column-gap: 14px; 267 display: grid; 268 grid-template-columns: 235px minmax(0, 1fr); 269 } 270 271 .meshsmtp-field-row label { 272 margin-bottom: 0; 273 } 274 275 .meshsmtp-field-row > input, 276 .meshsmtp-field-row > select, 277 .meshsmtp-field-row > .meshsmtp-password-wrap, 278 .meshsmtp-field-row > .meshsmtp-preset-row, 279 .meshsmtp-field-row > .description { 280 grid-column: 2; 281 } 282 283 .meshsmtp-field-row > .description { 284 margin-top: 4px; 285 } 286 287 .meshsmtp-dual { 288 align-items: start; 289 column-gap: 14px; 290 grid-template-columns: 235px minmax(0, 1fr) minmax(0, 1fr); 291 } 292 293 .meshsmtp-dual::before { 294 content: ''; 295 } 296 297 .meshsmtp-dual > .meshsmtp-field { 298 margin-bottom: 0; 299 } 300 301 .meshsmtp-test-card .meshsmtp-field-row { 302 grid-template-columns: 170px minmax(0, 1fr); 303 } 304 } 305 176 306 @media (max-width: 1100px) { 177 .meshsmtp-grid { 307 .meshsmtp-grid, 308 .meshsmtp-statusbar { 178 309 grid-template-columns: 1fr; 179 310 } … … 187 318 188 319 .meshsmtp-hero { 189 align-items: flex-start;190 320 flex-direction: column; 191 321 gap: 10px; 192 322 } 193 323 194 .meshsmtp-dual { 324 .meshsmtp-preset-row { 325 align-items: stretch; 326 flex-direction: column; 327 } 328 329 .meshsmtp-password-wrap { 195 330 grid-template-columns: 1fr; 196 } 197 } 198 199 200 /* Make WP admin notices readable on Mesh SMTP page */ 201 .meshsmtp-wrap .notice p { 202 color: #0f172a !important; 203 font-weight: 600; 204 } 331 display: grid; 332 } 333 } -
mesh-smtp/tags/1.2.4/assets/admin.js
r3481868 r3482419 6 6 const secureField = document.getElementById('meshsmtp-secure'); 7 7 const portField = document.getElementById('meshsmtp-port'); 8 9 if (!providerField || !hostField || !secureField || !portField) { 10 return; 11 } 8 const enabledField = document.getElementById('meshsmtp-enabled'); 9 const form = document.querySelector('.meshsmtp-card form'); 10 const providerHelp = document.getElementById('meshsmtp-provider-help'); 11 const providerDetectResult = document.getElementById('meshsmtp-provider-detect-result'); 12 const autoDetectButton = document.getElementById('meshsmtp-auto-detect'); 13 const usernameField = document.getElementById('meshsmtp-username'); 14 const fromEmailField = document.getElementById('meshsmtp-from-email'); 15 const passwordField = document.getElementById('meshsmtp-password'); 16 const passwordToggle = document.getElementById('meshsmtp-password-toggle'); 17 const adminConfig = window.meshsmtpAdmin || {}; 18 let allowFromEmailSync = false; 12 19 13 20 const presets = { … … 21 28 }; 22 29 23 providerField.addEventListener('change', function () { 24 const selected = presets[this.value]; 30 const presetHelp = { 31 gmail: 'Use a Google App Password instead of your normal Gmail password.', 32 outlook: 'SMTP AUTH may need to be enabled for the mailbox in Microsoft 365.', 33 zoho: 'Use the full Zoho mailbox address as the SMTP username.', 34 hostinger: 'Use the full mailbox email address as the SMTP login.', 35 sendgrid: 'Use "apikey" as the username and your API key as the password when applicable.', 36 mailgun: 'Mailgun SMTP hosts can vary by region or account configuration.', 37 custom: 'Enter the exact SMTP details provided by your hosting or email service.' 38 }; 39 40 const providerLabels = { 41 gmail: 'Gmail', 42 outlook: 'Outlook / Microsoft 365', 43 zoho: 'Zoho', 44 hostinger: 'Hostinger', 45 sendgrid: 'SendGrid', 46 mailgun: 'Mailgun', 47 custom: 'Custom' 48 }; 49 50 function setProviderDetectResult(message, type) { 51 if (!providerDetectResult) { 52 return; 53 } 54 55 providerDetectResult.textContent = message || ''; 56 providerDetectResult.className = 'description meshsmtp-provider-detect-result'; 57 58 if (message && type) { 59 providerDetectResult.classList.add('is-' + type); 60 } 61 } 62 63 function applyProviderHelp() { 64 if (!providerField || !providerHelp) { 65 return; 66 } 67 68 providerHelp.textContent = presetHelp[providerField.value] || ''; 69 } 70 71 function applyProviderPreset() { 72 if (!providerField || !hostField || !secureField || !portField) { 73 return; 74 } 75 76 const selected = presets[providerField.value]; 25 77 if (!selected) { 78 applyProviderHelp(); 26 79 return; 27 80 } … … 30 83 secureField.value = selected.secure; 31 84 portField.value = selected.port; 32 }); 33 34 secureField.addEventListener('change', function () { 35 if (this.value === 'ssl') { 85 applyProviderHelp(); 86 } 87 88 function applyDetectedProvider(provider, message, type) { 89 if (!providerField || !provider) { 90 return; 91 } 92 93 providerField.value = provider; 94 applyProviderPreset(); 95 setProviderDetectResult(message, type); 96 } 97 98 function syncPortToEncryption() { 99 if (!secureField || !portField) { 100 return; 101 } 102 103 if (secureField.value === 'ssl') { 36 104 portField.value = '465'; 37 105 return; 38 106 } 39 107 40 if ( this.value === 'tls') {108 if (secureField.value === 'tls') { 41 109 portField.value = '587'; 42 110 return; … … 44 112 45 113 portField.value = '25'; 114 } 115 116 function toggleFields() { 117 if (!enabledField || !form) { 118 return; 119 } 120 121 form.querySelectorAll('input, select, button').forEach(function (field) { 122 if ( 123 field.id === 'meshsmtp-enabled' || 124 field.type === 'hidden' || 125 field.type === 'submit' || 126 field.tagName === 'BUTTON' 127 ) { 128 return; 129 } 130 131 field.disabled = !enabledField.checked; 132 }); 133 } 134 135 function bindPasswordToggle() { 136 if (!passwordField || !passwordToggle) { 137 return; 138 } 139 140 passwordToggle.addEventListener('click', function () { 141 const isPassword = passwordField.getAttribute('type') === 'password'; 142 passwordField.setAttribute('type', isPassword ? 'text' : 'password'); 143 passwordToggle.textContent = isPassword ? 'Hide' : 'Show'; 144 }); 145 } 146 147 function normalizeValue(value) { 148 return String(value || '').trim().toLowerCase(); 149 } 150 151 function looksLikeEmail(value) { 152 const text = String(value || '').trim(); 153 154 return text.indexOf('@') > 0 && text.indexOf(' ') === -1; 155 } 156 157 function initializeFromEmailSync() { 158 if (!usernameField || !fromEmailField) { 159 return; 160 } 161 162 const username = normalizeValue(usernameField.value); 163 const fromEmail = normalizeValue(fromEmailField.value); 164 165 allowFromEmailSync = !username || !fromEmail || username === fromEmail; 166 167 if (allowFromEmailSync && looksLikeEmail(usernameField.value) && !fromEmailField.value.trim()) { 168 fromEmailField.value = String(usernameField.value || '').trim(); 169 } 170 } 171 172 function syncFromEmailFromUsername() { 173 if (!allowFromEmailSync || !usernameField || !fromEmailField) { 174 return; 175 } 176 177 const usernameValue = String(usernameField.value || '').trim(); 178 if (!looksLikeEmail(usernameValue)) { 179 return; 180 } 181 182 fromEmailField.value = usernameValue; 183 } 184 185 function trackFromEmailOverride() { 186 if (!usernameField || !fromEmailField) { 187 return; 188 } 189 190 const username = normalizeValue(usernameField.value); 191 const fromEmail = normalizeValue(fromEmailField.value); 192 193 allowFromEmailSync = !fromEmail || !username || fromEmail === username; 194 } 195 196 function extractEmailDomain(value) { 197 const email = normalizeValue(value); 198 const atIndex = email.lastIndexOf('@'); 199 200 if (atIndex === -1) { 201 return ''; 202 } 203 204 return email.slice(atIndex + 1); 205 } 206 207 function detectProviderByHost(host) { 208 if (!host) { 209 return null; 210 } 211 212 if (host.indexOf('smtp.gmail.com') !== -1) { 213 return { provider: 'gmail', source: 'smtp_host', match: host }; 214 } 215 216 if ( 217 host.indexOf('office365.com') !== -1 || 218 host.indexOf('outlook.com') !== -1 || 219 host.indexOf('hotmail.com') !== -1 || 220 host.indexOf('live.com') !== -1 221 ) { 222 return { provider: 'outlook', source: 'smtp_host', match: host }; 223 } 224 225 if (host.indexOf('zoho') !== -1) { 226 return { provider: 'zoho', source: 'smtp_host', match: host }; 227 } 228 229 if (host.indexOf('hostinger') !== -1) { 230 return { provider: 'hostinger', source: 'smtp_host', match: host }; 231 } 232 233 if (host.indexOf('sendgrid') !== -1) { 234 return { provider: 'sendgrid', source: 'smtp_host', match: host }; 235 } 236 237 if (host.indexOf('mailgun') !== -1) { 238 return { provider: 'mailgun', source: 'smtp_host', match: host }; 239 } 240 241 return null; 242 } 243 244 function detectProviderByDomain(domain) { 245 if (!domain) { 246 return null; 247 } 248 249 if (domain === 'gmail.com' || domain === 'googlemail.com') { 250 return { provider: 'gmail', source: 'smtp_login_domain', match: domain }; 251 } 252 253 if ( 254 domain === 'outlook.com' || 255 domain === 'hotmail.com' || 256 domain === 'live.com' || 257 domain === 'msn.com' || 258 domain.indexOf('.onmicrosoft.com') !== -1 259 ) { 260 return { provider: 'outlook', source: 'smtp_login_domain', match: domain }; 261 } 262 263 if ( 264 domain === 'zoho.com' || 265 domain.indexOf('zoho.') === 0 || 266 domain.indexOf('.zoho.') !== -1 267 ) { 268 return { provider: 'zoho', source: 'smtp_login_domain', match: domain }; 269 } 270 271 if (domain === 'hostinger.com' || domain.indexOf('.hostinger.com') !== -1) { 272 return { provider: 'hostinger', source: 'smtp_login_domain', match: domain }; 273 } 274 275 if (domain === 'sendgrid.net' || domain.indexOf('.sendgrid.net') !== -1) { 276 return { provider: 'sendgrid', source: 'smtp_login_domain', match: domain }; 277 } 278 279 if (domain === 'mailgun.org' || domain.indexOf('.mailgun.org') !== -1) { 280 return { provider: 'mailgun', source: 'smtp_login_domain', match: domain }; 281 } 282 283 return null; 284 } 285 286 function detectProvider() { 287 const detectedByHost = detectProviderByHost(normalizeValue(hostField && hostField.value)); 288 289 if (detectedByHost) { 290 return detectedByHost; 291 } 292 293 const candidateDomains = [extractEmailDomain(usernameField && usernameField.value)]; 294 295 for (let index = 0; index < candidateDomains.length; index += 1) { 296 const detectedByDomain = detectProviderByDomain(candidateDomains[index]); 297 298 if (detectedByDomain) { 299 return detectedByDomain; 300 } 301 } 302 303 return null; 304 } 305 306 function buildExactDetectMessage(result) { 307 if (!result || !result.provider) { 308 return ''; 309 } 310 311 if (result.source === 'smtp_host') { 312 return 'Detected ' + providerLabels[result.provider] + ' from SMTP host ' + result.match + '. Review the values, then save settings.'; 313 } 314 315 if (result.source === 'smtp_login_domain') { 316 return 'Detected ' + providerLabels[result.provider] + ' from SMTP login domain ' + result.match + '. Review the values, then save settings.'; 317 } 318 319 return 'Detected ' + providerLabels[result.provider] + ' automatically. Review the values, then save settings.'; 320 } 321 322 function buildHintMessage(hint) { 323 if (!hint || !hint.provider) { 324 return ''; 325 } 326 327 if (hint.source === 'mx' && hint.match) { 328 return 'Suggested ' + providerLabels[hint.provider] + ' from MX record ' + hint.match + ' for ' + hint.siteDomain + '. Review the values before saving.'; 329 } 330 331 if (hint.source === 'mx') { 332 return 'Suggested ' + providerLabels[hint.provider] + ' from the mail routing on ' + hint.siteDomain + '. Review the values before saving.'; 333 } 334 335 return 'Suggested ' + providerLabels[hint.provider] + ' automatically. Review the values before saving.'; 336 } 337 338 function requestProviderHintFromSiteDomain() { 339 if (!adminConfig.ajaxUrl || !adminConfig.detectNonce || typeof window.fetch !== 'function') { 340 return Promise.resolve(null); 341 } 342 343 const requestBody = new window.URLSearchParams(); 344 requestBody.set('action', 'meshsmtp_detect_provider_hint'); 345 requestBody.set('nonce', adminConfig.detectNonce); 346 347 return window.fetch(adminConfig.ajaxUrl, { 348 method: 'POST', 349 credentials: 'same-origin', 350 headers: { 351 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 352 }, 353 body: requestBody.toString() 354 }) 355 .then(function (response) { 356 if (!response.ok) { 357 return null; 358 } 359 360 return response.json(); 361 }) 362 .then(function (payload) { 363 if (!payload || !payload.success || !payload.data) { 364 return null; 365 } 366 367 return payload.data; 368 }) 369 .catch(function () { 370 return null; 371 }); 372 } 373 374 function finishWithoutMatch() { 375 if (!providerField.value) { 376 providerField.value = 'custom'; 377 applyProviderHelp(); 378 } 379 380 setProviderDetectResult( 381 'No exact provider match was found. Keep using Custom or choose a provider manually.', 382 'warning' 383 ); 384 } 385 386 function autoDetectProvider() { 387 if (!providerField) { 388 return; 389 } 390 391 const detectedProvider = detectProvider(); 392 393 if (detectedProvider && detectedProvider.provider) { 394 applyDetectedProvider( 395 detectedProvider.provider, 396 buildExactDetectMessage(detectedProvider), 397 'success' 398 ); 399 return; 400 } 401 402 if (autoDetectButton) { 403 autoDetectButton.disabled = true; 404 autoDetectButton.textContent = 'Checking...'; 405 } 406 407 setProviderDetectResult('No exact match yet. Checking your site domain mail routing for a hint...', 'warning'); 408 409 requestProviderHintFromSiteDomain().then(function (hint) { 410 if (autoDetectButton) { 411 autoDetectButton.disabled = false; 412 autoDetectButton.textContent = 'Auto Detect'; 413 } 414 415 if (hint && hint.provider) { 416 applyDetectedProvider( 417 hint.provider, 418 buildHintMessage(hint), 419 'warning' 420 ); 421 return; 422 } 423 424 finishWithoutMatch(); 425 }); 426 } 427 428 document.addEventListener('DOMContentLoaded', function () { 429 applyProviderHelp(); 430 toggleFields(); 431 bindPasswordToggle(); 432 initializeFromEmailSync(); 433 434 if (providerField) { 435 providerField.addEventListener('change', function () { 436 setProviderDetectResult(''); 437 applyProviderPreset(); 438 }); 439 } 440 441 if (secureField) { 442 secureField.addEventListener('change', syncPortToEncryption); 443 } 444 445 if (enabledField) { 446 enabledField.addEventListener('change', toggleFields); 447 } 448 449 if (usernameField) { 450 usernameField.addEventListener('input', syncFromEmailFromUsername); 451 usernameField.addEventListener('change', syncFromEmailFromUsername); 452 } 453 454 if (fromEmailField) { 455 fromEmailField.addEventListener('input', trackFromEmailOverride); 456 fromEmailField.addEventListener('change', trackFromEmailOverride); 457 } 458 459 if (autoDetectButton) { 460 autoDetectButton.addEventListener('click', autoDetectProvider); 461 } 46 462 }); 47 463 })(); -
mesh-smtp/tags/1.2.4/mesh-smtp.php
r3481868 r3482419 3 3 * Plugin Name: Mesh SMTP 4 4 * Plugin URI: https://meshcreation.com/plugins/mesh-smtp 5 * * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI.and a modern admin UI.6 * Version: 1.2. 25 * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI. 6 * Version: 1.2.4 7 7 * Author: Mesh Creation 8 8 * Author URI: https://meshcreation.com … … 19 19 } 20 20 21 define('MESHSMTP_PLUGIN_VERSION', '1.2. 2');21 define('MESHSMTP_PLUGIN_VERSION', '1.2.4'); 22 22 define('MESHSMTP_PLUGIN_FILE', __FILE__); 23 23 define('MESHSMTP_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 26 26 define('MESHSMTP_LEGACY_OPTION_KEY', 'msm_settings'); 27 27 define('MESHSMTP_LAST_TEST_OPTION', 'meshsmtp_last_test_email'); 28 define('MESHSMTP_LAST_TEST_STATUS_OPTION', 'meshsmtp_last_test_status'); 29 define('MESHSMTP_LAST_TEST_TIME_OPTION', 'meshsmtp_last_test_time'); 28 30 define('MESHSMTP_LEGACY_LAST_TEST_OPTION', 'msm_last_test_email'); 29 31 define('MESHSMTP_MIGRATION_FLAG_OPTION', 'meshsmtp_legacy_migration_complete'); … … 147 149 148 150 $phpmailer->isSMTP(); 149 $phpmailer->Host = (string) $settings['host'];150 $phpmailer->SMTPAuth = true;151 $phpmailer->Port = (int) $settings['port'];152 $phpmailer->Username = (string) $settings['username'];153 $phpmailer->Password = (string) $settings['password'];154 $phpmailer->SMTPAutoTLS = true;155 $phpmailer->Timeout = 20;156 $phpmailer->SMTPDebug = 0;151 $phpmailer->Host = (string) $settings['host']; 152 $phpmailer->SMTPAuth = true; 153 $phpmailer->Port = (int) $settings['port']; 154 $phpmailer->Username = (string) $settings['username']; 155 $phpmailer->Password = (string) $settings['password']; 156 $phpmailer->SMTPAutoTLS = ('tls' === $settings['secure']); 157 $phpmailer->Timeout = 20; 158 $phpmailer->SMTPDebug = 0; 157 159 158 160 if (is_email($settings['from_email'])) { -
mesh-smtp/tags/1.2.4/readme.txt
r3481868 r3482419 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.4 8 Stable tag: 1.2. 28 Stable tag: 1.2.4 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 21 21 22 22 * Simple SMTP configuration for host, port, encryption, username, and password 23 * Provider presets for faster setup23 * Provider presets with auto-detect help for known email domains, SMTP hosts, and site mail-routing hints 24 24 * Test email tool with clear success and error feedback 25 * Last test status and timestamp summary 26 * Better setup hints for popular providers 25 27 * Clean and modern admin UI 26 28 * Capability checks, nonce verification, and sanitized settings handling 27 29 * Passwords are not shown back in the UI after saving 30 * Saved SMTP details stay intact when you temporarily disable the SMTP override 28 31 * Works with WordPress emails sent through `wp_mail()` 29 32 … … 53 56 2. Activate the plugin through the **Plugins** screen in WordPress. 54 57 3. Open **Mesh SMTP** from the WordPress admin menu. 55 4. Enter your SMTP details manually or choose a supported provider preset.58 4. Enter your SMTP details manually, use Auto Detect, or choose a supported provider preset. 56 59 5. Save the settings. 57 60 6. Use the **Connection Test** section to send a test email. … … 87 90 == Changelog == 88 91 92 = 1.2.4 = 93 * Added an Auto Detect helper for known provider domains and SMTP hosts 94 * Added a soft site-domain mail-routing hint to better suggest Hostinger or other supported providers 95 * Added clearer auto-detect feedback so admins can see whether the match came from SMTP host, login domain, or MX routing 96 * Preserved saved SMTP settings when the SMTP override is turned off 97 * Stopped forcing opportunistic TLS when encryption is set to None 98 * Removed admin-page notice suppression for better WordPress.org review compatibility 99 100 = 1.2.3 = 101 * Fixed plugin header description formatting 102 * Hid unrelated admin notices on the Mesh SMTP settings screen 103 * Added a compact connection status bar 104 * Added last test result and timestamp details 105 * Added clearer failure hints and provider help text 106 * Added password visibility toggle and disabled fields when SMTP override is off 107 89 108 = 1.2.2 = 90 109 * Fixed settings page behavior for the updated WordPress.org review build … … 111 130 == Upgrade Notice == 112 131 132 = 1.2.4 = 133 Recommended update for safer SMTP toggling, provider auto-detect, and cleaner WordPress.org submission behavior. 134 135 = 1.2.3 = 136 Recommended update for a cleaner settings screen and more helpful SMTP diagnostics. 137 113 138 = 1.2.2 = 114 139 Recommended update for improved settings page reliability and compatibility metadata. -
mesh-smtp/tags/1.2.4/uninstall.php
r3481868 r3482419 14 14 'msm_settings', 15 15 'meshsmtp_last_test_email', 16 'meshsmtp_last_test_status', 17 'meshsmtp_last_test_time', 16 18 'msm_last_test_email', 17 19 'meshsmtp_legacy_migration_complete', -
mesh-smtp/trunk/admin/settings-page.php
r3481868 r3482419 10 10 add_action('admin_post_meshsmtp_send_test_email', 'meshsmtp_handle_test_email'); 11 11 add_action('wp_mail_failed', 'meshsmtp_capture_mail_error'); 12 add_action('wp_ajax_meshsmtp_detect_provider_hint', 'meshsmtp_ajax_detect_provider_hint'); 12 13 13 14 /** … … 65 66 true 66 67 ); 68 69 wp_localize_script( 70 'meshsmtp-admin-script', 71 'meshsmtpAdmin', 72 array( 73 'ajaxUrl' => admin_url('admin-ajax.php'), 74 'detectNonce' => wp_create_nonce('meshsmtp_detect_provider'), 75 ) 76 ); 67 77 } 68 78 … … 81 91 } 82 92 83 $host = '';93 $host = (string) $existing['host']; 84 94 if (isset($input['host'])) { 85 95 $host = sanitize_text_field(wp_unslash((string) $input['host'])); … … 87 97 } 88 98 89 $port = isset($input['port']) ? absint($input['port']) : (int) $ defaults['port'];99 $port = isset($input['port']) ? absint($input['port']) : (int) $existing['port']; 90 100 if ($port < 1 || $port > 65535) { 91 101 $port = (int) $defaults['port']; 92 102 } 93 103 94 $username = '';104 $username = (string) $existing['username']; 95 105 if (isset($input['username'])) { 96 106 $username = sanitize_text_field(wp_unslash((string) $input['username'])); … … 104 114 } 105 115 106 $from_email = '';116 $from_email = (string) $existing['from_email']; 107 117 if (isset($input['from_email'])) { 108 118 $from_email = sanitize_email(wp_unslash((string) $input['from_email'])); … … 112 122 } 113 123 114 $from_name = '';124 $from_name = (string) $existing['from_name']; 115 125 if (isset($input['from_name'])) { 116 126 $from_name = sanitize_text_field(wp_unslash((string) $input['from_name'])); … … 120 130 } 121 131 122 $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $ defaults['secure'];132 $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $existing['secure']; 123 133 if (!in_array($secure, array('none', 'ssl', 'tls'), true)) { 124 134 $secure = (string) $defaults['secure']; … … 138 148 139 149 /** 150 * Map raw mail errors into clearer hints. 151 * 152 * @param string $error_message Raw mail error message. 153 * @return string 154 */ 155 function meshsmtp_get_friendly_error_hint($error_message) { 156 $message = strtolower((string) $error_message); 157 158 if (false !== strpos($message, 'authenticate') || false !== strpos($message, 'username') || false !== strpos($message, 'password')) { 159 return __('Authentication failed. Recheck the SMTP username and password or app password.', 'mesh-smtp'); 160 } 161 162 if (false !== strpos($message, 'connect') || false !== strpos($message, 'timed out') || false !== strpos($message, 'connection refused')) { 163 return __('Could not connect to the SMTP server. Recheck the host, port, firewall, or hosting restrictions.', 'mesh-smtp'); 164 } 165 166 if (false !== strpos($message, 'tls') || false !== strpos($message, 'ssl') || false !== strpos($message, 'certificate')) { 167 return __('Encryption handshake failed. Recheck the selected TLS or SSL option and matching port.', 'mesh-smtp'); 168 } 169 170 if (false !== strpos($message, 'from') || false !== strpos($message, 'sender')) { 171 return __('The sender address may be rejected. Recheck the From Email value and mailbox permissions.', 'mesh-smtp'); 172 } 173 174 return __('Email could not be sent. Recheck the host, port, credentials, and firewall settings.', 'mesh-smtp'); 175 } 176 177 /** 178 * Extract email domain safely. 179 * 180 * @param string $email Email address. 181 * @return string 182 */ 183 function meshsmtp_get_email_domain($email) { 184 $email = sanitize_email((string) $email); 185 if (!$email || false === strpos($email, '@')) { 186 return ''; 187 } 188 189 $parts = explode('@', $email); 190 return strtolower((string) end($parts)); 191 } 192 193 /** 194 * Get normalized site domain for the current install. 195 * 196 * @return string 197 */ 198 function meshsmtp_get_site_domain() { 199 $host = wp_parse_url(home_url(), PHP_URL_HOST); 200 if (!is_string($host) || '' === $host) { 201 return ''; 202 } 203 204 $host = strtolower($host); 205 if (0 === strpos($host, 'www.')) { 206 $host = substr($host, 4); 207 } 208 209 return sanitize_text_field($host); 210 } 211 212 /** 213 * Detect a supported provider from MX hosts. 214 * 215 * @param array<int, string> $mx_hosts MX hosts. 216 * @return array{provider: string, match: string} 217 */ 218 function meshsmtp_detect_provider_from_mx_hosts($mx_hosts) { 219 if (!is_array($mx_hosts)) { 220 return array( 221 'provider' => '', 222 'match' => '', 223 ); 224 } 225 226 foreach ($mx_hosts as $mx_host) { 227 $mx_host = strtolower(sanitize_text_field((string) $mx_host)); 228 if ('' === $mx_host) { 229 continue; 230 } 231 232 if (false !== strpos($mx_host, 'hostinger')) { 233 return array( 234 'provider' => 'hostinger', 235 'match' => $mx_host, 236 ); 237 } 238 239 if (false !== strpos($mx_host, 'google.com') || false !== strpos($mx_host, 'googlemail.com')) { 240 return array( 241 'provider' => 'gmail', 242 'match' => $mx_host, 243 ); 244 } 245 246 if (false !== strpos($mx_host, 'outlook.com') || false !== strpos($mx_host, 'protection.outlook.com')) { 247 return array( 248 'provider' => 'outlook', 249 'match' => $mx_host, 250 ); 251 } 252 253 if (false !== strpos($mx_host, 'zoho')) { 254 return array( 255 'provider' => 'zoho', 256 'match' => $mx_host, 257 ); 258 } 259 } 260 261 return array( 262 'provider' => '', 263 'match' => '', 264 ); 265 } 266 267 /** 268 * AJAX endpoint for soft provider hints based on site-domain mail routing. 269 */ 270 function meshsmtp_ajax_detect_provider_hint() { 271 if (!current_user_can('manage_options')) { 272 wp_send_json_error(array('message' => __('You are not allowed to do that.', 'mesh-smtp')), 403); 273 } 274 275 check_ajax_referer('meshsmtp_detect_provider', 'nonce'); 276 277 $site_domain = meshsmtp_get_site_domain(); 278 if ('' === $site_domain) { 279 wp_send_json_success( 280 array( 281 'provider' => '', 282 'siteDomain' => '', 283 'source' => '', 284 'match' => '', 285 ) 286 ); 287 } 288 289 $mx_hosts = array(); 290 291 if (function_exists('getmxrr')) { 292 $resolved_hosts = array(); 293 $resolved = getmxrr($site_domain, $resolved_hosts); 294 if ($resolved && is_array($resolved_hosts)) { 295 $mx_hosts = $resolved_hosts; 296 } 297 } 298 299 if (empty($mx_hosts) && function_exists('dns_get_record')) { 300 $dns_records = dns_get_record($site_domain, DNS_MX); 301 if (is_array($dns_records)) { 302 foreach ($dns_records as $dns_record) { 303 if (!empty($dns_record['target']) && is_string($dns_record['target'])) { 304 $mx_hosts[] = $dns_record['target']; 305 } 306 } 307 } 308 } 309 310 $mx_match = meshsmtp_detect_provider_from_mx_hosts($mx_hosts); 311 312 wp_send_json_success( 313 array( 314 'provider' => $mx_match['provider'], 315 'siteDomain' => $site_domain, 316 'source' => !empty($mx_hosts) ? 'mx' : '', 317 'match' => $mx_match['match'], 318 ) 319 ); 320 } 321 322 /** 140 323 * Handle secure send-test-email action. 141 324 */ … … 156 339 } 157 340 158 update_option(MESHSMTP_LAST_TEST_OPTION, $test_to );341 update_option(MESHSMTP_LAST_TEST_OPTION, $test_to, false); 159 342 delete_transient(meshsmtp_mail_error_key()); 160 343 … … 167 350 $message = sprintf( 168 351 /* translators: %s: formatted date time */ 169 __("Success! If you received this message, your SMTP configuration is working.\n\nSent at: %s", 'mesh-smtp'), 352 __("Success! If you received this message, your SMTP configuration is working. 353 354 Sent at: %s", 'mesh-smtp'), 170 355 wp_date('Y-m-d H:i:s') 171 356 ); … … 178 363 ); 179 364 365 update_option(MESHSMTP_LAST_TEST_TIME_OPTION, time(), false); 366 180 367 if ($sent) { 368 update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'success', false); 181 369 meshsmtp_set_notice('success', esc_html__('Test email sent successfully.', 'mesh-smtp')); 182 370 } else { 371 update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'failed', false); 183 372 $error = get_transient(meshsmtp_mail_error_key()); 184 $text = esc_html__('Email could not be sent. Recheck host, port, credentials, and firewall settings.', 'mesh-smtp'); 185 if (!empty($error) && is_string($error)) { 186 $text .= ' ' . sprintf( 373 $text = !empty($error) && is_string($error) 374 ? meshsmtp_get_friendly_error_hint($error) . ' ' . sprintf( 187 375 /* translators: %s: wp_mail error message */ 188 376 esc_html__('Error: %s', 'mesh-smtp'), 189 377 sanitize_text_field($error) 190 ) ;191 }378 ) 379 : esc_html__('Email could not be sent. Recheck the host, port, credentials, and firewall settings.', 'mesh-smtp'); 192 380 meshsmtp_set_notice('error', $text); 193 381 } … … 265 453 */ 266 454 function meshsmtp_render_notice($type, $message) { 267 $class = ('success' === $type) ? 'notice notice-success is-dismissible' : 'notice notice-error is-dismissible'; 455 $class = ('success' === $type) 456 ? 'notice notice-success is-dismissible meshsmtp-notice' 457 : 'notice notice-error is-dismissible meshsmtp-notice'; 268 458 ?> 269 459 <div class="<?php echo esc_attr($class); ?>"> … … 281 471 } 282 472 283 $options = meshsmtp_get_settings(); 284 $last_test = get_option(MESHSMTP_LAST_TEST_OPTION, get_bloginfo('admin_email')); 285 $notice = meshsmtp_pop_notice(); 473 $options = meshsmtp_get_settings(); 474 $last_test = get_option(MESHSMTP_LAST_TEST_OPTION, get_bloginfo('admin_email')); 475 $last_test_status = get_option(MESHSMTP_LAST_TEST_STATUS_OPTION, ''); 476 $last_test_time = absint(get_option(MESHSMTP_LAST_TEST_TIME_OPTION, 0)); 477 $notice = meshsmtp_pop_notice(); 478 $username_domain = meshsmtp_get_email_domain((string) $options['username']); 479 $from_domain = meshsmtp_get_email_domain((string) $options['from_email']); 480 $status_text = ('1' === (string) $options['enabled']) ? esc_html__('Enabled', 'mesh-smtp') : esc_html__('Disabled', 'mesh-smtp'); 286 481 ?> 287 <div class="wrap meshsmtp-wrap"> 288 <div class="meshsmtp-hero"> 289 <div> 290 <h1><?php echo esc_html__('Mesh SMTP', 'mesh-smtp'); ?></h1> 291 <p><?php echo esc_html__('Secure SMTP controls with a cleaner, modern workflow.', 'mesh-smtp'); ?></p> 292 </div> 293 <div class="meshsmtp-badge"><?php echo esc_html('v' . MESHSMTP_PLUGIN_VERSION); ?></div> 482 <div class="wrap meshsmtp-wrap"> 483 <div class="meshsmtp-hero"> 484 <div> 485 <h1><?php echo esc_html__('Mesh SMTP', 'mesh-smtp'); ?></h1> 486 <p><?php echo esc_html__('Simple SMTP setup for reliable WordPress email delivery.', 'mesh-smtp'); ?></p> 294 487 </div> 488 <div class="meshsmtp-badge"><?php echo esc_html('v' . MESHSMTP_PLUGIN_VERSION); ?></div> 489 </div> 490 491 <div class="meshsmtp-statusbar"> 492 <div><strong><?php echo esc_html__('SMTP', 'mesh-smtp'); ?>:</strong> <?php echo esc_html($status_text); ?></div> 493 <div><strong><?php echo esc_html__('Host', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['host'] ?: '—'); ?></div> 494 <div><strong><?php echo esc_html__('Encryption', 'mesh-smtp'); ?>:</strong> <?php echo esc_html(strtoupper((string) $options['secure'])); ?></div> 495 <div><strong><?php echo esc_html__('Port', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['port']); ?></div> 496 <div><strong><?php echo esc_html__('From', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $options['from_email'] ?: '—'); ?></div> 497 </div> 295 498 296 499 <?php … … 301 504 meshsmtp_render_notice($notice['type'], $notice['message']); 302 505 } 506 if ($username_domain && $from_domain && $username_domain !== $from_domain) { 507 meshsmtp_render_notice('error', esc_html__('From Email domain does not match the SMTP login domain. This can affect deliverability.', 'mesh-smtp')); 508 } 303 509 ?> 304 510 305 <div class="meshsmtp-grid"> 306 <section class="meshsmtp-card"> 307 <h2><?php echo esc_html__('SMTP Configuration', 'mesh-smtp'); ?></h2> 308 <form method="post" action="options.php" autocomplete="off"> 309 <?php settings_fields('meshsmtp_settings_group'); ?> 310 311 <div class="meshsmtp-field meshsmtp-field-inline"> 312 <label for="meshsmtp-provider"><?php echo esc_html__('Provider Preset', 'mesh-smtp'); ?></label> 511 <div class="meshsmtp-grid"> 512 <section class="meshsmtp-card"> 513 <h2><?php echo esc_html__('SMTP Configuration', 'mesh-smtp'); ?></h2> 514 <form method="post" action="options.php" autocomplete="off"> 515 <?php settings_fields('meshsmtp_settings_group'); ?> 516 517 <div class="meshsmtp-field meshsmtp-field-inline meshsmtp-field-row"> 518 <label for="meshsmtp-provider"><?php echo esc_html__('Provider Preset', 'mesh-smtp'); ?></label> 519 <div class="meshsmtp-preset-row"> 313 520 <select id="meshsmtp-provider"> 314 <option value=""><?php echo esc_html__('Select a provider', 'mesh-smtp'); ?></option> 315 <option value="gmail">Gmail</option> 316 <option value="outlook">Outlook / Microsoft 365</option> 317 <option value="zoho">Zoho</option> 318 <option value="hostinger">Hostinger</option> 319 <option value="sendgrid">SendGrid</option> 320 <option value="mailgun">Mailgun</option> 321 <option value="custom">Custom</option> 322 </select> 323 </div> 324 325 <div class="meshsmtp-toggle"> 326 <label for="meshsmtp-enabled"><?php echo esc_html__('Enable SMTP override', 'mesh-smtp'); ?></label> 327 <input id="meshsmtp-enabled" type="checkbox" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="1" <?php checked('1', (string) $options['enabled']); ?> /> 521 <option value=""><?php echo esc_html__('Select a provider', 'mesh-smtp'); ?></option> 522 <option value="gmail">Gmail</option> 523 <option value="outlook">Outlook / Microsoft 365</option> 524 <option value="zoho">Zoho</option> 525 <option value="hostinger">Hostinger</option> 526 <option value="sendgrid">SendGrid</option> 527 <option value="mailgun">Mailgun</option> 528 <option value="custom">Custom</option> 529 </select> 530 <button type="button" class="button meshsmtp-password-toggle meshsmtp-tertiary" id="meshsmtp-auto-detect"><?php echo esc_html__('Auto Detect', 'mesh-smtp'); ?></button> 328 531 </div> 329 532 <p class="description"><?php echo esc_html__('Auto-detect uses your SMTP login, host, and mail routing hints.', 'mesh-smtp'); ?></p> 533 <p id="meshsmtp-provider-help" class="description"></p> 534 <p id="meshsmtp-provider-detect-result" class="description meshsmtp-provider-detect-result"></p> 535 </div> 536 537 <div class="meshsmtp-toggle"> 538 <label for="meshsmtp-enabled"><?php echo esc_html__('Enable SMTP override', 'mesh-smtp'); ?></label> 539 <input id="meshsmtp-enabled-hidden" type="hidden" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="0" /> 540 <input id="meshsmtp-enabled" type="checkbox" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[enabled]" value="1" <?php checked('1', (string) $options['enabled']); ?> /> 541 </div> 542 543 <div class="meshsmtp-field meshsmtp-field-row"> 544 <label for="meshsmtp-host"><?php echo esc_html__('SMTP Host', 'mesh-smtp'); ?></label> 545 <input id="meshsmtp-host" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[host]" value="<?php echo esc_attr((string) $options['host']); ?>" placeholder="smtp.example.com" required /> 546 </div> 547 548 <div class="meshsmtp-dual"> 330 549 <div class="meshsmtp-field"> 331 <label for="meshsmtp-host"><?php echo esc_html__('SMTP Host', 'mesh-smtp'); ?></label> 332 <input id="meshsmtp-host" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[host]" value="<?php echo esc_attr((string) $options['host']); ?>" placeholder="smtp.example.com" required /> 550 <label for="meshsmtp-secure"><?php echo esc_html__('Encryption', 'mesh-smtp'); ?></label> 551 <select id="meshsmtp-secure" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[secure]"> 552 <option value="tls" <?php selected((string) $options['secure'], 'tls'); ?>>TLS</option> 553 <option value="ssl" <?php selected((string) $options['secure'], 'ssl'); ?>>SSL</option> 554 <option value="none" <?php selected((string) $options['secure'], 'none'); ?>>None</option> 555 </select> 333 556 </div> 334 335 <div class="meshsmtp-dual"> 336 <div class="meshsmtp-field"> 337 <label for="meshsmtp-secure"><?php echo esc_html__('Encryption', 'mesh-smtp'); ?></label> 338 <select id="meshsmtp-secure" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[secure]"> 339 <option value="tls" <?php selected((string) $options['secure'], 'tls'); ?>>TLS</option> 340 <option value="ssl" <?php selected((string) $options['secure'], 'ssl'); ?>>SSL</option> 341 <option value="none" <?php selected((string) $options['secure'], 'none'); ?>>None</option> 342 </select> 343 </div> 344 <div class="meshsmtp-field"> 345 <label for="meshsmtp-port"><?php echo esc_html__('Port', 'mesh-smtp'); ?></label> 346 <input id="meshsmtp-port" type="number" min="1" max="65535" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[port]" value="<?php echo esc_attr((string) $options['port']); ?>" required /> 347 </div> 557 <div class="meshsmtp-field"> 558 <label for="meshsmtp-port"><?php echo esc_html__('Port', 'mesh-smtp'); ?></label> 559 <input id="meshsmtp-port" type="number" min="1" max="65535" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[port]" value="<?php echo esc_attr((string) $options['port']); ?>" required /> 348 560 </div> 349 561 </div> 562 563 <div class="meshsmtp-field meshsmtp-field-row"> 564 <label for="meshsmtp-username"><?php echo esc_html__('Username / SMTP Login', 'mesh-smtp'); ?></label> 565 <input id="meshsmtp-username" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[username]" value="<?php echo esc_attr((string) $options['username']); ?>" autocomplete="off" required /> 566 </div> 567 568 <div class="meshsmtp-field meshsmtp-field-row"> 569 <label for="meshsmtp-password"><?php echo esc_html__('Password / App Password', 'mesh-smtp'); ?></label> 570 <div class="meshsmtp-password-wrap"> 571 <input id="meshsmtp-password" type="password" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[password]" value="" placeholder="<?php echo esc_attr__('Leave blank to keep current password', 'mesh-smtp'); ?>" autocomplete="new-password" /> 572 <button type="button" class="button meshsmtp-password-toggle" id="meshsmtp-password-toggle"><?php echo esc_html__('Show', 'mesh-smtp'); ?></button> 573 </div> 574 </div> 575 576 <div class="meshsmtp-dual"> 350 577 <div class="meshsmtp-field"> 351 <label for="meshsmtp- username"><?php echo esc_html__('Username / SMTP Login', 'mesh-smtp'); ?></label>352 <input id="meshsmtp- username" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[username]" value="<?php echo esc_attr((string) $options['username']); ?>" autocomplete="off" required />578 <label for="meshsmtp-from-email"><?php echo esc_html__('From Email', 'mesh-smtp'); ?></label> 579 <input id="meshsmtp-from-email" type="email" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_email]" value="<?php echo esc_attr((string) $options['from_email']); ?>" required /> 353 580 </div> 354 355 581 <div class="meshsmtp-field"> 356 <label for="meshsmtp- password"><?php echo esc_html__('Password / App Password', 'mesh-smtp'); ?></label>357 <input id="meshsmtp- password" type="password" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[password]" value="" placeholder="<?php echo esc_attr__('Leave blank to keep current password', 'mesh-smtp'); ?>" autocomplete="new-password"/>582 <label for="meshsmtp-from-name"><?php echo esc_html__('From Name', 'mesh-smtp'); ?></label> 583 <input id="meshsmtp-from-name" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_name]" value="<?php echo esc_attr((string) $options['from_name']); ?>" required /> 358 584 </div> 359 360 <div class="meshsmtp-dual"> 361 <div class="meshsmtp-field"> 362 <label for="meshsmtp-from-email"><?php echo esc_html__('From Email', 'mesh-smtp'); ?></label> 363 <input id="meshsmtp-from-email" type="email" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_email]" value="<?php echo esc_attr((string) $options['from_email']); ?>" required /> 364 </div> 365 <div class="meshsmtp-field"> 366 <label for="meshsmtp-from-name"><?php echo esc_html__('From Name', 'mesh-smtp'); ?></label> 367 <input id="meshsmtp-from-name" type="text" name="<?php echo esc_attr(MESHSMTP_OPTION_KEY); ?>[from_name]" value="<?php echo esc_attr((string) $options['from_name']); ?>" required /> 368 </div> 369 </div> 370 371 <p class="submit"> 372 <button type="submit" class="button button-primary meshsmtp-primary"><?php echo esc_html__('Save SMTP Settings', 'mesh-smtp'); ?></button> 373 </p> 374 </form> 375 </section> 376 377 <section class="meshsmtp-card meshsmtp-test-card"> 378 <h2><?php echo esc_html__('Connection Test', 'mesh-smtp'); ?></h2> 379 <p><?php echo esc_html__('Send a live test to verify your current SMTP setup.', 'mesh-smtp'); ?></p> 380 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 381 <?php wp_nonce_field('meshsmtp_send_test_email_action'); ?> 382 <input type="hidden" name="action" value="meshsmtp_send_test_email" /> 383 <div class="meshsmtp-field"> 384 <label for="meshsmtp-test-email"><?php echo esc_html__('Recipient Email', 'mesh-smtp'); ?></label> 385 <input id="meshsmtp-test-email" type="email" name="meshsmtp_test_email" value="<?php echo esc_attr((string) $last_test); ?>" required /> 386 </div> 387 <button type="submit" class="button meshsmtp-secondary"><?php echo esc_html__('Send Test Email', 'mesh-smtp'); ?></button> 388 </form> 585 </div> 586 587 <p class="submit"> 588 <button type="submit" class="button button-primary meshsmtp-primary"><?php echo esc_html__('Save SMTP Settings', 'mesh-smtp'); ?></button> 589 </p> 590 </form> 591 </section> 592 593 <section class="meshsmtp-card meshsmtp-test-card"> 594 <h2><?php echo esc_html__('Connection Test', 'mesh-smtp'); ?></h2> 595 <p><?php echo esc_html__('Send a live test to verify your current SMTP setup.', 'mesh-smtp'); ?></p> 596 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"> 597 <?php wp_nonce_field('meshsmtp_send_test_email_action'); ?> 598 <input type="hidden" name="action" value="meshsmtp_send_test_email" /> 599 <div class="meshsmtp-field meshsmtp-field-row"> 600 <label for="meshsmtp-test-email"><?php echo esc_html__('Recipient Email', 'mesh-smtp'); ?></label> 601 <input id="meshsmtp-test-email" type="email" name="meshsmtp_test_email" value="<?php echo esc_attr((string) $last_test); ?>" required /> 602 </div> 603 <button type="submit" class="button meshsmtp-secondary"><?php echo esc_html__('Send Test Email', 'mesh-smtp'); ?></button> 604 </form> 605 606 <hr /> 607 <h3><?php echo esc_html__('Last Test', 'mesh-smtp'); ?></h3> 608 <ul class="meshsmtp-meta-list"> 609 <li><strong><?php echo esc_html__('Recipient', 'mesh-smtp'); ?>:</strong> <?php echo esc_html((string) $last_test ?: '—'); ?></li> 610 <li><strong><?php echo esc_html__('Result', 'mesh-smtp'); ?>:</strong> <?php echo esc_html($last_test_status ? ucfirst((string) $last_test_status) : '—'); ?></li> 611 <li><strong><?php echo esc_html__('Time', 'mesh-smtp'); ?>:</strong> <?php echo $last_test_time ? esc_html(wp_date('Y-m-d H:i:s', $last_test_time)) : '—'; ?></li> 612 </ul> 389 613 390 614 <hr /> -
mesh-smtp/trunk/assets/admin.css
r3481868 r3482419 16 16 17 17 .meshsmtp-hero { 18 align-items: center;18 align-items: flex-start; 19 19 background: linear-gradient(120deg, #0f172a, #0b4f6c 52%, #0d9488); 20 20 border-radius: 14px; … … 22 22 display: flex; 23 23 justify-content: space-between; 24 margin-bottom: 20px;25 padding: 1 8px 22px;24 margin-bottom: 16px; 25 padding: 14px 18px; 26 26 } 27 27 28 28 .meshsmtp-hero h1 { 29 29 color: #ffffff; 30 font-size: 2 4px;30 font-size: 22px; 31 31 line-height: 1.2; 32 32 margin: 0; … … 35 35 .meshsmtp-hero p { 36 36 color: #cbd5e1; 37 margin: 8px 0 0; 37 font-size: 13px; 38 line-height: 1.5; 39 margin: 4px 0 0; 38 40 } 39 41 … … 42 44 border: 1px solid rgba(255, 255, 255, 0.35); 43 45 border-radius: 999px; 44 font-weight: 600; 45 padding: 6px 12px; 46 font-size: 12px; 47 font-weight: 600; 48 padding: 5px 10px; 49 } 50 51 .meshsmtp-statusbar { 52 display: grid; 53 gap: 10px; 54 grid-template-columns: repeat(5, minmax(0, 1fr)); 55 background: rgba(255, 255, 255, 0.75); 56 border: 1px solid #dbe4ee; 57 border-radius: 12px; 58 margin-bottom: 16px; 59 padding: 12px 14px; 60 font-size: 13px; 61 color: #334155; 46 62 } 47 63 … … 71 87 72 88 .meshsmtp-card p, 73 .meshsmtp-card li { 89 .meshsmtp-card li, 90 .meshsmtp-field .description { 74 91 color: var(--meshsmtp-muted); 75 92 } 76 93 77 94 .meshsmtp-field { 78 margin-bottom: 1 4px;95 margin-bottom: 12px; 79 96 } 80 97 … … 103 120 } 104 121 122 .meshsmtp-field input:disabled, 123 .meshsmtp-field select:disabled { 124 background: #f8fafc; 125 color: #94a3b8; 126 cursor: not-allowed; 127 } 128 105 129 .meshsmtp-field-inline { 106 130 margin-bottom: 16px; 107 131 } 108 132 133 .meshsmtp-field-row .description { 134 margin-bottom: 0; 135 } 136 137 .meshsmtp-preset-row { 138 align-items: center; 139 display: flex; 140 gap: 10px; 141 } 142 143 .meshsmtp-preset-row select { 144 flex: 1 1 auto; 145 } 146 109 147 .meshsmtp-dual { 110 column-gap: 14px;111 148 display: grid; 112 grid-template-columns: repeat(2, minmax(0, 1fr)); 149 gap: 12px; 150 grid-template-columns: 1fr; 113 151 } 114 152 … … 129 167 } 130 168 169 .meshsmtp-password-wrap { 170 display: flex; 171 gap: 10px; 172 } 173 174 .meshsmtp-password-wrap input { 175 flex: 1 1 auto; 176 } 177 178 .meshsmtp-password-toggle { 179 border-radius: 10px; 180 min-height: 40px; 181 padding: 0 14px; 182 white-space: nowrap; 183 } 184 185 .meshsmtp-tertiary { 186 min-width: 116px; 187 } 188 189 .meshsmtp-provider-detect-result { 190 margin-top: 8px; 191 } 192 193 .meshsmtp-provider-detect-result.is-success { 194 color: #065f46; 195 font-weight: 600; 196 } 197 198 .meshsmtp-provider-detect-result.is-warning { 199 color: #92400e; 200 font-weight: 600; 201 } 202 131 203 .meshsmtp-primary, 132 204 .meshsmtp-secondary { … … 146 218 .meshsmtp-primary:focus { 147 219 background: linear-gradient(120deg, #0284c7, #0369a1); 220 border-color: #0369a1; 148 221 color: #ffffff; 149 222 } … … 174 247 } 175 248 249 .meshsmtp-meta-list { 250 list-style: none !important; 251 padding-left: 0 !important; 252 } 253 254 .meshsmtp-meta-list li { 255 margin-bottom: 8px; 256 } 257 258 .meshsmtp-wrap .notice p { 259 color: #0f172a !important; 260 font-weight: 600; 261 } 262 263 @media (min-width: 900px) { 264 .meshsmtp-field-row { 265 align-items: center; 266 column-gap: 14px; 267 display: grid; 268 grid-template-columns: 235px minmax(0, 1fr); 269 } 270 271 .meshsmtp-field-row label { 272 margin-bottom: 0; 273 } 274 275 .meshsmtp-field-row > input, 276 .meshsmtp-field-row > select, 277 .meshsmtp-field-row > .meshsmtp-password-wrap, 278 .meshsmtp-field-row > .meshsmtp-preset-row, 279 .meshsmtp-field-row > .description { 280 grid-column: 2; 281 } 282 283 .meshsmtp-field-row > .description { 284 margin-top: 4px; 285 } 286 287 .meshsmtp-dual { 288 align-items: start; 289 column-gap: 14px; 290 grid-template-columns: 235px minmax(0, 1fr) minmax(0, 1fr); 291 } 292 293 .meshsmtp-dual::before { 294 content: ''; 295 } 296 297 .meshsmtp-dual > .meshsmtp-field { 298 margin-bottom: 0; 299 } 300 301 .meshsmtp-test-card .meshsmtp-field-row { 302 grid-template-columns: 170px minmax(0, 1fr); 303 } 304 } 305 176 306 @media (max-width: 1100px) { 177 .meshsmtp-grid { 307 .meshsmtp-grid, 308 .meshsmtp-statusbar { 178 309 grid-template-columns: 1fr; 179 310 } … … 187 318 188 319 .meshsmtp-hero { 189 align-items: flex-start;190 320 flex-direction: column; 191 321 gap: 10px; 192 322 } 193 323 194 .meshsmtp-dual { 324 .meshsmtp-preset-row { 325 align-items: stretch; 326 flex-direction: column; 327 } 328 329 .meshsmtp-password-wrap { 195 330 grid-template-columns: 1fr; 196 } 197 } 198 199 200 /* Make WP admin notices readable on Mesh SMTP page */ 201 .meshsmtp-wrap .notice p { 202 color: #0f172a !important; 203 font-weight: 600; 204 } 331 display: grid; 332 } 333 } -
mesh-smtp/trunk/assets/admin.js
r3481868 r3482419 6 6 const secureField = document.getElementById('meshsmtp-secure'); 7 7 const portField = document.getElementById('meshsmtp-port'); 8 9 if (!providerField || !hostField || !secureField || !portField) { 10 return; 11 } 8 const enabledField = document.getElementById('meshsmtp-enabled'); 9 const form = document.querySelector('.meshsmtp-card form'); 10 const providerHelp = document.getElementById('meshsmtp-provider-help'); 11 const providerDetectResult = document.getElementById('meshsmtp-provider-detect-result'); 12 const autoDetectButton = document.getElementById('meshsmtp-auto-detect'); 13 const usernameField = document.getElementById('meshsmtp-username'); 14 const fromEmailField = document.getElementById('meshsmtp-from-email'); 15 const passwordField = document.getElementById('meshsmtp-password'); 16 const passwordToggle = document.getElementById('meshsmtp-password-toggle'); 17 const adminConfig = window.meshsmtpAdmin || {}; 18 let allowFromEmailSync = false; 12 19 13 20 const presets = { … … 21 28 }; 22 29 23 providerField.addEventListener('change', function () { 24 const selected = presets[this.value]; 30 const presetHelp = { 31 gmail: 'Use a Google App Password instead of your normal Gmail password.', 32 outlook: 'SMTP AUTH may need to be enabled for the mailbox in Microsoft 365.', 33 zoho: 'Use the full Zoho mailbox address as the SMTP username.', 34 hostinger: 'Use the full mailbox email address as the SMTP login.', 35 sendgrid: 'Use "apikey" as the username and your API key as the password when applicable.', 36 mailgun: 'Mailgun SMTP hosts can vary by region or account configuration.', 37 custom: 'Enter the exact SMTP details provided by your hosting or email service.' 38 }; 39 40 const providerLabels = { 41 gmail: 'Gmail', 42 outlook: 'Outlook / Microsoft 365', 43 zoho: 'Zoho', 44 hostinger: 'Hostinger', 45 sendgrid: 'SendGrid', 46 mailgun: 'Mailgun', 47 custom: 'Custom' 48 }; 49 50 function setProviderDetectResult(message, type) { 51 if (!providerDetectResult) { 52 return; 53 } 54 55 providerDetectResult.textContent = message || ''; 56 providerDetectResult.className = 'description meshsmtp-provider-detect-result'; 57 58 if (message && type) { 59 providerDetectResult.classList.add('is-' + type); 60 } 61 } 62 63 function applyProviderHelp() { 64 if (!providerField || !providerHelp) { 65 return; 66 } 67 68 providerHelp.textContent = presetHelp[providerField.value] || ''; 69 } 70 71 function applyProviderPreset() { 72 if (!providerField || !hostField || !secureField || !portField) { 73 return; 74 } 75 76 const selected = presets[providerField.value]; 25 77 if (!selected) { 78 applyProviderHelp(); 26 79 return; 27 80 } … … 30 83 secureField.value = selected.secure; 31 84 portField.value = selected.port; 32 }); 33 34 secureField.addEventListener('change', function () { 35 if (this.value === 'ssl') { 85 applyProviderHelp(); 86 } 87 88 function applyDetectedProvider(provider, message, type) { 89 if (!providerField || !provider) { 90 return; 91 } 92 93 providerField.value = provider; 94 applyProviderPreset(); 95 setProviderDetectResult(message, type); 96 } 97 98 function syncPortToEncryption() { 99 if (!secureField || !portField) { 100 return; 101 } 102 103 if (secureField.value === 'ssl') { 36 104 portField.value = '465'; 37 105 return; 38 106 } 39 107 40 if ( this.value === 'tls') {108 if (secureField.value === 'tls') { 41 109 portField.value = '587'; 42 110 return; … … 44 112 45 113 portField.value = '25'; 114 } 115 116 function toggleFields() { 117 if (!enabledField || !form) { 118 return; 119 } 120 121 form.querySelectorAll('input, select, button').forEach(function (field) { 122 if ( 123 field.id === 'meshsmtp-enabled' || 124 field.type === 'hidden' || 125 field.type === 'submit' || 126 field.tagName === 'BUTTON' 127 ) { 128 return; 129 } 130 131 field.disabled = !enabledField.checked; 132 }); 133 } 134 135 function bindPasswordToggle() { 136 if (!passwordField || !passwordToggle) { 137 return; 138 } 139 140 passwordToggle.addEventListener('click', function () { 141 const isPassword = passwordField.getAttribute('type') === 'password'; 142 passwordField.setAttribute('type', isPassword ? 'text' : 'password'); 143 passwordToggle.textContent = isPassword ? 'Hide' : 'Show'; 144 }); 145 } 146 147 function normalizeValue(value) { 148 return String(value || '').trim().toLowerCase(); 149 } 150 151 function looksLikeEmail(value) { 152 const text = String(value || '').trim(); 153 154 return text.indexOf('@') > 0 && text.indexOf(' ') === -1; 155 } 156 157 function initializeFromEmailSync() { 158 if (!usernameField || !fromEmailField) { 159 return; 160 } 161 162 const username = normalizeValue(usernameField.value); 163 const fromEmail = normalizeValue(fromEmailField.value); 164 165 allowFromEmailSync = !username || !fromEmail || username === fromEmail; 166 167 if (allowFromEmailSync && looksLikeEmail(usernameField.value) && !fromEmailField.value.trim()) { 168 fromEmailField.value = String(usernameField.value || '').trim(); 169 } 170 } 171 172 function syncFromEmailFromUsername() { 173 if (!allowFromEmailSync || !usernameField || !fromEmailField) { 174 return; 175 } 176 177 const usernameValue = String(usernameField.value || '').trim(); 178 if (!looksLikeEmail(usernameValue)) { 179 return; 180 } 181 182 fromEmailField.value = usernameValue; 183 } 184 185 function trackFromEmailOverride() { 186 if (!usernameField || !fromEmailField) { 187 return; 188 } 189 190 const username = normalizeValue(usernameField.value); 191 const fromEmail = normalizeValue(fromEmailField.value); 192 193 allowFromEmailSync = !fromEmail || !username || fromEmail === username; 194 } 195 196 function extractEmailDomain(value) { 197 const email = normalizeValue(value); 198 const atIndex = email.lastIndexOf('@'); 199 200 if (atIndex === -1) { 201 return ''; 202 } 203 204 return email.slice(atIndex + 1); 205 } 206 207 function detectProviderByHost(host) { 208 if (!host) { 209 return null; 210 } 211 212 if (host.indexOf('smtp.gmail.com') !== -1) { 213 return { provider: 'gmail', source: 'smtp_host', match: host }; 214 } 215 216 if ( 217 host.indexOf('office365.com') !== -1 || 218 host.indexOf('outlook.com') !== -1 || 219 host.indexOf('hotmail.com') !== -1 || 220 host.indexOf('live.com') !== -1 221 ) { 222 return { provider: 'outlook', source: 'smtp_host', match: host }; 223 } 224 225 if (host.indexOf('zoho') !== -1) { 226 return { provider: 'zoho', source: 'smtp_host', match: host }; 227 } 228 229 if (host.indexOf('hostinger') !== -1) { 230 return { provider: 'hostinger', source: 'smtp_host', match: host }; 231 } 232 233 if (host.indexOf('sendgrid') !== -1) { 234 return { provider: 'sendgrid', source: 'smtp_host', match: host }; 235 } 236 237 if (host.indexOf('mailgun') !== -1) { 238 return { provider: 'mailgun', source: 'smtp_host', match: host }; 239 } 240 241 return null; 242 } 243 244 function detectProviderByDomain(domain) { 245 if (!domain) { 246 return null; 247 } 248 249 if (domain === 'gmail.com' || domain === 'googlemail.com') { 250 return { provider: 'gmail', source: 'smtp_login_domain', match: domain }; 251 } 252 253 if ( 254 domain === 'outlook.com' || 255 domain === 'hotmail.com' || 256 domain === 'live.com' || 257 domain === 'msn.com' || 258 domain.indexOf('.onmicrosoft.com') !== -1 259 ) { 260 return { provider: 'outlook', source: 'smtp_login_domain', match: domain }; 261 } 262 263 if ( 264 domain === 'zoho.com' || 265 domain.indexOf('zoho.') === 0 || 266 domain.indexOf('.zoho.') !== -1 267 ) { 268 return { provider: 'zoho', source: 'smtp_login_domain', match: domain }; 269 } 270 271 if (domain === 'hostinger.com' || domain.indexOf('.hostinger.com') !== -1) { 272 return { provider: 'hostinger', source: 'smtp_login_domain', match: domain }; 273 } 274 275 if (domain === 'sendgrid.net' || domain.indexOf('.sendgrid.net') !== -1) { 276 return { provider: 'sendgrid', source: 'smtp_login_domain', match: domain }; 277 } 278 279 if (domain === 'mailgun.org' || domain.indexOf('.mailgun.org') !== -1) { 280 return { provider: 'mailgun', source: 'smtp_login_domain', match: domain }; 281 } 282 283 return null; 284 } 285 286 function detectProvider() { 287 const detectedByHost = detectProviderByHost(normalizeValue(hostField && hostField.value)); 288 289 if (detectedByHost) { 290 return detectedByHost; 291 } 292 293 const candidateDomains = [extractEmailDomain(usernameField && usernameField.value)]; 294 295 for (let index = 0; index < candidateDomains.length; index += 1) { 296 const detectedByDomain = detectProviderByDomain(candidateDomains[index]); 297 298 if (detectedByDomain) { 299 return detectedByDomain; 300 } 301 } 302 303 return null; 304 } 305 306 function buildExactDetectMessage(result) { 307 if (!result || !result.provider) { 308 return ''; 309 } 310 311 if (result.source === 'smtp_host') { 312 return 'Detected ' + providerLabels[result.provider] + ' from SMTP host ' + result.match + '. Review the values, then save settings.'; 313 } 314 315 if (result.source === 'smtp_login_domain') { 316 return 'Detected ' + providerLabels[result.provider] + ' from SMTP login domain ' + result.match + '. Review the values, then save settings.'; 317 } 318 319 return 'Detected ' + providerLabels[result.provider] + ' automatically. Review the values, then save settings.'; 320 } 321 322 function buildHintMessage(hint) { 323 if (!hint || !hint.provider) { 324 return ''; 325 } 326 327 if (hint.source === 'mx' && hint.match) { 328 return 'Suggested ' + providerLabels[hint.provider] + ' from MX record ' + hint.match + ' for ' + hint.siteDomain + '. Review the values before saving.'; 329 } 330 331 if (hint.source === 'mx') { 332 return 'Suggested ' + providerLabels[hint.provider] + ' from the mail routing on ' + hint.siteDomain + '. Review the values before saving.'; 333 } 334 335 return 'Suggested ' + providerLabels[hint.provider] + ' automatically. Review the values before saving.'; 336 } 337 338 function requestProviderHintFromSiteDomain() { 339 if (!adminConfig.ajaxUrl || !adminConfig.detectNonce || typeof window.fetch !== 'function') { 340 return Promise.resolve(null); 341 } 342 343 const requestBody = new window.URLSearchParams(); 344 requestBody.set('action', 'meshsmtp_detect_provider_hint'); 345 requestBody.set('nonce', adminConfig.detectNonce); 346 347 return window.fetch(adminConfig.ajaxUrl, { 348 method: 'POST', 349 credentials: 'same-origin', 350 headers: { 351 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 352 }, 353 body: requestBody.toString() 354 }) 355 .then(function (response) { 356 if (!response.ok) { 357 return null; 358 } 359 360 return response.json(); 361 }) 362 .then(function (payload) { 363 if (!payload || !payload.success || !payload.data) { 364 return null; 365 } 366 367 return payload.data; 368 }) 369 .catch(function () { 370 return null; 371 }); 372 } 373 374 function finishWithoutMatch() { 375 if (!providerField.value) { 376 providerField.value = 'custom'; 377 applyProviderHelp(); 378 } 379 380 setProviderDetectResult( 381 'No exact provider match was found. Keep using Custom or choose a provider manually.', 382 'warning' 383 ); 384 } 385 386 function autoDetectProvider() { 387 if (!providerField) { 388 return; 389 } 390 391 const detectedProvider = detectProvider(); 392 393 if (detectedProvider && detectedProvider.provider) { 394 applyDetectedProvider( 395 detectedProvider.provider, 396 buildExactDetectMessage(detectedProvider), 397 'success' 398 ); 399 return; 400 } 401 402 if (autoDetectButton) { 403 autoDetectButton.disabled = true; 404 autoDetectButton.textContent = 'Checking...'; 405 } 406 407 setProviderDetectResult('No exact match yet. Checking your site domain mail routing for a hint...', 'warning'); 408 409 requestProviderHintFromSiteDomain().then(function (hint) { 410 if (autoDetectButton) { 411 autoDetectButton.disabled = false; 412 autoDetectButton.textContent = 'Auto Detect'; 413 } 414 415 if (hint && hint.provider) { 416 applyDetectedProvider( 417 hint.provider, 418 buildHintMessage(hint), 419 'warning' 420 ); 421 return; 422 } 423 424 finishWithoutMatch(); 425 }); 426 } 427 428 document.addEventListener('DOMContentLoaded', function () { 429 applyProviderHelp(); 430 toggleFields(); 431 bindPasswordToggle(); 432 initializeFromEmailSync(); 433 434 if (providerField) { 435 providerField.addEventListener('change', function () { 436 setProviderDetectResult(''); 437 applyProviderPreset(); 438 }); 439 } 440 441 if (secureField) { 442 secureField.addEventListener('change', syncPortToEncryption); 443 } 444 445 if (enabledField) { 446 enabledField.addEventListener('change', toggleFields); 447 } 448 449 if (usernameField) { 450 usernameField.addEventListener('input', syncFromEmailFromUsername); 451 usernameField.addEventListener('change', syncFromEmailFromUsername); 452 } 453 454 if (fromEmailField) { 455 fromEmailField.addEventListener('input', trackFromEmailOverride); 456 fromEmailField.addEventListener('change', trackFromEmailOverride); 457 } 458 459 if (autoDetectButton) { 460 autoDetectButton.addEventListener('click', autoDetectProvider); 461 } 46 462 }); 47 463 })(); -
mesh-smtp/trunk/mesh-smtp.php
r3481868 r3482419 3 3 * Plugin Name: Mesh SMTP 4 4 * Plugin URI: https://meshcreation.com/plugins/mesh-smtp 5 * * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI.and a modern admin UI.6 * Version: 1.2. 25 * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI. 6 * Version: 1.2.4 7 7 * Author: Mesh Creation 8 8 * Author URI: https://meshcreation.com … … 19 19 } 20 20 21 define('MESHSMTP_PLUGIN_VERSION', '1.2. 2');21 define('MESHSMTP_PLUGIN_VERSION', '1.2.4'); 22 22 define('MESHSMTP_PLUGIN_FILE', __FILE__); 23 23 define('MESHSMTP_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 26 26 define('MESHSMTP_LEGACY_OPTION_KEY', 'msm_settings'); 27 27 define('MESHSMTP_LAST_TEST_OPTION', 'meshsmtp_last_test_email'); 28 define('MESHSMTP_LAST_TEST_STATUS_OPTION', 'meshsmtp_last_test_status'); 29 define('MESHSMTP_LAST_TEST_TIME_OPTION', 'meshsmtp_last_test_time'); 28 30 define('MESHSMTP_LEGACY_LAST_TEST_OPTION', 'msm_last_test_email'); 29 31 define('MESHSMTP_MIGRATION_FLAG_OPTION', 'meshsmtp_legacy_migration_complete'); … … 147 149 148 150 $phpmailer->isSMTP(); 149 $phpmailer->Host = (string) $settings['host'];150 $phpmailer->SMTPAuth = true;151 $phpmailer->Port = (int) $settings['port'];152 $phpmailer->Username = (string) $settings['username'];153 $phpmailer->Password = (string) $settings['password'];154 $phpmailer->SMTPAutoTLS = true;155 $phpmailer->Timeout = 20;156 $phpmailer->SMTPDebug = 0;151 $phpmailer->Host = (string) $settings['host']; 152 $phpmailer->SMTPAuth = true; 153 $phpmailer->Port = (int) $settings['port']; 154 $phpmailer->Username = (string) $settings['username']; 155 $phpmailer->Password = (string) $settings['password']; 156 $phpmailer->SMTPAutoTLS = ('tls' === $settings['secure']); 157 $phpmailer->Timeout = 20; 158 $phpmailer->SMTPDebug = 0; 157 159 158 160 if (is_email($settings['from_email'])) { -
mesh-smtp/trunk/readme.txt
r3481868 r3482419 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.4 8 Stable tag: 1.2. 28 Stable tag: 1.2.4 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 21 21 22 22 * Simple SMTP configuration for host, port, encryption, username, and password 23 * Provider presets for faster setup23 * Provider presets with auto-detect help for known email domains, SMTP hosts, and site mail-routing hints 24 24 * Test email tool with clear success and error feedback 25 * Last test status and timestamp summary 26 * Better setup hints for popular providers 25 27 * Clean and modern admin UI 26 28 * Capability checks, nonce verification, and sanitized settings handling 27 29 * Passwords are not shown back in the UI after saving 30 * Saved SMTP details stay intact when you temporarily disable the SMTP override 28 31 * Works with WordPress emails sent through `wp_mail()` 29 32 … … 53 56 2. Activate the plugin through the **Plugins** screen in WordPress. 54 57 3. Open **Mesh SMTP** from the WordPress admin menu. 55 4. Enter your SMTP details manually or choose a supported provider preset.58 4. Enter your SMTP details manually, use Auto Detect, or choose a supported provider preset. 56 59 5. Save the settings. 57 60 6. Use the **Connection Test** section to send a test email. … … 87 90 == Changelog == 88 91 92 = 1.2.4 = 93 * Added an Auto Detect helper for known provider domains and SMTP hosts 94 * Added a soft site-domain mail-routing hint to better suggest Hostinger or other supported providers 95 * Added clearer auto-detect feedback so admins can see whether the match came from SMTP host, login domain, or MX routing 96 * Preserved saved SMTP settings when the SMTP override is turned off 97 * Stopped forcing opportunistic TLS when encryption is set to None 98 * Removed admin-page notice suppression for better WordPress.org review compatibility 99 100 = 1.2.3 = 101 * Fixed plugin header description formatting 102 * Hid unrelated admin notices on the Mesh SMTP settings screen 103 * Added a compact connection status bar 104 * Added last test result and timestamp details 105 * Added clearer failure hints and provider help text 106 * Added password visibility toggle and disabled fields when SMTP override is off 107 89 108 = 1.2.2 = 90 109 * Fixed settings page behavior for the updated WordPress.org review build … … 111 130 == Upgrade Notice == 112 131 132 = 1.2.4 = 133 Recommended update for safer SMTP toggling, provider auto-detect, and cleaner WordPress.org submission behavior. 134 135 = 1.2.3 = 136 Recommended update for a cleaner settings screen and more helpful SMTP diagnostics. 137 113 138 = 1.2.2 = 114 139 Recommended update for improved settings page reliability and compatibility metadata. -
mesh-smtp/trunk/uninstall.php
r3481868 r3482419 14 14 'msm_settings', 15 15 'meshsmtp_last_test_email', 16 'meshsmtp_last_test_status', 17 'meshsmtp_last_test_time', 16 18 'msm_last_test_email', 17 19 'meshsmtp_legacy_migration_complete',
Note: See TracChangeset
for help on using the changeset viewer.