PluginProbe ʕ •ᴥ•ʔ
Admin and Site Enhancements (ASE) / 8.6.2
Admin and Site Enhancements (ASE) v8.6.2
9.1.0 9.0.2 9.0.1 9.0.0 8.9.2 8.9.1 8.9.0 8.8.8 8.8.7 8.8.6 8.8.5 8.8.4 8.8.3 8.8.2 8.8.1 8.8.0 8.7.3 8.7.2 8.7.1 8.2.1 8.2.2 8.2.3 8.3.0 8.3.1 8.3.2 8.4.0 8.4.1 8.4.2 8.5.0 8.5.1 8.5.2 8.6.0 8.6.1 8.6.2 8.7.0 5.0.1 5.0.2 5.0.2.1 5.0.2.2 5.0.2.3 5.0.2.4 5.1.0 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.8.0 5.8.1 6.0.0 6.0.3 6.0.4 6.0.5 6.0.5.1 6.0.6 6.0.7 6.0.8.1 6.1.0 6.1.3 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.3.0 6.3.1 6.3.2 6.4.0 6.5.0 6.5.1 6.6.0 6.7.0 6.8.0 6.8.2 6.8.3 6.9.0 6.9.1 6.9.10 6.9.11 6.9.12 6.9.13.1 6.9.13.2 6.9.2 6.9.3 6.9.4 6.9.5 6.9.6 6.9.7 6.9.8 6.9.9 7.0.0 7.0.2 7.0.2.1 7.0.2.2 7.0.2.3 7.0.3 7.1.0 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2.0 7.2.1 7.3.0 7.3.1 7.3.2 7.3.3 7.4.0 7.4.2 7.4.4 7.4.5 7.4.6 7.4.7 7.4.8 7.5.0 7.5.1 7.5.2 7.5.3 7.5.4 7.6.0 7.6.1 7.6.1.1 7.6.10 7.6.11 7.6.2 7.6.3 7.6.4 7.6.5 7.6.6 7.6.7 7.6.7.1 7.6.8 7.6.9 7.7.0 7.8.0 7.8.1 7.8.10 7.8.11 7.8.12 7.8.13 7.8.14 7.8.15 7.8.16 7.8.17 7.8.18 7.8.2 7.8.3 7.8.4 7.8.5 7.8.5.1 7.8.6 7.8.7 7.8.8 7.8.9 7.9.0 7.9.1 7.9.10 7.9.11 7.9.2 7.9.3 7.9.4 7.9.5 7.9.6 7.9.7 7.9.8 7.9.9 8.0.0 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.0.6 8.0.7 8.0.8 8.1.0 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 trunk 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 1.9.0 2.0.0 2.1.0 2.2.0 2.3.0 2.4.0 2.5.0 2.6.0 2.7.0 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 3.6.1 3.7.0 3.8.0 3.9.0 3.9.1 3.9.2 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.9.0 4.9.1 4.9.2 4.9.3 5.0.0
admin-site-enhancements / classes / class-email-delivery.php
admin-site-enhancements / classes Last commit date
class-activation.php 4 months ago class-admin-menu-organizer.php 4 months ago class-admin-menu-svg-icon-mask.php 4 months ago class-auto-publish-posts-with-missed-schedule.php 4 months ago class-avif-upload.php 4 months ago class-captcha-protection.php 4 months ago class-change-login-url.php 4 months ago class-cleanup-admin-bar.php 4 months ago class-common-methods.php 4 months ago class-content-duplication.php 4 months ago class-content-order.php 4 months ago class-custom-admin-footer-text.php 4 months ago class-custom-body-class.php 4 months ago class-custom-css.php 4 months ago class-custom-nav-menu-items-in-new-tab.php 4 months ago class-deactivation.php 4 months ago class-disable-author-archives.php 4 months ago class-disable-comments.php 4 months ago class-disable-dashboard-widgets.php 4 months ago class-disable-embeds.php 4 months ago class-disable-feeds.php 4 months ago class-disable-gutenberg.php 4 months ago class-disable-rest-api.php 4 months ago class-disable-smaller-components.php 4 months ago class-disable-updates.php 4 months ago class-disable-xml-rpc.php 4 months ago class-display-system-summary.php 4 months ago class-email-address-obfuscator.php 4 months ago class-email-delivery.php 4 months ago class-enhance-list-tables.php 4 months ago class-external-permalinks.php 4 months ago class-heartbeat-control.php 4 months ago class-hide-admin-bar.php 4 months ago class-hide-admin-notices.php 4 months ago class-image-sizes-panel.php 4 months ago class-image-upload-control.php 4 months ago class-insert-head-body-footer-code.php 4 months ago class-last-login-column.php 4 months ago class-limit-login-attempts.php 4 months ago class-login-id-type.php 4 months ago class-login-logout-menu.php 4 months ago class-maintenance-mode.php 4 months ago class-manage-ads-appads-txt.php 4 months ago class-manage-robots-txt.php 4 months ago class-media-files-visibility-control.php 4 months ago class-media-replacement.php 4 months ago class-multiple-user-roles.php 4 months ago class-obfuscate-author-slugs.php 4 months ago class-open-external-links-in-new-tab.php 4 months ago class-password-protection.php 4 months ago class-redirect-after-login.php 4 months ago class-redirect-after-logout.php 4 months ago class-redirect-fourofour.php 4 months ago class-registration-date-column.php 4 months ago class-revisions-control.php 4 months ago class-search-engines-visibility.php 4 months ago class-settings-fields-render.php 4 months ago class-settings-sanitization.php 4 months ago class-settings-sections-fields.php 4 months ago class-show-custom-taxonomy-filters.php 4 months ago class-site-identity-on-login-page.php 4 months ago class-svg-upload.php 4 months ago class-various-admin-ui-enhancements.php 4 months ago class-view-admin-as-role.php 4 months ago class-wider-admin-menu.php 4 months ago class-wp-config-transformer.php 4 months ago
class-email-delivery.php
399 lines
1 <?php
2
3 namespace ASENHA\Classes;
4
5 use WP_Error;
6 use ASENHA\EmailDelivery\Email_Log_Table;
7 /**
8 * Class for Email Delivery module
9 *
10 * @since 6.9.5
11 */
12 class Email_Delivery {
13 const SMTP_PASSWORD_PREFIX = 'asenha_encrypted::smtp_password::v1::';
14
15 const SMTP_PASSWORD_STATUS_EMPTY = 'empty';
16
17 const SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT = 'legacy_plaintext';
18
19 const SMTP_PASSWORD_STATUS_ENCRYPTED_VALID = 'encrypted_valid';
20
21 const SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID = 'encrypted_invalid';
22
23 private $log_entry_id;
24
25 /**
26 * Derive a stable encryption key for the stored SMTP password.
27 *
28 * @since 8.4.3
29 *
30 * @return string
31 */
32 private function get_smtp_password_encryption_key() {
33 return hash( 'sha256', \wp_salt( 'auth' ) . \wp_salt( 'secure_auth' ) . 'asenha_smtp_password', true );
34 }
35
36 /**
37 * Check whether the current environment can encrypt/decrypt the SMTP password.
38 *
39 * @since 8.4.3
40 *
41 * @return bool
42 */
43 private function can_handle_smtp_password_encryption() {
44 return function_exists( 'openssl_encrypt' ) && function_exists( 'openssl_decrypt' ) && function_exists( 'openssl_cipher_iv_length' ) && function_exists( 'random_bytes' );
45 }
46
47 /**
48 * Check whether the stored SMTP password value is encrypted.
49 *
50 * @since 8.4.3
51 *
52 * @param string $stored_password Stored option value.
53 * @return bool
54 */
55 public function is_smtp_password_encrypted( $stored_password ) {
56 return is_string( $stored_password ) && 0 === strpos( $stored_password, self::SMTP_PASSWORD_PREFIX );
57 }
58
59 /**
60 * Encrypt SMTP password for storage in options.
61 *
62 * @since 8.4.3
63 *
64 * @param string $password SMTP password.
65 * @return string Encrypted payload or empty string on failure.
66 */
67 public function encrypt_smtp_password( $password ) {
68 if ( '' === $password ) {
69 return '';
70 }
71 if ( !$this->can_handle_smtp_password_encryption() ) {
72 return '';
73 }
74 $cipher = 'AES-256-CBC';
75 $key = $this->get_smtp_password_encryption_key();
76 $iv_len = openssl_cipher_iv_length( $cipher );
77 if ( false === $iv_len ) {
78 return '';
79 }
80 try {
81 $iv = random_bytes( $iv_len );
82 } catch ( \Exception $exception ) {
83 return '';
84 }
85 $ciphertext_raw = openssl_encrypt(
86 $password,
87 $cipher,
88 $key,
89 OPENSSL_RAW_DATA,
90 $iv
91 );
92 if ( false === $ciphertext_raw ) {
93 return '';
94 }
95 $hmac = hash_hmac(
96 'sha256',
97 $ciphertext_raw,
98 $key,
99 true
100 );
101 return self::SMTP_PASSWORD_PREFIX . base64_encode( $iv . $hmac . $ciphertext_raw );
102 }
103
104 /**
105 * Decrypt stored SMTP password.
106 *
107 * @since 8.4.3
108 *
109 * @param string $stored_password Stored option value.
110 * @return string|false
111 */
112 public function decrypt_smtp_password( $stored_password ) {
113 if ( !$this->is_smtp_password_encrypted( $stored_password ) ) {
114 return false;
115 }
116 if ( !$this->can_handle_smtp_password_encryption() ) {
117 return false;
118 }
119 $payload = substr( $stored_password, strlen( self::SMTP_PASSWORD_PREFIX ) );
120 $decoded = base64_decode( $payload, true );
121 if ( false === $decoded ) {
122 return false;
123 }
124 $cipher = 'AES-256-CBC';
125 $key = $this->get_smtp_password_encryption_key();
126 $iv_len = openssl_cipher_iv_length( $cipher );
127 if ( false === $iv_len || strlen( $decoded ) <= $iv_len + 32 ) {
128 return false;
129 }
130 $iv = substr( $decoded, 0, $iv_len );
131 $stored_hmac = substr( $decoded, $iv_len, 32 );
132 $ciphertext_raw = substr( $decoded, $iv_len + 32 );
133 $calc_hmac = hash_hmac(
134 'sha256',
135 $ciphertext_raw,
136 $key,
137 true
138 );
139 if ( !hash_equals( $stored_hmac, $calc_hmac ) ) {
140 return false;
141 }
142 return openssl_decrypt(
143 $ciphertext_raw,
144 $cipher,
145 $key,
146 OPENSSL_RAW_DATA,
147 $iv
148 );
149 }
150
151 /**
152 * Get the current storage status of the SMTP password.
153 *
154 * @since 8.4.3
155 *
156 * @param string|null $stored_password Stored option value.
157 * @return string
158 */
159 public function get_smtp_password_status( $stored_password = null ) {
160 if ( null === $stored_password ) {
161 $options = get_option( ASENHA_SLUG_U, array() );
162 $stored_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
163 }
164 if ( empty( $stored_password ) ) {
165 return self::SMTP_PASSWORD_STATUS_EMPTY;
166 }
167 if ( $this->is_smtp_password_encrypted( $stored_password ) ) {
168 return ( false !== $this->decrypt_smtp_password( $stored_password ) ? self::SMTP_PASSWORD_STATUS_ENCRYPTED_VALID : self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID );
169 }
170 return self::SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT;
171 }
172
173 /**
174 * Resolve SMTP password for runtime delivery use.
175 *
176 * Legacy plaintext remains supported as a migration fallback until the
177 * settings are re-saved and rewritten to the encrypted format.
178 *
179 * @since 8.4.3
180 *
181 * @param string|null $stored_password Stored option value.
182 * @return string
183 */
184 public function get_smtp_password_for_runtime( $stored_password = null ) {
185 if ( null === $stored_password ) {
186 $options = get_option( ASENHA_SLUG_U, array() );
187 $stored_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
188 }
189 switch ( $this->get_smtp_password_status( $stored_password ) ) {
190 case self::SMTP_PASSWORD_STATUS_ENCRYPTED_VALID:
191 $decrypted_password = $this->decrypt_smtp_password( $stored_password );
192 return ( false !== $decrypted_password ? $decrypted_password : '' );
193 case self::SMTP_PASSWORD_STATUS_LEGACY_PLAINTEXT:
194 return (string) $stored_password;
195 default:
196 return '';
197 }
198 }
199
200 /**
201 * Get message shown when the stored SMTP password can no longer be used.
202 *
203 * @since 8.4.3
204 *
205 * @return string
206 */
207 public function get_smtp_password_reentry_message() {
208 return __( 'The stored SMTP password can no longer be decrypted. Please enter it again and save changes.', 'admin-site-enhancements' );
209 }
210
211 /**
212 * Send emails using external SMTP service
213 *
214 * @since 4.6.0
215 */
216 public function deliver_email_via_smtp( $phpmailer ) {
217 $options = get_option( ASENHA_SLUG_U, array() );
218 $smtp_host = $options['smtp_host'];
219 $smtp_port = $options['smtp_port'];
220 $smtp_security = $options['smtp_security'];
221 $smtp_authentication = ( isset( $options['smtp_authentication'] ) ? $options['smtp_authentication'] : 'enable' );
222 $smtp_username = $options['smtp_username'];
223 $smtp_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
224 $smtp_default_from_name = $options['smtp_default_from_name'];
225 $smtp_default_from_email = $options['smtp_default_from_email'];
226 $smtp_force_from = $options['smtp_force_from'];
227 $smtp_bypass_ssl_verification = $options['smtp_bypass_ssl_verification'];
228 $smtp_debug = $options['smtp_debug'];
229 // Do nothing if host or password is empty
230 // if ( empty( $smtp_host ) || empty( $smtp_password ) ) {
231 // return;
232 // }
233 // Maybe override FROM email and/or name if the sender is "WordPress <wordpress@sitedomain.com>", the default from WordPress core and not yet overridden by another plugin.
234 $from_name = $phpmailer->FromName;
235 $from_email_beginning = substr( $phpmailer->From, 0, 9 );
236 // Get the first 9 characters of the current FROM email
237 if ( $smtp_force_from ) {
238 $phpmailer->FromName = $smtp_default_from_name;
239 $phpmailer->From = $smtp_default_from_email;
240 // WP 6.9: set SMTP envelope (MAIL FROM) using PHPMailer::Sender only.
241 // PHPMailer maintainers treat envelope bounce handling as **Sender**, not a separate ReturnPath property;
242 // receiving MTAs derive Return-Path from the envelope sender.
243 // Ref: https://make.wordpress.org/core/2025/11/18/more-reliable-email-in-wordpress-6-9/
244 $phpmailer->Sender = $smtp_default_from_email;
245 } else {
246 if ( 'WordPress' === $from_name && !empty( $smtp_default_from_name ) ) {
247 $phpmailer->FromName = $smtp_default_from_name;
248 }
249 if ( 'wordpress' === $from_email_beginning && !empty( $smtp_default_from_email ) ) {
250 $phpmailer->From = $smtp_default_from_email;
251 // WP 6.9: set SMTP envelope (MAIL FROM) using PHPMailer::Sender only.
252 // PHPMailer maintainers treat envelope bounce handling as **Sender**, not a separate ReturnPath property;
253 // receiving MTAs derive Return-Path from the envelope sender.
254 // Ref: https://make.wordpress.org/core/2025/11/18/more-reliable-email-in-wordpress-6-9/
255 $phpmailer->Sender = $smtp_default_from_email;
256 }
257 }
258 $smtp_password = $this->get_smtp_password_for_runtime( $smtp_password );
259 // Only attempt to send via SMTP if all the required info is present. Otherwise, use default PHP Mailer settings as set by wp_mail()
260 if ( !empty( $smtp_host ) && !empty( $smtp_port ) && !empty( $smtp_security ) ) {
261 // Send using SMTP
262 $phpmailer->isSMTP();
263 // phpcs:ignore
264 if ( 'enable' == $smtp_authentication ) {
265 $phpmailer->SMTPAuth = true;
266 // phpcs:ignore
267 } else {
268 $phpmailer->SMTPAuth = false;
269 // phpcs:ignore
270 }
271 // Set some other defaults
272 // $phpmailer->CharSet = 'utf-8'; // phpcs:ignore
273 $phpmailer->XMailer = 'Admin and Site Enhancements v' . ASENHA_VERSION . ' - a WordPress plugin';
274 // phpcs:ignore
275 $phpmailer->Host = $smtp_host;
276 // phpcs:ignore
277 $phpmailer->Port = $smtp_port;
278 // phpcs:ignore
279 $phpmailer->SMTPSecure = $smtp_security;
280 // phpcs:ignore
281 if ( 'enable' == $smtp_authentication ) {
282 $phpmailer->Username = trim( $smtp_username );
283 // phpcs:ignore
284 $phpmailer->Password = trim( $smtp_password );
285 // phpcs:ignore
286 }
287 }
288 // If verification of SSL certificate is bypassed
289 // Reference: https://www.php.net/manual/en/context.ssl.php & https://stackoverflow.com/a/30803024
290 if ( $smtp_bypass_ssl_verification ) {
291 $phpmailer->SMTPOptions = [
292 'ssl' => [
293 'verify_peer' => false,
294 'verify_peer_name' => false,
295 'allow_self_signed' => true,
296 ],
297 ];
298 }
299 // If debug mode is enabled, send debug info (SMTP::DEBUG_CONNECTION) to WordPress debug.log file set in wp-config.php
300 // Reference: https://github.com/PHPMailer/PHPMailer/wiki/SMTP-Debugging
301 if ( $smtp_debug ) {
302 $phpmailer->SMTPDebug = 4;
303 //phpcs:ignore
304 $phpmailer->Debugoutput = 'error_log';
305 //phpcs:ignore
306 }
307 }
308
309 /**
310 * Send a test email and use SMTP host if defined in settings
311 *
312 * @since 5.3.0
313 */
314 public function send_test_email() {
315 if ( isset( $_REQUEST['email_to'] ) && isset( $_REQUEST['nonce'] ) && current_user_can( 'manage_options' ) ) {
316 if ( wp_verify_nonce( sanitize_text_field( $_REQUEST['nonce'] ), 'send-test-email-nonce_' . get_current_user_id() ) ) {
317 $options = get_option( ASENHA_SLUG_U, array() );
318 $smtp_host = ( isset( $options['smtp_host'] ) ? $options['smtp_host'] : '' );
319 $smtp_port = ( isset( $options['smtp_port'] ) ? $options['smtp_port'] : '' );
320 $smtp_security = ( isset( $options['smtp_security'] ) ? $options['smtp_security'] : '' );
321 $smtp_authentication = ( isset( $options['smtp_authentication'] ) ? $options['smtp_authentication'] : 'enable' );
322 $smtp_password = ( isset( $options['smtp_password'] ) ? $options['smtp_password'] : '' );
323 $smtp_password_status = $this->get_smtp_password_status( $smtp_password );
324 $smtp_is_configured = !empty( $smtp_host ) && !empty( $smtp_port ) && !empty( $smtp_security );
325 if ( $smtp_is_configured && 'enable' === $smtp_authentication && self::SMTP_PASSWORD_STATUS_ENCRYPTED_INVALID === $smtp_password_status ) {
326 wp_send_json( array(
327 'status' => 'failed',
328 'message' => $this->get_smtp_password_reentry_message(),
329 ) );
330 }
331 $content = array(
332 array(
333 'title' => 'Hey... are you getting this?',
334 'body' => '<p><strong>Looks like you did!</strong></p>',
335 ),
336 array(
337 'title' => 'There\'s a message for you...',
338 'body' => '<p><strong>Here it is:</strong></p>',
339 ),
340 array(
341 'title' => 'Is it working?',
342 'body' => '<p><strong>Yes, it\'s working!</strong></p>',
343 ),
344 array(
345 'title' => 'Hope you\'re getting this...',
346 'body' => '<p><strong>Looks like this was sent out just fine and you got it.</strong></p>',
347 ),
348 array(
349 'title' => 'Testing delivery configuration...',
350 'body' => '<p><strong>Everything looks good!</strong></p>',
351 ),
352 array(
353 'title' => 'Testing email delivery',
354 'body' => '<p><strong>Looks good!</strong></p>',
355 ),
356 array(
357 'title' => 'Config is looking good',
358 'body' => '<p><strong>Seems like everything has been set up properly!</strong></p>',
359 ),
360 array(
361 'title' => 'All set up',
362 'body' => '<p><strong>Your configuration is working properly.</strong></p>',
363 ),
364 array(
365 'title' => 'Good to go',
366 'body' => '<p><strong>Config is working great.</strong></p>',
367 ),
368 array(
369 'title' => 'Good job',
370 'body' => '<p><strong>Everything is set.</strong></p>',
371 )
372 );
373 $random_number = rand( 0, count( $content ) - 1 );
374 $to = sanitize_email( wp_unslash( $_REQUEST['email_to'] ) );
375 $title = $content[$random_number]['title'];
376 $body = $content[$random_number]['body'] . '<p>This message was sent from <a href="' . get_bloginfo( 'url' ) . '">' . get_bloginfo( 'url' ) . '</a> on ' . wp_date( 'F j, Y' ) . ' at ' . wp_date( 'H:i:s' ) . ' via ASE.</p>';
377 $headers = array('Content-Type: text/html; charset=UTF-8');
378 $success = wp_mail(
379 $to,
380 $title,
381 $body,
382 $headers
383 );
384 if ( $success ) {
385 $response = array(
386 'status' => 'success',
387 );
388 } else {
389 $response = array(
390 'status' => 'failed',
391 );
392 }
393 wp_send_json( $response );
394 }
395 }
396 }
397
398 }
399