-
-
Save thomasfw/5df1a041fd8f9c939ef9d88d887ce023 to your computer and use it in GitHub Desktop.
<?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; | |
}); |
PHP Fatal error: Uncaught ErrorException: Attempt to assign property "___wp_attachments" on null in /xxxs/enhanced-wpmail-attachments/enhanced-wpmail-attachments.php:56
At the time apply_filters for wp_mail is called, the global $phpmailer may not have been initialized, or gone missing for some reason. The wp_mail function itself checks and corrects that, if necessary, around line 257 - unfortunately after the filters have been applied.
@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.
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).
Installation
Drop the
wpmail-enhanced-attachments.php
file into your site'smu-plugins/
directory.Usage
The filter allows us to pass additional attachment data through
wp_mail()
to the underlingPHPMailer::addAttachment()
andPHPMailer:: addStringAttachment()
methods, crucially enabling us to attach files that aren't in the filesystem.By default, when using the WordPress
wp_mail()
function, "the filenames in the $attachments attribute have to be filesystem paths." - https://developer.wordpress.org/reference/functions/wp_mail/#notesWordPress Default
Adding attachments in the default manner, where
$attachments
can bestring
orstring[]
of filesystem paths:Adding a File Attachment
Any array value containing the
path
key will be treated as a file based attachment and added using the following method:PHPMailer::addAttachment( $path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment' )
Adding a String Attachment
Any array value containing the
string
key will be treated as a string attachment and added using the following method:PHPMailer::addStringAttachment( $string, $filename = '', $encoding = 'base64', $type = '', $disposition = 'attachment' )
So if for example you have a pdf generated with
dompdf/dompdf
, you can add it as an attachment like so:Combining Attachment Types
The value of
$attachments
may contain a mixture ofstring
filesystem paths and arrays containing your attachment data: