Skip to content

Commit accf941

Browse files
Mail: Support inline attachments.
MIME allows for referencing included attachments by their `Content-ID` header using the `cid` URL scheme. This can be used to embed images inline to the HTML message. For example, `<img src="cid:logo">`, will display the contents of message part with the `Content-Id: <logo>` header. The `wp_mail()` function now supports including inline attachments through a new `$embeds` parameter. It accepts a map of `Content-ID` values to file paths. The `wp_mail_embed_args` filter can be used to customize the resulting `PHPMailer::addEmbeddedImage` method call. Props jesin, swissspidy, chrisvendiadvertisingcom, SirLouen, mukesh27, yashjawale, iamadisingh. Fixes #28059. git-svn-id: https://develop.svn.wordpress.org/trunk@60698 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e3ba953 commit accf941

File tree

3 files changed

+145
-2
lines changed

3 files changed

+145
-2
lines changed

src/wp-includes/pluggable.php

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,22 @@ function cache_users( $user_ids ) {
158158
* The default charset is based on the charset used on the blog. The charset can
159159
* be set using the {@see 'wp_mail_charset'} filter.
160160
*
161+
* When using the `$embeds` parameter to embed images for use in HTML emails,
162+
* reference the embedded file in your HTML with a `cid:` URL whose value
163+
* matches the file's Content-ID. By default, the Content-ID (`cid`) used for
164+
* each embedded file is the key in the embeds array, unless modified via the
165+
* {@see 'wp_mail_embed_args'} filter. For example:
166+
*
167+
* `<img src="cid:0" alt="Logo">`
168+
* `<img src="cid:my-image" alt="Image">`
169+
*
170+
* You may also customize the Content-ID for each file by using the
171+
* {@see 'wp_mail_embed_args'} filter and setting the `cid` value.
172+
*
161173
* @since 1.2.1
162174
* @since 5.5.0 is_email() is used for email validation,
163175
* instead of PHPMailer's default validator.
176+
* @since 6.9.0 Added $embeds parameter.
164177
*
165178
* @global PHPMailer\PHPMailer\PHPMailer $phpmailer
166179
*
@@ -169,9 +182,10 @@ function cache_users( $user_ids ) {
169182
* @param string $message Message contents.
170183
* @param string|string[] $headers Optional. Additional headers.
171184
* @param string|string[] $attachments Optional. Paths to files to attach.
185+
* @param string|string[] $embeds Optional. Paths to files to embed.
172186
* @return bool Whether the email was sent successfully.
173187
*/
174-
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
188+
function wp_mail( $to, $subject, $message, $headers = '', $attachments = array(), $embeds = array() ) {
175189
// Compact the input, apply the filters, and extract them back out.
176190

177191
/**
@@ -187,9 +201,10 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
187201
* @type string $message Message contents.
188202
* @type string|string[] $headers Additional headers.
189203
* @type string|string[] $attachments Paths to files to attach.
204+
* @type string|string[] $embeds Paths to files to embed.
190205
* }
191206
*/
192-
$atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) );
207+
$atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments', 'embeds' ) );
193208

194209
/**
195210
* Filters whether to preempt sending an email.
@@ -209,6 +224,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
209224
* @type string $message Message contents.
210225
* @type string|string[] $headers Additional headers.
211226
* @type string|string[] $attachments Paths to files to attach.
227+
* @type string|string[] $embeds Paths to files to embed.
212228
* }
213229
*/
214230
$pre_wp_mail = apply_filters( 'pre_wp_mail', null, $atts );
@@ -244,6 +260,15 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
244260
if ( ! is_array( $attachments ) ) {
245261
$attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
246262
}
263+
264+
if ( isset( $atts['embeds'] ) ) {
265+
$embeds = $atts['embeds'];
266+
}
267+
268+
if ( ! is_array( $embeds ) ) {
269+
$embeds = explode( "\n", str_replace( "\r\n", "\n", $embeds ) );
270+
}
271+
247272
global $phpmailer;
248273

249274
// (Re)create it, if it's gone missing.
@@ -531,6 +556,50 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
531556
}
532557
}
533558

