updated plugin WP Mail SMTP version 2.5.0

This commit is contained in:
2020-10-23 01:19:42 +00:00
committed by Gitium
parent 1047e0b29f
commit 51360a4729
205 changed files with 36345 additions and 921 deletions

View File

@ -71,6 +71,9 @@ class Area {
// Add the options page.
add_action( 'admin_menu', [ $this, 'add_admin_options_page' ] );
// Add WPMS network-wide setting page for product education.
add_action( 'network_admin_menu', [ $this, 'add_wpms_network_wide_setting_product_education_page' ] );
// Register on load Email Log admin menu hook.
add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-logs', [ $this, 'maybe_redirect_email_log_menu_to_email_log_settings_tab' ] );
@ -238,6 +241,97 @@ class Area {
}
}
/**
* Add network admin settings page for the WPMS product education.
*
* @since 2.5.0
*/
public function add_wpms_network_wide_setting_product_education_page() {
add_menu_page(
esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ),
esc_html__( 'WP Mail SMTP', 'wp-mail-smtp' ),
'manage_options',
self::SLUG,
[ $this, 'display_network_product_education_page' ],
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiM5ZWEzYTgiIHdpZHRoPSI2NCIgaGVpZ2h0PSI2NCIgdmlld0JveD0iMCAwIDQzIDM0Ij48cGF0aCBkPSJNMC4wMDcsMy41ODVWMjAuNDIxcTAsMy41ODYsMy43NTEsMy41ODVMMjAsMjRWMTlIMzBWMTQuMDE0bDAuOTkxLTFMMzQsMTNWMy41ODVRMzQsMCwzMC4yNDksMEgzLjc1OFEwLjAwNywwLC4wMDcsMy41ODVoMFpNMy41MjQsNi4xNTdhMS40OSwxLjQ5LDAsMCwxLS41MDgtMC45MzUsMS41ODEsMS41ODEsMCwwLDEsLjI3NC0xLjIwOCwxLjQ0OSwxLjQ0OSwwLDAsMSwxLjA5NC0uNjYzLDEuNzU2LDEuNzU2LDAsMCwxLDEuMjUuMzEybDExLjQwOSw3LjcxNkwyOC4zNzQsMy42NjNhMS45NiwxLjk2LDAsMCwxLDEuMjg5LS4zMTIsMS41NDYsMS41NDYsMCwwLDEsMS4wOTQuNjYzLDEuNCwxLjQsMCwwLDEsLjI3MywxLjIwOCwxLjY3LDEuNjcsMCwwLDEtLjU0Ny45MzVMMTcuMDQzLDE3LjIyNVoiLz48cGF0aCBkPSJNMjIsMjhIMzJsLTAuMDA5LDQuNjI0YTEuMTI2LDEuMTI2LDAsMCwwLDEuOTIyLjhsOC4yNS04LjIzNmExLjEyNiwxLjEyNiwwLDAsMCwwLTEuNTk0bC04LjI1LTguMjQxYTEuMTI2LDEuMTI2LDAsMCwwLTEuOTIyLjh2NC44NjZMMjIsMjF2N1oiLz48L3N2Zz4=',
98
);
}
/**
* HTML output for the network admin settings page (for the WPMS product education).
*
* @since 2.5.0
*/
public function display_network_product_education_page() {
// Skip if not on multisite and not on network admin site.
if ( ! is_multisite() || ! is_network_admin() ) {
return;
}
?>
<div class="wrap" id="wp-mail-smtp">
<div class="wp-mail-smtp-page wp-mail-smtp-page-general wp-mail-smtp-page-nw-product-edu wp-mail-smtp-tab-settings">
<div class="wp-mail-smtp-page-title">
<a href="#" class="tab active">
<?php esc_html_e( 'General', 'wp-mail-smtp' ); ?>
</a>
</div>
<div class="wp-mail-smtp-page-content">
<h1 class="screen-reader-text">
<?php esc_html_e( 'General', 'wp-mail-smtp' ); ?>
</h1>
<?php do_action( 'wp_mail_smtp_admin_pages_before_content' ); ?>
<!-- Multisite Section Title -->
<div class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-content wp-mail-smtp-clear section-heading no-desc" id="wp-mail-smtp-setting-row-multisite-heading">
<div class="wp-mail-smtp-setting-field">
<h2><?php esc_html_e( 'Multisite', 'wp-mail-smtp' ); ?></h2>
<img src="<?php echo esc_url( wp_mail_smtp()->assets_url . '/images/pro-badge.svg' ); ?>" class="badge" alt="<?php esc_attr_e( 'Pro+ badge icon', 'wp-mail-smtp' ); ?>">
</div>
<p>
<?php esc_html_e( 'Simply enable network-wide settings and every site on your network will inherit the same SMTP settings. Save time and only configure your SMTP provider once.', 'wp-amil-smtp' ); ?>
</p>
</div>
<!-- Network wide setting -->
<div id="wp-mail-smtp-setting-row-multisite" class="wp-mail-smtp-setting-row wp-mail-smtp-setting-row-multisite wp-mail-smtp-clear">
<div class="wp-mail-smtp-setting-label">
<label for="wp-mail-smtp-setting-multisite-settings-control"><?php esc_html_e( 'Settings control', 'wp-mail-smtp' ); ?></label>
</div>
<div class="wp-mail-smtp-setting-field">
<input name="wp-mail-smtp[general][nw_product_edu]" type="checkbox" value="true" id="wp-mail-smtp-setting-nw-product-edu" disabled>
<label for="wp-mail-smtp-setting-nw-product-edu">
<?php esc_html_e( 'Make the plugin settings global network-wide', 'wp-mail-smtp' ); ?>
</label>
<p class="desc">
<?php esc_html_e( 'If disabled, each subsite of this multisite will have its own WP Mail SMTP settings page that has to be configured separately.', 'wp-mail-smtp' ); ?>
<br>
<?php esc_html_e( 'If enabled, these global settings will manage email sending for all subsites of this multisite.', 'wp-mail-smtp' ); ?>
</p>
</div>
</div>
<div class="wp-mail-smtp-setting-row-no-setting">
<a href="<?php echo esc_url( wp_mail_smtp()->get_upgrade_link( [ 'medium' => 'network-settings', 'content' => '' ] ) ); // phpcs:ignore ?>" target="_blank" rel="noopener noreferrer" class="wp-mail-smtp-btn wp-mail-smtp-btn-lg wp-mail-smtp-btn-orange">
<?php esc_html_e( 'Upgrade to WP Mail SMTP Pro', 'wp-mail-smtp' ); ?>
</a>
</div>
</div>
</div>
</div>
<?php
}
/**
* Redirect the "Email Log" WP menu link to the "Email Log" setting tab for lite version of the plugin.
*

View File

@ -248,10 +248,8 @@ class MiscTab extends PageAbstract {
$data['general'][ UsageTracking::SETTINGS_SLUG ] = false;
}
$to_save = Options::array_merge_recursive( $options->get_all(), $data );
// All the sanitization is done there.
$options->set( $to_save );
$options->set( $data, false, false );
WP::add_admin_notice(
esc_html__( 'Settings were successfully saved.', 'wp-mail-smtp' ),

View File

@ -549,13 +549,23 @@ class SettingsTab extends PageAbstract {
$data['smtp']['auth'] = false;
}
// Remove all debug messages when switching mailers.
// When switching mailers.
if (
! empty( $old_opt['mail']['mailer'] ) &&
! empty( $data['mail']['mailer'] ) &&
$old_opt['mail']['mailer'] !== $data['mail']['mailer']
) {
// Remove all debug messages when switching mailers.
Debug::clear();
// Save correct from email address if Zoho or Outlook mailers are already configured.
if (
in_array( $data['mail']['mailer'], [ 'zoho', 'outlook' ], true ) &&
! empty( $old_opt[ $data['mail']['mailer'] ]['user_details']['email'] )
) {
$data['mail']['from_email'] = $old_opt[ $data['mail']['mailer'] ]['user_details']['email'];
}
}
$to_redirect = false;
@ -582,11 +592,8 @@ class SettingsTab extends PageAbstract {
$data = apply_filters( 'wp_mail_smtp_settings_tab_process_post', $data );
// New gmail clients data will be added from new $data.
$to_save = Options::array_merge_recursive( $old_opt, $data );
// All the sanitization is done in Options class.
$options->set( $to_save );
$options->set( $data, false, false );
if ( $to_redirect ) {
wp_redirect( $_POST['_wp_http_referer'] . '#wp-mail-smtp-setting-row-gmail-authorize' );

View File

@ -824,11 +824,9 @@ Lead Developer, WP Mail SMTP';
'description' => [
'<strong>' . esc_html__( 'Google API Error.', 'wp-mail-smtp' ) . '</strong>',
esc_html__( 'Unfortunately, this error can be due to many different reasons.', 'wp-mail-smtp' ),
],
'steps' => [
sprintf(
wp_kses( /* translators: %s - Blog article URL. */
__( 'Please <a href="%s" target="_blank" rel="noopener noreferrer">read this article</a> to learn more about what can cause this error and how it can be resolved.', 'wp-mail-smtp' ),
__( 'Please <a href="%s" target="_blank" rel="noopener noreferrer">read this article</a> to learn more about what can cause this error and follow the steps below.', 'wp-mail-smtp' ),
[
'a' => [
'href' => [],
@ -840,6 +838,10 @@ Lead Developer, WP Mail SMTP';
'https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35'
),
],
'steps' => [
esc_html__( 'Go to WP Mail SMTP plugin settings page. Click the “Remove Connection” button.', 'wp-mail-smtp' ),
esc_html__( 'Then click the “Allow plugin to send emails using your Google account” button and re-enable access.', 'wp-mail-smtp' ),
],
],
// [gmail] - Code was already redeemed.
[

View File

@ -0,0 +1,162 @@
<?php
namespace WPMailSMTP\Helpers;
// WP 5.2+ already load Sodium Compat polyfill for libsodium-fallback.
// We need to do the same for under 5.2 versions (4.9-5.1).
if ( ! version_compare( get_bloginfo( 'version' ), '5.2', '>=' ) && ! function_exists( 'sodium_crypto_box' ) ) {
require_once dirname( WPMS_PLUGIN_FILE ) . '/vendor/paragonie/sodium_compat/autoload.php';
}
/**
* Class for encryption functionality.
*
* @since 2.5.0
*
* @link https://www.php.net/manual/en/intro.sodium.php
*/
class Crypto {
/**
* Get a secret key for encrypt/decrypt.
*
* @since 2.5.0
*
* @param bool $create Should the key be created, if it does not exist yet.
*
* @return string|bool
*/
public static function get_secret_key( $create = false ) {
if ( defined( 'WPMS_CRYPTO_KEY' ) ) {
return WPMS_CRYPTO_KEY;
}
$secret_key = get_option( 'wp_mail_smtp_mail_key' );
// If we already have the secret, send it back.
if ( false !== $secret_key ) {
return base64_decode( $secret_key ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
}
if ( $create ) {
// We don't have a secret, so let's generate one.
try {
$secret_key = sodium_crypto_secretbox_keygen(); // phpcs:ignore
} catch ( \Exception $e ) {
$secret_key = wp_generate_password( SODIUM_CRYPTO_SECRETBOX_KEYBYTES ); // phpcs:ignore
}
add_option( 'wp_mail_smtp_mail_key', base64_encode( $secret_key ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
return $secret_key;
}
return false;
}
/**
* Encrypt a message.
*
* @since 2.5.0
*
* @param string $message Message to encrypt.
* @param string $key Encryption key.
*
* @return string
* @throws \Exception The exception object.
*/
public static function encrypt( $message, $key = '' ) {
if ( apply_filters( 'wp_mail_smtp_helpers_crypto_stop', false ) ) {
return $message;
}
// Create a nonce for this operation. It will be stored and recovered in the message itself.
$nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); // phpcs:ignore
if ( empty( $key ) ) {
$key = self::get_secret_key( true );
}
// Encrypt message and combine with nonce.
$cipher = base64_encode( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$nonce .
sodium_crypto_secretbox( // phpcs:ignore
$message,
$nonce,
$key
)
);
try {
sodium_memzero( $message ); // phpcs:ignore
sodium_memzero( $key ); // phpcs:ignore
} catch ( \Exception $e ) {
return $cipher;
}
return $cipher;
}
/**
* Decrypt a message.
* Returns encrypted message on any failure and the decrypted message on success.
*
* @since 2.5.0
*
* @param string $encrypted Encrypted message.
* @param string $key Encryption key.
*
* @return string
* @throws \Exception The exception object.
*/
public static function decrypt( $encrypted, $key = '' ) {
if ( apply_filters( 'wp_mail_smtp_helpers_crypto_stop', false ) ) {
return $encrypted;
}
// Unpack base64 message.
$decoded = base64_decode( $encrypted ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
if ( false === $decoded ) {
return $encrypted;
}
if ( mb_strlen( $decoded, '8bit' ) < ( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES ) ) { // phpcs:ignore
return $encrypted;
}
// Pull nonce and ciphertext out of unpacked message.
$nonce = mb_substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' ); // phpcs:ignore
$ciphertext = mb_substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' ); // phpcs:ignore
$key = empty( $key ) ? self::get_secret_key() : $key;
if ( empty( $key ) ) {
return $encrypted;
}
// Decrypt it.
$message = sodium_crypto_secretbox_open( // phpcs:ignore
$ciphertext,
$nonce,
$key
);
// Check for decryption failures.
if ( false === $message ) {
return $encrypted;
}
try {
sodium_memzero( $ciphertext ); // phpcs:ignore
sodium_memzero( $key ); // phpcs:ignore
} catch ( \Exception $e ) {
return $message;
}
return $message;
}
}

View File

@ -315,7 +315,7 @@ class Migration {
$converted['mail']['return_path'] = ( $old_value === 'true' );
break;
case 'mailer':
$converted['mail']['mailer'] = $old_value;
$converted['mail']['mailer'] = ! empty( $old_value ) ? $old_value : 'mail';
break;
case 'wp_mail_smtp_am_notifications_hidden':
$converted['general']['am_notifications_hidden'] = ( isset( $old_value ) && $old_value === 'true' );

View File

@ -2,6 +2,7 @@
namespace WPMailSMTP;
use WPMailSMTP\Helpers\Crypto;
use WPMailSMTP\UsageTracking\UsageTracking;
/**
@ -244,13 +245,15 @@ class Options {
* Options::init()->get( 'smtp', 'host' ) - will return only SMTP 'host' option.
*
* @since 1.0.0
* @since 2.5.0 Added $strip_slashes method parameter.
*
* @param string $group
* @param string $key
* @param string $group The option group.
* @param string $key The option key.
* @param bool $strip_slashes If the slashes should be stripped from string values.
*
* @return mixed|null Null if value doesn't exist anywhere: in constants, in DB, in a map. So it's completely custom or a typo.
*/
public function get( $group, $key ) {
public function get( $group, $key, $strip_slashes = true ) {
// Just to feel safe.
$group = sanitize_key( $group );
@ -266,7 +269,7 @@ class Options {
if ( isset( $this->_options[ $group ] ) ) {
// Get the options key of a group.
if ( isset( $this->_options[ $group ][ $key ] ) ) {
$value = $this->_options[ $group ][ $key ];
$value = $this->get_existing_option_value( $group, $key );
} else {
$value = $this->postprocess_key_defaults( $group, $key );
}
@ -284,14 +287,37 @@ class Options {
}
}
// Strip slashes only from values saved in DB. Constants should be processed as is.
if ( is_string( $value ) && ! $this->is_const_defined( $group, $key ) ) {
// Conditionally strip slashes only from values saved in DB. Constants should be processed as is.
if ( $strip_slashes && is_string( $value ) && ! $this->is_const_defined( $group, $key ) ) {
$value = stripslashes( $value );
}
return apply_filters( 'wp_mail_smtp_options_get', $value, $group, $key );
}
/**
* Get the existing cached option value.
*
* @since 2.5.0
*
* @param string $group The options group.
* @param string $key The options key.
*
* @return mixed
*/
private function get_existing_option_value( $group, $key ) {
if ( $group === 'smtp' && $key === 'pass' ) {
try {
return Crypto::decrypt( $this->_options[ $group ][ $key ] );
} catch ( \Exception $e ) {
return $this->_options[ $group ][ $key ];
}
}
return $this->_options[ $group ][ $key ];
}
/**
* Some options may be non-empty by default,
* so we need to postprocess them to convert.
@ -823,14 +849,47 @@ class Options {
* @since 1.3.0 Added $once argument to save options only if they don't exist already.
* @since 1.4.0 Added Mailgun:region.
* @since 1.5.0 Added Outlook/AmazonSES, Email Log. Stop saving const values into DB.
* @since 2.5.0 Added $overwrite_existing method parameter.
*
* @param array $options Plugin options to save.
* @param bool $once Whether to update existing options or to add these options only once.
* @param array $options Plugin options to save.
* @param bool $once Whether to update existing options or to add these options only once.
* @param bool $overwrite_existing Whether to overwrite existing settings or merge these passed options with existing ones.
*/
public function set( $options, $once = false ) {
/*
* Process generic options.
*/
public function set( $options, $once = false, $overwrite_existing = true ) {
// Merge existing settings with new values.
if ( ! $overwrite_existing ) {
$options = self::array_merge_recursive( $this->get_all_raw(), $options );
}
$options = $this->process_generic_options( $options );
$options = $this->process_mailer_specific_options( $options );
$options = apply_filters( 'wp_mail_smtp_options_set', $options );
// Whether to update existing options or to add these options only once if they don't exist yet.
if ( $once ) {
add_option( self::META_KEY, $options, '', 'no' ); // Do not autoload these options.
} else {
update_option( self::META_KEY, $options, 'no' );
}
// Now we need to re-cache values.
$this->populate_options();
do_action( 'wp_mail_smtp_options_set_after', $options );
}
/**
* Process the generic plugin options.
*
* @since 2.5.0
*
* @param array $options The options array.
*
* @return array
*/
private function process_generic_options( $options ) { // phpcs:ignore
foreach ( (array) $options as $group => $keys ) {
foreach ( $keys as $option_name => $option_value ) {
switch ( $group ) {
@ -871,9 +930,20 @@ class Options {
}
}
/*
* Process mailers-specific options.
*/
return $options;
}
/**
* Process mailers-specific plugin options.
*
* @since 2.5.0
*
* @param array $options The options array.
*
* @return array
*/
private function process_mailer_specific_options( $options ) { // phpcs:ignore
if (
! empty( $options['mail']['mailer'] ) &&
isset( $options[ $options['mail']['mailer'] ] ) &&
@ -902,7 +972,14 @@ class Options {
case 'pass': // smtp.
// Do not process as they may contain certain special characters, but allow to be overwritten using constants.
$options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? '' : trim( (string) $option_value );
$option_value = trim( (string) $option_value );
$options[ $mailer ][ $option_name ] = $this->is_const_defined( $mailer, $option_name ) ? '' : $option_value;
if ( $mailer === 'smtp' && ! $this->is_const_defined( 'smtp', 'pass' ) ) {
try {
$options[ $mailer ][ $option_name ] = Crypto::encrypt( $option_value );
} catch ( \Exception $e ) {} // phpcs:ignore
}
break;
case 'api_key': // mailgun/sendgrid/sendinblue/pepipostapi/smtpcom.
@ -923,19 +1000,7 @@ class Options {
}
}
$options = apply_filters( 'wp_mail_smtp_options_set', $options );
// Whether to update existing options or to add these options only once if they don't exist yet.
if ( $once ) {
add_option( self::META_KEY, $options, '', 'no' ); // Do not autoload these options.
} else {
update_option( self::META_KEY, $options, 'no' );
}
// Now we need to re-cache values.
$this->populate_options();
do_action( 'wp_mail_smtp_options_set_after', $options );
return $options;
}
/**
@ -1036,4 +1101,24 @@ class Options {
public function is_mailer_smtp() {
return apply_filters( 'wp_mail_smtp_options_is_mailer_smtp', in_array( $this->get( 'mail', 'mailer' ), array( 'pepipost', 'smtp' ), true ) );
}
/**
* Get all the options, but without stripping the slashes.
*
* @since 2.5.0
*
* @return array
*/
public function get_all_raw() {
$options = $this->_options;
foreach ( $options as $group => $g_value ) {
foreach ( $g_value as $key => $value ) {
$options[ $group ][ $key ] = $this->get( $group, $key, false );
}
}
return $options;
}
}

View File

@ -77,15 +77,18 @@ abstract class AuthAbstract implements AuthInterface {
protected function update_auth_code( $code ) {
$options = new PluginOptions();
$all = $options->get_all();
// To save in DB.
$all[ $this->mailer_slug ]['auth_code'] = $code;
$updated_settings = [
$this->mailer_slug => [
'auth_code' => $code,
],
];
// To save in currently retrieved options array.
$this->options['auth_code'] = $code;
$options->set( $all );
$options->set( $updated_settings, false, false );
}
/**
@ -98,15 +101,18 @@ abstract class AuthAbstract implements AuthInterface {
protected function update_access_token( $token ) {
$options = new PluginOptions();
$all = $options->get_all();
// To save in DB.
$all[ $this->mailer_slug ]['access_token'] = $token;
$updated_settings = [
$this->mailer_slug => [
'access_token' => $token,
],
];
// To save in currently retrieved options array.
$this->options['access_token'] = $token;
$options->set( $all );
$options->set( $updated_settings, false, false );
}
/**
@ -119,15 +125,18 @@ abstract class AuthAbstract implements AuthInterface {
protected function update_refresh_token( $token ) {
$options = new PluginOptions();
$all = $options->get_all();
// To save in DB.
$all[ $this->mailer_slug ]['refresh_token'] = $token;
$updated_settings = [
$this->mailer_slug => [
'refresh_token' => $token,
],
];
// To save in currently retrieved options array.
$this->options['refresh_token'] = $token;
$options->set( $all );
$options->set( $updated_settings, false, false );
}
/**

View File

@ -92,7 +92,7 @@ class Auth extends AuthAbstract {
'client_id' => $this->options['client_id'],
'client_secret' => $this->options['client_secret'],
'redirect_uris' => array(
self::get_plugin_auth_url(),
self::get_oauth_redirect_url(),
),
)
);
@ -102,7 +102,8 @@ class Auth extends AuthAbstract {
$client->setIncludeGrantedScopes( true );
// We request only the sending capability, as it's what we only need to do.
$client->setScopes( array( Google_Service_Gmail::MAIL_GOOGLE_COM ) );
$client->setRedirectUri( self::get_plugin_auth_url() );
$client->setRedirectUri( self::get_oauth_redirect_url() );
$client->setState( self::get_plugin_auth_url() );
// Apply custom options to the client.
$client = apply_filters( 'wp_mail_smtp_providers_gmail_auth_get_client_custom_options', $client );
@ -115,15 +116,22 @@ class Auth extends AuthAbstract {
$creds = $client->fetchAccessTokenWithAuthCode( $this->options['auth_code'] );
} catch ( \Exception $e ) {
$creds['error'] = $e->getMessage();
Debug::set(
'Mailer: Gmail' . "\r\n" .
$creds['error']
);
}
// Bail if we have an error.
if ( ! empty( $creds['error'] ) ) {
if ( $creds['error'] === 'invalid_client' ) {
$creds['error'] .= PHP_EOL . esc_html__( 'Please make sure your Google Client ID and Secret in the plugin settings are valid. Save the settings and try the Authorization again.' , 'wp-mail-smtp' );
}
Debug::set(
'Mailer: Gmail' . "\r\n" .
$creds['error']
);
return $client;
} else {
Debug::clear();
}
$this->update_access_token( $client->getAccessToken() );
@ -173,7 +181,7 @@ class Auth extends AuthAbstract {
*/
public function process() {
if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) {
if ( ! ( isset( $_GET['tab'] ) && $_GET['tab'] === 'auth' ) ) { // phpcs:ignore
wp_safe_redirect( wp_mail_smtp()->get_admin()->get_admin_page_url() );
exit;
}
@ -199,8 +207,8 @@ class Auth extends AuthAbstract {
$scope = '';
$error = '';
if ( isset( $_GET['error'] ) ) {
$error = sanitize_key( $_GET['error'] );
if ( isset( $_GET['error'] ) ) { // phpcs:ignore
$error = sanitize_key( $_GET['error'] ); // phpcs:ignore
}
// In case of any error: display a message to a user.
@ -215,11 +223,11 @@ class Auth extends AuthAbstract {
exit;
}
if ( isset( $_GET['code'] ) ) {
$code = $_GET['code'];
if ( isset( $_GET['code'] ) ) { // phpcs:ignore
$code = urldecode( $_GET['code'] ); // phpcs:ignore
}
if ( isset( $_GET['scope'] ) ) {
$scope = urldecode( $_GET['scope'] );
if ( isset( $_GET['scope'] ) ) { // phpcs:ignore
$scope = urldecode( base64_decode( $_GET['scope'] ) ); // phpcs:ignore
}
// Let's try to get the access token.
@ -328,4 +336,19 @@ class Auth extends AuthAbstract {
return $this->aliases;
}
/**
* Get the Google oAuth 2.0 redirect URL.
*
* This is the URL that Google will redirect after the access to the Gmail account is granted or rejected.
* The below endpoint will then redirect back to the user's WP site (to self::get_plugin_auth_url() URL).
*
* @since 2.5.0
*
* @return string
*/
public static function get_oauth_redirect_url() {
return 'https://connect.wpmailsmtp.com/google/';
}
}

View File

@ -110,6 +110,8 @@ class Mailer extends MailerAbstract {
$this->process_response( $response );
} catch ( \Exception $e ) {
$this->error_message = $e->getMessage();
Debug::set(
'Mailer: Gmail' . "\r\n" .
$this->process_exception_message( $e->getMessage() )
@ -131,6 +133,16 @@ class Mailer extends MailerAbstract {
$this->response = $response;
if ( ! method_exists( $this->response, 'getId' ) ) {
$this->error_message = esc_html__( 'The response object is invalid (missing getId method).', 'wp-mail-smtp' );
} else {
$message_id = $this->response->getId();
if ( empty( $message_id ) ) {
$this->error_message = esc_html__( 'The email message ID is missing.', 'wp-mail-smtp' );
}
}
do_action( 'wp_mail_smtp_providers_gmail_mailer_process_response', $this->response, $this->phpmailer );
}

View File

@ -118,7 +118,7 @@ class Options extends OptionsAbstract {
</div>
<div class="wp-mail-smtp-setting-field">
<input type="text" readonly="readonly" onfocus="this.select();"
value="<?php echo esc_attr( Auth::get_plugin_auth_url() ); ?>"
value="<?php echo esc_attr( Auth::get_oauth_redirect_url() ); ?>"
id="wp-mail-smtp-setting-<?php echo esc_attr( $this->get_slug() ); ?>-client_redirect"
/>
<button type="button" class="wp-mail-smtp-btn wp-mail-smtp-btn-md wp-mail-smtp-btn-light-grey wp-mail-smtp-setting-copy"
@ -245,7 +245,7 @@ class Options extends OptionsAbstract {
return;
}
$old_opt = $options->get_all();
$old_opt = $options->get_all_raw();
foreach ( $old_opt[ $this->get_slug() ] as $key => $value ) {
// Unset everything except Client ID and Secret.

View File

@ -23,18 +23,21 @@ abstract class MailerAbstract implements MailerInterface {
* @var int
*/
protected $email_sent_code = 200;
/**
* @since 1.0.0
*
* @var Options
*/
protected $options;
/**
* @since 1.0.0
*
* @var MailCatcherInterface
*/
protected $phpmailer;
/**
* @since 1.0.0
*
@ -50,18 +53,21 @@ abstract class MailerAbstract implements MailerInterface {
* @var string
*/
protected $url = '';
/**
* @since 1.0.0
*
* @var array
*/
protected $headers = array();
/**
* @since 1.0.0
*
* @var array
*/
protected $body = array();
/**
* @since 1.0.0
*
@ -69,6 +75,24 @@ abstract class MailerAbstract implements MailerInterface {
*/
protected $response = array();
/**
* The error message recorded when email sending failed and the error can't be processed from the API response.
*
* @since 2.5.0
*
* @var string
*/
protected $error_message = '';
/**
* Should the email sent by this mailer have its "sent status" verified via its API?
*
* @since 2.5.0
*
* @var bool
*/
protected $verify_sent_status = false;
/**
* Mailer constructor.
*
@ -269,7 +293,7 @@ abstract class MailerAbstract implements MailerInterface {
// Save the error text.
$errors = $response->get_error_messages();
foreach ( $errors as $error ) {
Debug::set( $error );
$this->error_message .= $error . PHP_EOL;
}
return;
@ -342,15 +366,17 @@ abstract class MailerAbstract implements MailerInterface {
}
/**
* The error message when email sending failed.
* Should be overwritten when appropriate.
*
* @since 1.2.0
* @since 2.5.0 Return a non-empty error_message attribute.
*
* @return string
*/
protected function get_response_error() {
public function get_response_error() {
return '';
return ! empty( $this->error_message ) ? $this->error_message : '';
}
/**
@ -461,4 +487,45 @@ abstract class MailerAbstract implements MailerInterface {
],
];
}
/**
* Should the email sent by this mailer have its "sent status" verified via its API?
*
* @since 2.5.0
*
* @return bool
*/
public function should_verify_sent_status() {
return $this->verify_sent_status;
}
/**
* Verify the "sent status" of the provided email log ID.
* The actual verification background task is triggered in the below action hook.
*
* @since 2.5.0
*
* @param int $email_log_id The ID of the email log.
*/
public function verify_sent_status( $email_log_id ) {
if ( ! $this->should_verify_sent_status() ) {
return;
}
do_action( 'wp_mail_smtp_providers_mailer_verify_sent_status', $email_log_id, $this );
}
/**
* Get the name/slug of the current mailer.
*
* @since 2.5.0
*
* @return string
*/
public function get_mailer_name() {
return $this->mailer;
}
}

View File

@ -366,6 +366,27 @@ class Mailer extends MailerAbstract {
);
}
/**
* We might need to do something after the email was sent to the API.
* In this method we preprocess the response from the API.
*
* @since 2.5.0
*
* @param mixed $response Response data.
*/
protected function process_response( $response ) {
parent::process_response( $response );
if (
! is_wp_error( $response ) &&
! empty( $this->response['body']->id )
) {
$this->phpmailer->MessageID = $this->response['body']->id;
$this->verify_sent_status = true;
}
}
/**
* Whether the email is sent or not.
* We basically check the response code from a request to provider.
@ -392,6 +413,8 @@ class Mailer extends MailerAbstract {
esc_html__( 'This could point to an incorrect Domain Name in the plugin settings.', 'wp-mail-smtp' ) . PHP_EOL .
esc_html__( 'Please check the WP Mail SMTP plugin settings and make sure the Mailgun Domain Name setting is correct.', 'wp-mail-smtp' );
$this->error_message = $message;
Debug::set( $message );
return false;
@ -407,7 +430,7 @@ class Mailer extends MailerAbstract {
*
* @return string
*/
protected function get_response_error() {
public function get_response_error() {
$body = (array) wp_remote_retrieve_body( $this->response );
@ -419,6 +442,8 @@ class Mailer extends MailerAbstract {
} else {
$error_text[] = \json_encode( $body['message'] );
}
} elseif ( ! empty( $this->error_message ) ) {
$error_text[] = $this->error_message;
} elseif ( ! empty( $body[0] ) ) {
if ( is_string( $body[0] ) ) {
$error_text[] = $body[0];

View File

@ -356,7 +356,7 @@ abstract class OptionsAbstract implements OptionsInterface {
id="wp-mail-smtp-setting-<?php echo esc_attr( $this->get_slug() ); ?>-pass" spellcheck="false" autocomplete="new-password"
/>
<p class="desc">
<?php esc_html_e( 'The password will be stored in plain text. For improved security, we highly recommend using your site\'s WordPress configuration file to set your password.', 'wp-mail-smtp' ); ?>
<?php esc_html_e( 'The password is encrypted in the database, but for improved security we recommend using your site\'s WordPress configuration file to set your password.', 'wp-mail-smtp' ); ?>
<br>
<a href="https://wpmailsmtp.com/docs/how-to-secure-smtp-settings-by-using-constants/" target="_blank" rel="noopener noreferrer">
<strong><?php esc_html_e( 'Learn More', 'wp-mail-smtp' ); ?></strong>

View File

@ -320,7 +320,7 @@ class Mailer extends MailerAbstract {
*
* @return string
*/
protected function get_response_error() {
public function get_response_error() {
$body = (array) wp_remote_retrieve_body( $this->response );
@ -328,7 +328,9 @@ class Mailer extends MailerAbstract {
$info = ! empty( $body['info'] ) ? $body['info'] : '';
$message = '';
if ( is_string( $error ) ) {
if ( ! empty( $this->error_message ) ) {
$message = $this->error_message;
} elseif ( is_string( $error ) ) {
$message = $error . ( ( ! empty( $info ) ) ? ' - ' . $info : '' );
} elseif ( is_array( $error ) ) {
$message = '';

View File

@ -307,7 +307,7 @@ class Mailer extends MailerAbstract {
$filetype = str_replace( ';', '', trim( $attachment[4] ) );
$data[] = array(
'content' => base64_encode( $file ),
'content' => chunk_split( base64_encode( $file ) ), // phpcs:ignore
'type' => $filetype,
'encoding' => 'base64',
'filename' => empty( $attachment[2] ) ? 'file-' . wp_hash( microtime() ) . '.' . $filetype : trim( $attachment[2] ),
@ -388,6 +388,31 @@ class Mailer extends MailerAbstract {
*/
public function set_return_path( $from_email ) {}
/**
* We might need to do something after the email was sent to the API.
* In this method we preprocess the response from the API.
*
* @since 2.5.0
*
* @param mixed $response Response data.
*/
protected function process_response( $response ) {
parent::process_response( $response );
if (
! is_wp_error( $response ) &&
! empty( $this->response['body']->data->message )
) {
preg_match( '/msg_id: (.*)/', $this->response['body']->data->message, $output );
if ( ! empty( $output[1] ) ) {
$this->phpmailer->addCustomHeader( 'X-Msg-ID', $output[1] );
$this->verify_sent_status = true;
}
}
}
/**
* Get a SMTP.com-specific response with a helpful error.
*
@ -405,7 +430,7 @@ class Mailer extends MailerAbstract {
*
* @return string
*/
protected function get_response_error() {
public function get_response_error() {
$body = (array) wp_remote_retrieve_body( $this->response );
@ -415,6 +440,8 @@ class Mailer extends MailerAbstract {
foreach ( (array) $body['data'] as $error_key => $error_message ) {
$error_text[] = $error_key . ' - ' . $error_message;
}
} elseif ( ! empty( $this->error_message ) ) {
$error_text[] = $this->error_message;
}
return implode( PHP_EOL, array_map( 'esc_textarea', $error_text ) );

View File

@ -352,7 +352,7 @@ class Mailer extends MailerAbstract {
*
* @return string
*/
protected function get_response_error() {
public function get_response_error() { // phpcs:ignore
$body = (array) wp_remote_retrieve_body( $this->response );
@ -374,6 +374,8 @@ class Mailer extends MailerAbstract {
$error_text[] = $error->message . ( ! empty( $extra ) ? ' - ' . $extra : '' );
}
}
} elseif ( ! empty( $this->error_message ) ) {
$error_text[] = $this->error_message;
}
return implode( '<br>', array_map( 'esc_textarea', $error_text ) );

View File

@ -318,8 +318,12 @@ class Mailer extends MailerAbstract {
$message = $e->getMessage();
}
$this->error_message = $message;
Debug::set( 'Mailer: Sendinblue' . PHP_EOL . $message );
} catch ( \Exception $e ) {
$this->error_message = $e->getMessage();
Debug::set( 'Mailer: Sendinblue' . PHP_EOL . $e->getMessage() );
return;
@ -338,6 +342,14 @@ class Mailer extends MailerAbstract {
protected function process_response( $response ) {
$this->response = $response;
if (
is_a( $response, 'WPMailSMTP\Vendor\SendinBlue\Client\Model\CreateSmtpEmail' ) &&
method_exists( $response, 'getMessageId' )
) {
$this->phpmailer->MessageID = $response->getMessageId();
$this->verify_sent_status = true;
}
}
/**

View File

@ -61,11 +61,13 @@ class Upgrade {
*/
public function v110_upgrade() {
$values = Options::init()->get_all();
// Enable SMTPAutoTLS option.
$values['smtp']['autotls'] = true;
$values = [
'smtp' => [
'autotls' => true,
],
];
Options::init()->set( $values );
Options::init()->set( $values, false, false );
}
}

View File

@ -22,11 +22,16 @@ class SendUsageTask extends Task {
* Server URL to send requests to.
*
* @since 2.3.0
*
* @var string
*/
const TRACK_URL = 'https://wpmailsmtpusage.com/v1/smtptrack';
/**
* Option name to store the timestamp of the last run.
*
* @since 2.5.0
*/
const LAST_RUN = 'wp_mail_smtp_send_usage_last_run';
/**
* Class constructor.
*
@ -71,7 +76,8 @@ class SendUsageTask extends Task {
*/
private function generate_start_date() {
$tracking = [];
$tracking = [];
$tracking['days'] = wp_rand( 0, 6 ) * DAY_IN_SECONDS;
$tracking['hours'] = wp_rand( 0, 23 ) * HOUR_IN_SECONDS;
$tracking['minutes'] = wp_rand( 0, 59 ) * MINUTE_IN_SECONDS;
@ -88,6 +94,17 @@ class SendUsageTask extends Task {
*/
public function process() {
$last_run = get_option( self::LAST_RUN );
// Make sure we do not run it more than once a day.
if (
$last_run !== false &&
( time() - $last_run ) < DAY_IN_SECONDS
) {
return;
}
// Send data to the usage tracking API.
$ut = new UsageTracking();
wp_remote_post(
@ -101,5 +118,8 @@ class SendUsageTask extends Task {
'user-agent' => $ut->get_user_agent(),
]
);
// Update the last run option to the current timestamp.
update_option( self::LAST_RUN, time() );
}
}