Last active
January 18, 2025 21:11
-
-
Save thomasfw/5df1a041fd8f9c939ef9d88d887ce023 to your computer and use it in GitHub Desktop.
Enable non-filesystem attachments with wp_mail()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Plugin Name: Enhanced WP Mail Attachments | |
* Plugin URI: https://gist.github.com/thomasfw/5df1a041fd8f9c939ef9d88d887ce023/ | |
* Version: 0.1 | |
*/ | |
/** | |
* Adds support for defining attachments as data arrays in wp_mail(). | |
* Allows us to send string-based or binary attachments (non-filesystem) | |
* and gives us more control over the attachment data. | |
* | |
* @param array $atts Array of the `wp_mail()` arguments. | |
* - string|string[] $to Array or comma-separated list of email addresses to send message. | |
* - string $subject Email subject. | |
* - string $message Message contents. | |
* - string|string[] $headers Additional headers. | |
* - string|string[] $attachments Paths to files to attach. | |
* | |
* @see https://gist.github.com/thomasfw/5df1a041fd8f9c939ef9d88d887ce023/ | |
*/ | |
add_filter( 'wp_mail', function( $atts ) | |
{ | |
$attachment_arrays = []; | |
if ( array_key_exists('attachments', $atts) && isset($atts['attachments']) && $atts['attachments'] ) | |
{ | |
$attachments = $atts['attachments']; | |
if ( is_array($attachments) && ! empty($attachments) ) | |
{ | |
// Is the $attachments array a single array of attachment data, or an array containing multiple arrays of | |
// attachment data? (note that the array may also be a one-dimensional array of file paths, as-per default usage). | |
$is_multidimensional_array = count($attachments) == count($attachments, COUNT_RECURSIVE) ? false : true; | |
if ( ! $is_multidimensional_array ) $attachments = [ $attachments ]; | |
// Work out which attachments we want to process here. If the value is an array with either | |
// a 'path' or 'path' key, then we'll process it separately and remove it from the | |
// $atts['attachments'] so that WP doesn't try to process it in wp_mail(). | |
foreach ( $attachments as $index => $attachment ) | |
{ | |
if ( is_array($attachment) && (array_key_exists('path', $attachment) || array_key_exists('string', $attachment)) ) | |
{ | |
$attachment_arrays[] = $attachment; | |
if ( $is_multidimensional_array ) { | |
unset( $atts['attachments'][$index] ); | |
} else { | |
$atts['attachments'] = []; | |
} | |
} | |
} | |
} | |
// Set the $wp_mail_attachments global to our attachment data. | |
// We'll read this later to check if any extra attachments should | |
// be added to the email. The value will be reset every time wp_mail() | |
// is called. | |
global $wp_mail_attachments; | |
$wp_mail_attachments = $attachment_arrays; | |
// We can't use the global $phpmailer to add our attachments directly in the 'wp_mail' filter callback because WP calls $phpmailer->clearAttachments() | |
// after this filter runs. Instead, we now hook into the 'phpmailer_init' action (triggered right before the email is sent), and read | |
// the $wp_mail_attachments global to check for any additional attachments to add. | |
add_action( 'phpmailer_init', function( \PHPMailer\PHPMailer\PHPMailer $phpmailer ) | |
{ | |
// Check the $wp_mail_attachments global for any attachment data, and reset it for good measure. | |
$attachment_arrays = []; | |
if ( array_key_exists('wp_mail_attachments', $GLOBALS) ) | |
{ | |
global $wp_mail_attachments; | |
$attachment_arrays = $wp_mail_attachments; | |
$wp_mail_attachments = []; | |
} | |
// Loop through our attachment arrays and attempt to add them using PHPMailer::addAttachment() or PHPMailer::addStringAttachment(): | |
foreach ( $attachment_arrays as $attachment ) | |
{ | |
$is_filesystem_attachment = array_key_exists( 'path', $attachment ) ? true : false; | |
try | |
{ | |
$encoding = $attachment['encoding'] ?? $phpmailer::ENCODING_BASE64; | |
$type = $attachment['type'] ?? ''; | |
$disposition = $attachment['disposition'] ?? 'attachment'; | |
if ( $is_filesystem_attachment ) | |
{ | |
$phpmailer->addAttachment( ($attachment['path'] ?? null), ($attachment['name'] ?? ''), $encoding, $type, $disposition ); | |
} | |
else | |
{ | |
$phpmailer->addStringAttachment( ($attachment['string'] ?? null), ($attachment['filename'] ?? ''), $encoding, $type, $disposition ); | |
} | |
} | |
catch ( \PHPMailer\PHPMailer\Exception $e ) { continue; } | |
} | |
// var_dump( $phpmailer->getAttachments() ); // Debug the mail attachments, including those parsed by WP. | |
}); | |
} | |
return $atts; | |
}); |
Yes. I can confirm that change solves the problem. Thanks!
Thanks for this!
I think the 'name' and 'filename' parameters are wrong in the examples.
In the foreach loop, files use 'name' and strings use 'filename'.
Switching my string to use 'filename' stopped the attachment coming through as 'noname'.
@robwent good catch, examples have been updated!
Really nice, this should be in core. You made my day 🥇
Thanks for the code!
Couldn't the lines 26-29 be simplified to this:?
...
$attachment_arrays = [];
if ( ! empty( $atts['attachments'] ) )
{
$attachments = $atts['attachments'];
if ( is_array($attachments) )
{
...
Because:
- Remove
array_key_exists
of first if condtion, becausearray_key_exists
is also checked byisset
- remove
isset
of first if conditon. Becauseisset
(variable declaration and non-null) is also checked by truthyfullness (last part of the first if condition) - Move
! empty( $attachments )
to first if condition, because! empty( $a )
is the same as$a ? true : false
(truthyfullness).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@flymikeGit - I've updated this to set the attachment data elsewhere instead of on the global
$phpmailer
instance (which as you reported, may or may not be available at the time the filter is applied).However, this change is untested for now until I get more time to check it. Feel free to try it and let me know if it's working for you.