559+
if ( ! empty( $embeds ) ) {
560+
foreach ( $embeds as $key => $embed_path ) {
561+
/**
562+
* Filters the arguments for PHPMailer's addEmbeddedImage() method.
563+
*
564+
* @since 6.9.0
565+
*
566+
* @param array $args {
567+
* An array of arguments for `addEmbeddedImage()`.
568+
* @type string $path The path to the file.
569+
* @type string $cid The Content-ID of the image. Default: The key in the embeds array.
570+
* @type string $name The filename of the image.
571+
* @type string $encoding The encoding of the image. Default: 'base64'.
572+
* @type string $type The MIME type of the image. Default: empty string, which lets PHPMailer auto-detect.
573+
* @type string $disposition The disposition of the image. Default: 'inline'.
574+
* }
575+
*/
576+
$embed_args = apply_filters(
577+
'wp_mail_embed_args',
578+
array(
579+
'path' => $embed_path,
580+
'cid' => (string) $key,
581+
'name' => basename( $embed_path ),
582+
'encoding' => 'base64',
583+
'type' => '',
584+
'disposition' => 'inline',
585+
)
586+
);
587+
588+
try {
589+
$phpmailer->addEmbeddedImage(
590+
$embed_args['path'],
591+
$embed_args['cid'],
592+
$embed_args['name'],
593+
$embed_args['encoding'],
594+
$embed_args['type'],
595+
$embed_args['disposition']
596+
);
597+
} catch ( PHPMailer\PHPMailer\Exception $e ) {
598+
continue;
599+
}
600+
}
601+
}
602+
534603
/**
535604
* Fires after PHPMailer is initialized.
536605
*
@@ -563,6 +632,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
563632
* @type string $message Message contents.
564633
* @type string[] $headers Additional headers.
565634
* @type string[] $attachments Paths to files to attach.
635+
* @type string[] $embeds Paths to files to embed.
566636
* }
567637
*/
568638
do_action( 'wp_mail_succeeded', $mail_data );

tests/phpunit/tests/pluggable/signatures.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ public function get_pluggable_function_signatures() {
137137
'message',
138138
'headers' => '',
139139
'attachments' => array(),
140+
'embeds' => array(),
140141
),
141142
'wp_authenticate' => array( 'username', 'password' ),
142143
'wp_logout' => array(),

tests/phpunit/tests/pluggable/wpMail.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,4 +554,76 @@ public function test_wp_mail_resets_properties() {
554554
$phpmailer = $GLOBALS['phpmailer'];
555555
$this->assertNotSame( 'user1', $phpmailer->AltBody );
556556
}
557+
558+
/**
559+
* Test that wp_mail() can send embedded images.
560+
*
561+
* @ticket 28059
562+
* @covers ::wp_mail
563+
*/
564+
public function test_wp_mail_can_send_embedded_images() {
565+
$embeds = array(
566+
'canola' => DIR_TESTDATA . '/images/canola.jpg',
567+
DIR_TESTDATA . '/images/test-image-2.gif',
568+
DIR_TESTDATA . '/images/avif-lossy.avif',
569+
);
570+
571+
$message = '';
572+
foreach ( $embeds as $key => $path ) {
573+
$message .= '<p><img src="cid:' . $key . '" alt="" /></p>';
574+
}
575+
576+
wp_mail(
577+
578+
'Embedded images test',
579+
$message,
580+
'Content-Type: text/html',
581+
array(),
582+
$embeds
583+
);
584+
585+
$mailer = tests_retrieve_phpmailer_instance();
586+
$attachments = $mailer->getAttachments();
587+
588+
foreach ( $attachments as $attachment ) {
589+
$inline_embed_exists = in_array( $attachment[0], $embeds, true ) && 'inline' === $attachment[6];
590+
$this->assertTrue( $inline_embed_exists, 'The attachment ' . $attachment[2] . ' is not inline in the embeds array.' );
591+
}
592+
foreach ( $embeds as $key => $path ) {
593+
$this->assertStringContainsString( 'cid:' . $key, $mailer->get_sent()->body, 'The cid ' . $key . ' is not referenced in the mail body.' );
594+
}
595+
}
596+
/**
597+
* Test that wp_mail() can send embedded images as a multiple line string.
598+
*
599+
* @ticket 28059
600+
* @covers ::wp_mail
601+
*/
602+
public function test_wp_mail_string_embeds() {
603+
$embeds = DIR_TESTDATA . '/images/canola.jpg' . "\n";
604+
$embeds .= DIR_TESTDATA . '/images/test-image-2.gif';
605+
606+
$message = '<p><img src="cid:0" alt="" /></p><p><img src="cid:1" alt="" /></p>';
607+
608+
wp_mail(
609+
610+
'Embedded images test',
611+
$message,
612+
'Content-Type: text/html',
613+
array(),
614+
$embeds
615+
);
616+
617+
$embeds_array = explode( "\n", $embeds );
618+
$mailer = tests_retrieve_phpmailer_instance();
619+
$attachments = $mailer->getAttachments();
620+
621+
foreach ( $attachments as $attachment ) {
622+
$inline_embed_exists = in_array( $attachment[0], $embeds_array, true ) && 'inline' === $attachment[6];
623+
$this->assertTrue( $inline_embed_exists, 'The attachment ' . $attachment[2] . ' is not inline in the embeds array.' );
624+
}
625+
foreach ( $embeds_array as $key => $path ) {
626+
$this->assertStringContainsString( 'cid:' . $key, $mailer->get_sent()->body, 'The cid ' . $key . ' is not referenced in the mail body.' );
627+
}
628+
}
557629
}

0 commit comments

Comments
 (0)