Plugin Directory

Changeset 3482419


Ignore:
Timestamp:
03/14/2026 09:03:24 AM (3 weeks ago)
Author:
meshcrea
Message:

Release version 1.2.4 with updated screenshots

Location:
mesh-smtp
Files:
2 added
12 edited
1 copied

Legend:

Unmodified
Added
Removed
  • mesh-smtp/tags/1.2.4/admin/settings-page.php

    r3481868 r3482419  
    1010add_action('admin_post_meshsmtp_send_test_email', 'meshsmtp_handle_test_email');
    1111add_action('wp_mail_failed', 'meshsmtp_capture_mail_error');
     12add_action('wp_ajax_meshsmtp_detect_provider_hint', 'meshsmtp_ajax_detect_provider_hint');
    1213
    1314/**
     
    6566        true
    6667    );
     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    );
    6777}
    6878
     
    8191    }
    8292
    83     $host = '';
     93    $host = (string) $existing['host'];
    8494    if (isset($input['host'])) {
    8595        $host = sanitize_text_field(wp_unslash((string) $input['host']));
     
    8797    }
    8898
    89     $port = isset($input['port']) ? absint($input['port']) : (int) $defaults['port'];
     99    $port = isset($input['port']) ? absint($input['port']) : (int) $existing['port'];
    90100    if ($port < 1 || $port > 65535) {
    91101        $port = (int) $defaults['port'];
    92102    }
    93103
    94     $username = '';
     104    $username = (string) $existing['username'];
    95105    if (isset($input['username'])) {
    96106        $username = sanitize_text_field(wp_unslash((string) $input['username']));
     
    104114    }
    105115
    106     $from_email = '';
     116    $from_email = (string) $existing['from_email'];
    107117    if (isset($input['from_email'])) {
    108118        $from_email = sanitize_email(wp_unslash((string) $input['from_email']));
     
    112122    }
    113123
    114     $from_name = '';
     124    $from_name = (string) $existing['from_name'];
    115125    if (isset($input['from_name'])) {
    116126        $from_name = sanitize_text_field(wp_unslash((string) $input['from_name']));
     
    120130    }
    121131
    122     $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $defaults['secure'];
     132    $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $existing['secure'];
    123133    if (!in_array($secure, array('none', 'ssl', 'tls'), true)) {
    124134        $secure = (string) $defaults['secure'];
     
    138148
    139149/**
     150 * Map raw mail errors into clearer hints.
     151 *
     152 * @param string $error_message Raw mail error message.
     153 * @return string
     154 */
     155function 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 */
     183function 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 */
     198function 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 */
     218function 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 */
     270function 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/**
    140323 * Handle secure send-test-email action.
    141324 */
     
    156339    }
    157340
    158     update_option(MESHSMTP_LAST_TEST_OPTION, $test_to);
     341    update_option(MESHSMTP_LAST_TEST_OPTION, $test_to, false);
    159342    delete_transient(meshsmtp_mail_error_key());
    160343
     
    167350    $message = sprintf(
    168351        /* 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
     354Sent at: %s", 'mesh-smtp'),
    170355        wp_date('Y-m-d H:i:s')
    171356    );
     
    178363    );
    179364
     365    update_option(MESHSMTP_LAST_TEST_TIME_OPTION, time(), false);
     366
    180367    if ($sent) {
     368        update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'success', false);
    181369        meshsmtp_set_notice('success', esc_html__('Test email sent successfully.', 'mesh-smtp'));
    182370    } else {
     371        update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'failed', false);
    183372        $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(
    187375                /* translators: %s: wp_mail error message */
    188376                esc_html__('Error: %s', 'mesh-smtp'),
    189377                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');
    192380        meshsmtp_set_notice('error', $text);
    193381    }
     
    265453 */
    266454function 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';
    268458    ?>
    269459    <div class="<?php echo esc_attr($class); ?>">
     
    281471    }
    282472
    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');
    286481    ?>
    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>
    294487            </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>
    295498
    296499        <?php
     
    301504            meshsmtp_render_notice($notice['type'], $notice['message']);
    302505        }
     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        }
    303509        ?>
    304510
    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">
    313520                            <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>
    328531                        </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">
    330549                        <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>
    333556                        </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 />
    348560                        </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">
    350577                        <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 />
    353580                        </div>
    354 
    355581                        <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 />
    358584                        </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>
    389613
    390614                <hr />
  • mesh-smtp/tags/1.2.4/assets/admin.css

    r3481868 r3482419  
    1616
    1717.meshsmtp-hero {
    18   align-items: center;
     18  align-items: flex-start;
    1919  background: linear-gradient(120deg, #0f172a, #0b4f6c 52%, #0d9488);
    2020  border-radius: 14px;
     
    2222  display: flex;
    2323  justify-content: space-between;
    24   margin-bottom: 20px;
    25   padding: 18px 22px;
     24  margin-bottom: 16px;
     25  padding: 14px 18px;
    2626}
    2727
    2828.meshsmtp-hero h1 {
    2929  color: #ffffff;
    30   font-size: 24px;
     30  font-size: 22px;
    3131  line-height: 1.2;
    3232  margin: 0;
     
    3535.meshsmtp-hero p {
    3636  color: #cbd5e1;
    37   margin: 8px 0 0;
     37  font-size: 13px;
     38  line-height: 1.5;
     39  margin: 4px 0 0;
    3840}
    3941
     
    4244  border: 1px solid rgba(255, 255, 255, 0.35);
    4345  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;
    4662}
    4763
     
    7187
    7288.meshsmtp-card p,
    73 .meshsmtp-card li {
     89.meshsmtp-card li,
     90.meshsmtp-field .description {
    7491  color: var(--meshsmtp-muted);
    7592}
    7693
    7794.meshsmtp-field {
    78   margin-bottom: 14px;
     95  margin-bottom: 12px;
    7996}
    8097
     
    103120}
    104121
     122.meshsmtp-field input:disabled,
     123.meshsmtp-field select:disabled {
     124  background: #f8fafc;
     125  color: #94a3b8;
     126  cursor: not-allowed;
     127}
     128
    105129.meshsmtp-field-inline {
    106130  margin-bottom: 16px;
    107131}
    108132
     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
    109147.meshsmtp-dual {
    110   column-gap: 14px;
    111148  display: grid;
    112   grid-template-columns: repeat(2, minmax(0, 1fr));
     149  gap: 12px;
     150  grid-template-columns: 1fr;
    113151}
    114152
     
    129167}
    130168
     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
    131203.meshsmtp-primary,
    132204.meshsmtp-secondary {
     
    146218.meshsmtp-primary:focus {
    147219  background: linear-gradient(120deg, #0284c7, #0369a1);
     220  border-color: #0369a1;
    148221  color: #ffffff;
    149222}
     
    174247}
    175248
     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
    176306@media (max-width: 1100px) {
    177   .meshsmtp-grid {
     307  .meshsmtp-grid,
     308  .meshsmtp-statusbar {
    178309    grid-template-columns: 1fr;
    179310  }
     
    187318
    188319  .meshsmtp-hero {
    189     align-items: flex-start;
    190320    flex-direction: column;
    191321    gap: 10px;
    192322  }
    193323
    194   .meshsmtp-dual {
     324  .meshsmtp-preset-row {
     325    align-items: stretch;
     326    flex-direction: column;
     327  }
     328
     329  .meshsmtp-password-wrap {
    195330    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  
    66  const secureField = document.getElementById('meshsmtp-secure');
    77  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;
    1219
    1320  const presets = {
     
    2128  };
    2229
    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];
    2577    if (!selected) {
     78      applyProviderHelp();
    2679      return;
    2780    }
     
    3083    secureField.value = selected.secure;
    3184    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') {
    36104      portField.value = '465';
    37105      return;
    38106    }
    39107
    40     if (this.value === 'tls') {
     108    if (secureField.value === 'tls') {
    41109      portField.value = '587';
    42110      return;
     
    44112
    45113    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    }
    46462  });
    47463})();
  • mesh-smtp/tags/1.2.4/mesh-smtp.php

    r3481868 r3482419  
    33 * Plugin Name: Mesh SMTP
    44 * 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.2
     5 * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI.
     6 * Version: 1.2.4
    77 * Author: Mesh Creation
    88 * Author URI: https://meshcreation.com
     
    1919}
    2020
    21 define('MESHSMTP_PLUGIN_VERSION', '1.2.2');
     21define('MESHSMTP_PLUGIN_VERSION', '1.2.4');
    2222define('MESHSMTP_PLUGIN_FILE', __FILE__);
    2323define('MESHSMTP_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    2626define('MESHSMTP_LEGACY_OPTION_KEY', 'msm_settings');
    2727define('MESHSMTP_LAST_TEST_OPTION', 'meshsmtp_last_test_email');
     28define('MESHSMTP_LAST_TEST_STATUS_OPTION', 'meshsmtp_last_test_status');
     29define('MESHSMTP_LAST_TEST_TIME_OPTION', 'meshsmtp_last_test_time');
    2830define('MESHSMTP_LEGACY_LAST_TEST_OPTION', 'msm_last_test_email');
    2931define('MESHSMTP_MIGRATION_FLAG_OPTION', 'meshsmtp_legacy_migration_complete');
     
    147149
    148150    $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;
    157159
    158160    if (is_email($settings['from_email'])) {
  • mesh-smtp/tags/1.2.4/readme.txt

    r3481868 r3482419  
    66Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 1.2.2
     8Stable tag: 1.2.4
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2121
    2222* Simple SMTP configuration for host, port, encryption, username, and password
    23 * Provider presets for faster setup
     23* Provider presets with auto-detect help for known email domains, SMTP hosts, and site mail-routing hints
    2424* Test email tool with clear success and error feedback
     25* Last test status and timestamp summary
     26* Better setup hints for popular providers
    2527* Clean and modern admin UI
    2628* Capability checks, nonce verification, and sanitized settings handling
    2729* Passwords are not shown back in the UI after saving
     30* Saved SMTP details stay intact when you temporarily disable the SMTP override
    2831* Works with WordPress emails sent through `wp_mail()`
    2932
     
    53562. Activate the plugin through the **Plugins** screen in WordPress.
    54573. Open **Mesh SMTP** from the WordPress admin menu.
    55 4. Enter your SMTP details manually or choose a supported provider preset.
     584. Enter your SMTP details manually, use Auto Detect, or choose a supported provider preset.
    56595. Save the settings.
    57606. Use the **Connection Test** section to send a test email.
     
    8790== Changelog ==
    8891
     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
    89108= 1.2.2 =
    90109* Fixed settings page behavior for the updated WordPress.org review build
     
    111130== Upgrade Notice ==
    112131
     132= 1.2.4 =
     133Recommended update for safer SMTP toggling, provider auto-detect, and cleaner WordPress.org submission behavior.
     134
     135= 1.2.3 =
     136Recommended update for a cleaner settings screen and more helpful SMTP diagnostics.
     137
    113138= 1.2.2 =
    114139Recommended update for improved settings page reliability and compatibility metadata.
  • mesh-smtp/tags/1.2.4/uninstall.php

    r3481868 r3482419  
    1414    'msm_settings',
    1515    'meshsmtp_last_test_email',
     16    'meshsmtp_last_test_status',
     17    'meshsmtp_last_test_time',
    1618    'msm_last_test_email',
    1719    'meshsmtp_legacy_migration_complete',
  • mesh-smtp/trunk/admin/settings-page.php

    r3481868 r3482419  
    1010add_action('admin_post_meshsmtp_send_test_email', 'meshsmtp_handle_test_email');
    1111add_action('wp_mail_failed', 'meshsmtp_capture_mail_error');
     12add_action('wp_ajax_meshsmtp_detect_provider_hint', 'meshsmtp_ajax_detect_provider_hint');
    1213
    1314/**
     
    6566        true
    6667    );
     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    );
    6777}
    6878
     
    8191    }
    8292
    83     $host = '';
     93    $host = (string) $existing['host'];
    8494    if (isset($input['host'])) {
    8595        $host = sanitize_text_field(wp_unslash((string) $input['host']));
     
    8797    }
    8898
    89     $port = isset($input['port']) ? absint($input['port']) : (int) $defaults['port'];
     99    $port = isset($input['port']) ? absint($input['port']) : (int) $existing['port'];
    90100    if ($port < 1 || $port > 65535) {
    91101        $port = (int) $defaults['port'];
    92102    }
    93103
    94     $username = '';
     104    $username = (string) $existing['username'];
    95105    if (isset($input['username'])) {
    96106        $username = sanitize_text_field(wp_unslash((string) $input['username']));
     
    104114    }
    105115
    106     $from_email = '';
     116    $from_email = (string) $existing['from_email'];
    107117    if (isset($input['from_email'])) {
    108118        $from_email = sanitize_email(wp_unslash((string) $input['from_email']));
     
    112122    }
    113123
    114     $from_name = '';
     124    $from_name = (string) $existing['from_name'];
    115125    if (isset($input['from_name'])) {
    116126        $from_name = sanitize_text_field(wp_unslash((string) $input['from_name']));
     
    120130    }
    121131
    122     $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $defaults['secure'];
     132    $secure = isset($input['secure']) ? sanitize_key($input['secure']) : (string) $existing['secure'];
    123133    if (!in_array($secure, array('none', 'ssl', 'tls'), true)) {
    124134        $secure = (string) $defaults['secure'];
     
    138148
    139149/**
     150 * Map raw mail errors into clearer hints.
     151 *
     152 * @param string $error_message Raw mail error message.
     153 * @return string
     154 */
     155function 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 */
     183function 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 */
     198function 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 */
     218function 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 */
     270function 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/**
    140323 * Handle secure send-test-email action.
    141324 */
     
    156339    }
    157340
    158     update_option(MESHSMTP_LAST_TEST_OPTION, $test_to);
     341    update_option(MESHSMTP_LAST_TEST_OPTION, $test_to, false);
    159342    delete_transient(meshsmtp_mail_error_key());
    160343
     
    167350    $message = sprintf(
    168351        /* 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
     354Sent at: %s", 'mesh-smtp'),
    170355        wp_date('Y-m-d H:i:s')
    171356    );
     
    178363    );
    179364
     365    update_option(MESHSMTP_LAST_TEST_TIME_OPTION, time(), false);
     366
    180367    if ($sent) {
     368        update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'success', false);
    181369        meshsmtp_set_notice('success', esc_html__('Test email sent successfully.', 'mesh-smtp'));
    182370    } else {
     371        update_option(MESHSMTP_LAST_TEST_STATUS_OPTION, 'failed', false);
    183372        $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(
    187375                /* translators: %s: wp_mail error message */
    188376                esc_html__('Error: %s', 'mesh-smtp'),
    189377                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');
    192380        meshsmtp_set_notice('error', $text);
    193381    }
     
    265453 */
    266454function 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';
    268458    ?>
    269459    <div class="<?php echo esc_attr($class); ?>">
     
    281471    }
    282472
    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');
    286481    ?>
    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>
    294487            </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>
    295498
    296499        <?php
     
    301504            meshsmtp_render_notice($notice['type'], $notice['message']);
    302505        }
     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        }
    303509        ?>
    304510
    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">
    313520                            <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>
    328531                        </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">
    330549                        <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>
    333556                        </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 />
    348560                        </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">
    350577                        <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 />
    353580                        </div>
    354 
    355581                        <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 />
    358584                        </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>
    389613
    390614                <hr />
  • mesh-smtp/trunk/assets/admin.css

    r3481868 r3482419  
    1616
    1717.meshsmtp-hero {
    18   align-items: center;
     18  align-items: flex-start;
    1919  background: linear-gradient(120deg, #0f172a, #0b4f6c 52%, #0d9488);
    2020  border-radius: 14px;
     
    2222  display: flex;
    2323  justify-content: space-between;
    24   margin-bottom: 20px;
    25   padding: 18px 22px;
     24  margin-bottom: 16px;
     25  padding: 14px 18px;
    2626}
    2727
    2828.meshsmtp-hero h1 {
    2929  color: #ffffff;
    30   font-size: 24px;
     30  font-size: 22px;
    3131  line-height: 1.2;
    3232  margin: 0;
     
    3535.meshsmtp-hero p {
    3636  color: #cbd5e1;
    37   margin: 8px 0 0;
     37  font-size: 13px;
     38  line-height: 1.5;
     39  margin: 4px 0 0;
    3840}
    3941
     
    4244  border: 1px solid rgba(255, 255, 255, 0.35);
    4345  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;
    4662}
    4763
     
    7187
    7288.meshsmtp-card p,
    73 .meshsmtp-card li {
     89.meshsmtp-card li,
     90.meshsmtp-field .description {
    7491  color: var(--meshsmtp-muted);
    7592}
    7693
    7794.meshsmtp-field {
    78   margin-bottom: 14px;
     95  margin-bottom: 12px;
    7996}
    8097
     
    103120}
    104121
     122.meshsmtp-field input:disabled,
     123.meshsmtp-field select:disabled {
     124  background: #f8fafc;
     125  color: #94a3b8;
     126  cursor: not-allowed;
     127}
     128
    105129.meshsmtp-field-inline {
    106130  margin-bottom: 16px;
    107131}
    108132
     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
    109147.meshsmtp-dual {
    110   column-gap: 14px;
    111148  display: grid;
    112   grid-template-columns: repeat(2, minmax(0, 1fr));
     149  gap: 12px;
     150  grid-template-columns: 1fr;
    113151}
    114152
     
    129167}
    130168
     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
    131203.meshsmtp-primary,
    132204.meshsmtp-secondary {
     
    146218.meshsmtp-primary:focus {
    147219  background: linear-gradient(120deg, #0284c7, #0369a1);
     220  border-color: #0369a1;
    148221  color: #ffffff;
    149222}
     
    174247}
    175248
     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
    176306@media (max-width: 1100px) {
    177   .meshsmtp-grid {
     307  .meshsmtp-grid,
     308  .meshsmtp-statusbar {
    178309    grid-template-columns: 1fr;
    179310  }
     
    187318
    188319  .meshsmtp-hero {
    189     align-items: flex-start;
    190320    flex-direction: column;
    191321    gap: 10px;
    192322  }
    193323
    194   .meshsmtp-dual {
     324  .meshsmtp-preset-row {
     325    align-items: stretch;
     326    flex-direction: column;
     327  }
     328
     329  .meshsmtp-password-wrap {
    195330    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  
    66  const secureField = document.getElementById('meshsmtp-secure');
    77  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;
    1219
    1320  const presets = {
     
    2128  };
    2229
    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];
    2577    if (!selected) {
     78      applyProviderHelp();
    2679      return;
    2780    }
     
    3083    secureField.value = selected.secure;
    3184    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') {
    36104      portField.value = '465';
    37105      return;
    38106    }
    39107
    40     if (this.value === 'tls') {
     108    if (secureField.value === 'tls') {
    41109      portField.value = '587';
    42110      return;
     
    44112
    45113    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    }
    46462  });
    47463})();
  • mesh-smtp/trunk/mesh-smtp.php

    r3481868 r3482419  
    33 * Plugin Name: Mesh SMTP
    44 * 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.2
     5 * Description: Lightweight, secure SMTP configuration with provider presets, test mail support, and a modern admin UI.
     6 * Version: 1.2.4
    77 * Author: Mesh Creation
    88 * Author URI: https://meshcreation.com
     
    1919}
    2020
    21 define('MESHSMTP_PLUGIN_VERSION', '1.2.2');
     21define('MESHSMTP_PLUGIN_VERSION', '1.2.4');
    2222define('MESHSMTP_PLUGIN_FILE', __FILE__);
    2323define('MESHSMTP_PLUGIN_DIR', plugin_dir_path(__FILE__));
     
    2626define('MESHSMTP_LEGACY_OPTION_KEY', 'msm_settings');
    2727define('MESHSMTP_LAST_TEST_OPTION', 'meshsmtp_last_test_email');
     28define('MESHSMTP_LAST_TEST_STATUS_OPTION', 'meshsmtp_last_test_status');
     29define('MESHSMTP_LAST_TEST_TIME_OPTION', 'meshsmtp_last_test_time');
    2830define('MESHSMTP_LEGACY_LAST_TEST_OPTION', 'msm_last_test_email');
    2931define('MESHSMTP_MIGRATION_FLAG_OPTION', 'meshsmtp_legacy_migration_complete');
     
    147149
    148150    $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;
    157159
    158160    if (is_email($settings['from_email'])) {
  • mesh-smtp/trunk/readme.txt

    r3481868 r3482419  
    66Tested up to: 6.9
    77Requires PHP: 7.4
    8 Stable tag: 1.2.2
     8Stable tag: 1.2.4
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2121
    2222* Simple SMTP configuration for host, port, encryption, username, and password
    23 * Provider presets for faster setup
     23* Provider presets with auto-detect help for known email domains, SMTP hosts, and site mail-routing hints
    2424* Test email tool with clear success and error feedback
     25* Last test status and timestamp summary
     26* Better setup hints for popular providers
    2527* Clean and modern admin UI
    2628* Capability checks, nonce verification, and sanitized settings handling
    2729* Passwords are not shown back in the UI after saving
     30* Saved SMTP details stay intact when you temporarily disable the SMTP override
    2831* Works with WordPress emails sent through `wp_mail()`
    2932
     
    53562. Activate the plugin through the **Plugins** screen in WordPress.
    54573. Open **Mesh SMTP** from the WordPress admin menu.
    55 4. Enter your SMTP details manually or choose a supported provider preset.
     584. Enter your SMTP details manually, use Auto Detect, or choose a supported provider preset.
    56595. Save the settings.
    57606. Use the **Connection Test** section to send a test email.
     
    8790== Changelog ==
    8891
     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
    89108= 1.2.2 =
    90109* Fixed settings page behavior for the updated WordPress.org review build
     
    111130== Upgrade Notice ==
    112131
     132= 1.2.4 =
     133Recommended update for safer SMTP toggling, provider auto-detect, and cleaner WordPress.org submission behavior.
     134
     135= 1.2.3 =
     136Recommended update for a cleaner settings screen and more helpful SMTP diagnostics.
     137
    113138= 1.2.2 =
    114139Recommended update for improved settings page reliability and compatibility metadata.
  • mesh-smtp/trunk/uninstall.php

    r3481868 r3482419  
    1414    'msm_settings',
    1515    'meshsmtp_last_test_email',
     16    'meshsmtp_last_test_status',
     17    'meshsmtp_last_test_time',
    1618    'msm_last_test_email',
    1719    'meshsmtp_legacy_migration_complete',
Note: See TracChangeset for help on using the changeset viewer.