From b8388b7165766bd72a74901706ec5d1bb03f3c52 Mon Sep 17 00:00:00 2001 From: Peter Kiss Date: Thu, 14 Dec 2023 13:39:11 +0100 Subject: [PATCH] Add multipart email support --- .../abstract-wp-job-manager-email.php | 2 +- ...ass-wp-job-manager-email-notifications.php | 104 ++++++++++++++---- ...ass.wp-job-manager-email-notifications.php | 2 +- 3 files changed, 83 insertions(+), 25 deletions(-) diff --git a/includes/abstracts/abstract-wp-job-manager-email.php b/includes/abstracts/abstract-wp-job-manager-email.php index 59e0c3441..2ad799310 100644 --- a/includes/abstracts/abstract-wp-job-manager-email.php +++ b/includes/abstracts/abstract-wp-job-manager-email.php @@ -188,7 +188,7 @@ public function get_headers() { * @return string */ public function get_plain_content() { - return wp_strip_all_tags( $this->get_rich_content() ); + return normalize_whitespace( wp_strip_all_tags( $this->get_rich_content() ) ); } /** diff --git a/includes/class-wp-job-manager-email-notifications.php b/includes/class-wp-job-manager-email-notifications.php index b76eaab5b..1c252f6d3 100644 --- a/includes/class-wp-job-manager-email-notifications.php +++ b/includes/class-wp-job-manager-email-notifications.php @@ -18,6 +18,7 @@ final class WP_Job_Manager_Email_Notifications { const EMAIL_SETTING_PREFIX = 'job_manager_email_'; const EMAIL_SETTING_ENABLED = 'enabled'; const EMAIL_SETTING_PLAIN_TEXT = 'plain_text'; + const MULTIPART_BOUNDARY = '--jm-boundary'; /** * Notifications to be scheduled. @@ -763,6 +764,8 @@ private static function is_email_notification_valid( $email_class ) { * @return bool */ private static function send_email( $email_notification_key, WP_Job_Manager_Email $email ) { + add_filter( 'wp_mail_content_type', [ __CLASS__, 'mail_content_type' ] ); + if ( ! $email->is_valid() ) { return false; } @@ -792,7 +795,15 @@ private static function send_email( $email_notification_key, WP_Job_Manager_Emai $sent_count = 0; foreach ( $send_to as $to_email ) { $args['to'] = $to_email; - $content = self::get_email_content( $email_notification_key, $args ); + + $is_plain_text_only = self::send_as_plain_text( $email_notification_key, $args ); + + $content_plain = self::get_email_content( $email_notification_key, $args, true ); + $content_html = null; + + if ( ! $is_plain_text_only ) { + $content_html = self::get_email_content( $email_notification_key, $args, false ); + } /** * Filter all email arguments for job manager notifications. @@ -813,9 +824,7 @@ private static function send_email( $email_notification_key, WP_Job_Manager_Emai $headers[] = 'CC: ' . $args['cc']; } - if ( ! self::send_as_plain_text( $email_notification_key, $args ) ) { - $headers[] = 'Content-Type: text/html'; - } + $multipart_body = self::get_multipart_body( $content_html, $content_plain ); /** * Allows for short-circuiting the actual sending of email notifications. @@ -828,29 +837,46 @@ private static function send_email( $email_notification_key, WP_Job_Manager_Emai * @param string $content Email content. * @param array $headers Email headers. */ - if ( ! apply_filters( 'job_manager_email_do_send_notification', true, $email, $args, $content, $headers ) ) { + if ( ! apply_filters( 'job_manager_email_do_send_notification', true, $email, $args, $multipart_body, $headers ) ) { continue; } - if ( wp_mail( $to_email, $args['subject'], $content, $headers, $args['attachments'] ) ) { + if ( wp_mail( $to_email, $args['subject'], $multipart_body, $headers, $args['attachments'] ) ) { $sent_count++; } } + remove_filter( 'wp_mail_content_type', [ __CLASS__, 'mail_content_type' ] ); + $job_manager_doing_email = false; + return $sent_count > 0; } + /** + * Set the "Content Type" header of the e-mail to multipart/alternative. + * + * @access private + * + * @since $$next-version$$ + * + * @return string + */ + public static function mail_content_type() { + return 'multipart/alternative; boundary="' . self::MULTIPART_BOUNDARY . '"'; + } + /** * Generates the content for an email. * * @access private * * @param string $email_notification_key Unique email notification key. - * @param array $args Arguments passed for generating email. + * @param array $args Arguments passed for generating email. + * @param bool $is_plain_text Whether to generate plain text or rich text content. + * * @return string */ - private static function get_email_content( $email_notification_key, $args ) { - $plain_text = self::send_as_plain_text( $email_notification_key, $args ); + private static function get_email_content( $email_notification_key, $args, $is_plain_text ) { ob_start(); @@ -860,15 +886,16 @@ private static function get_email_content( $email_notification_key, $args ) { * @since 1.31.0 * * @param string $email_notification_key Unique email notification key. - * @param array $args Arguments passed for generating email. - * @param bool $plain_text True if sending plain text email. + * @param array $args Arguments passed for generating email. + * @param bool $is_plain_text True if sending plain text email. */ - do_action( 'job_manager_email_header', $email_notification_key, $args, $plain_text ); + do_action( 'job_manager_email_header', $email_notification_key, $args, $is_plain_text ); - if ( $plain_text ) { - echo wp_kses_post( html_entity_decode( wptexturize( $args['plain_content'] ) ) ); + if ( $is_plain_text ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Plain text e-mail. + echo wp_specialchars_decode( wp_strip_all_tags( $args['plain_content'] ) ); } else { - echo wp_kses_post( wpautop( wptexturize( $args['rich_content'] ) ) ); + echo wp_kses_post( ( $args['rich_content'] ) ); } /** @@ -877,13 +904,13 @@ private static function get_email_content( $email_notification_key, $args ) { * @since 1.31.0 * * @param string $email_notification_key Unique email notification key. - * @param array $args Arguments passed for generating email. - * @param bool $plain_text True if sending plain text email. + * @param array $args Arguments passed for generating email. + * @param bool $is_plain_text True if sending plain text email. */ - do_action( 'job_manager_email_footer', $email_notification_key, $args, $plain_text ); + do_action( 'job_manager_email_footer', $email_notification_key, $args, $is_plain_text ); $content = ob_get_clean(); - if ( ! $plain_text ) { + if ( ! $is_plain_text ) { $content = self::inject_styles( $content ); } @@ -892,12 +919,12 @@ private static function get_email_content( $email_notification_key, $args ) { * * @since 1.31.0 * - * @param string $content Email content. + * @param string $content Email content. * @param string $email_notification_key Unique email notification key. - * @param array $args Arguments passed for generating email. - * @param bool $plain_text True if sending plain text email. + * @param array $args Arguments passed for generating email. + * @param bool $is_plain_text True if sending plain text email. */ - return apply_filters( 'job_manager_email_content', $content, $email_notification_key, $args, $plain_text ); + return apply_filters( 'job_manager_email_content', $content, $email_notification_key, $args, $is_plain_text ); } /** @@ -933,4 +960,35 @@ private static function get_styles() { return ob_get_clean(); } + /** + * Assemble multipart e-mail body. + * + * @param string $content_html + * @param string $content_plain + * + * @return string + */ + private static function get_multipart_body( string $content_html, string $content_plain ): string { + $multipart_body = ''; + + if ( ! empty( $content_html ) ) { + $multipart_body .= ' +--' . self::MULTIPART_BOUNDARY . ' +Content-Type: text/html; charset="utf-8" + +' . $content_html; + } + + if ( ! empty( $content_plain ) ) { + + $multipart_body .= ' +--' . self::MULTIPART_BOUNDARY . ' +Content-Type: text/plain; charset="utf-8" + +' . $content_plain; + } + + return $multipart_body; + } + } diff --git a/tests/php/tests/includes/test_class.wp-job-manager-email-notifications.php b/tests/php/tests/includes/test_class.wp-job-manager-email-notifications.php index 0bc2ed89d..1c0761552 100644 --- a/tests/php/tests/includes/test_class.wp-job-manager-email-notifications.php +++ b/tests/php/tests/includes/test_class.wp-job-manager-email-notifications.php @@ -118,7 +118,7 @@ public function test_send_deferred_notifications_valid_email() { $this->assertEquals( 'Test Subject', $sent_email->subject ); $this->assertStringContainsString( 'test', $sent_email->body ); $this->assertStringContainsString( 'From: From Name ', $sent_email->header ); - $this->assertStringContainsString( 'Content-Type: text/html;', $sent_email->header ); + $this->assertStringContainsString( 'Content-Type: text/html;', $sent_email->body ); } /**