Skip to content

Instantly share code, notes, and snippets.

@paulschreiber
Last active July 20, 2020 22:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save paulschreiber/a1057785f6117f72188f3b619e994702 to your computer and use it in GitHub Desktop.
Save paulschreiber/a1057785f6117f72188f3b619e994702 to your computer and use it in GitHub Desktop.
<?php
/**
* Adapted from https://wordpress.stackexchange.com/a/191974/8591
*
* Send mail, similar to PHP's mail
*
* A true return value does not automatically mean that the user received the
* email successfully. It just only means that the method used was able to
* process the request without any errors.
*
* Using the two 'wp_mail_from' and 'wp_mail_from_name' hooks allow from
* creating a from address like 'Name <email@address.com>' when both are set. If
* just 'wp_mail_from' is set, then just the email address will be used with no
* name.
*
* The default content type is 'text/plain' which does not allow using HTML.
* However, you can set the content type of the email by using the
* 'wp_mail_content_type' filter.
*
* If $message is an array, the key of each is used to add as an attachment
* with the value used as the body. The 'text/plain' element is used as the
* text version of the body, with the 'text/html' element used as the HTML
* version of the body. All other types are added as attachments.
*
* The default charset is based on the charset used on the blog. The charset can
* be set using the 'wp_mail_charset' filter.
*
* @since 1.2.1
*
* @uses PHPMailer
*
* @param string|array $to Array or comma-separated list of email addresses to send message.
* @param string $subject Email subject
* @param string|array $message Message contents
* @param string|array $headers Optional. Additional headers.
* @param string|array $attachments Optional. Files to attach.
* @return bool Whether the email contents were sent successfully.
*/
public static function wp_mail( $to, $subject, $message, $headers = '', $attachments = [] ) {
// Compact the input, apply the filters, and extract them back out
/**
* Filter the wp_mail() arguments.
*
* @since 2.2.0
*
* @param array $args A compacted array of wp_mail() arguments, including the "to" email,
* subject, message, headers, and attachments values.
*/
$atts = apply_filters( 'wp_mail', compact( 'to', 'subject', 'headers', 'attachments' ) );
// Since $message is an array, and will wp_staticize_emoji_for_email() expects strings, walk over it one item at a time
if ( ! is_array( $message ) ) {
$message = [ $message ];
}
foreach ( $message as $message_part ) {
$message_part = apply_filters( 'wp_mail', $message_part );
}
$atts['message'] = $message;
if ( isset( $atts['to'] ) ) {
$to = $atts['to'];
}
if ( isset( $atts['subject'] ) ) {
$subject = $atts['subject'];
}
if ( isset( $atts['message'] ) ) {
$message = $atts['message'];
}
if ( isset( $atts['headers'] ) ) {
$headers = $atts['headers'];
}
if ( isset( $atts['attachments'] ) ) {
$attachments = $atts['attachments'];
}
if ( ! is_array( $attachments ) ) {
$attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
}
global $phpmailer;
// (Re)create it, if it's gone missing
if ( ! ( $phpmailer instanceof PHPMailer ) ) {
require_once ABSPATH . WPINC . '/class-phpmailer.php';
require_once ABSPATH . WPINC . '/class-smtp.php';
$phpmailer = new PHPMailer( true ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
// Headers
if ( empty( $headers ) ) {
$headers = [];
} else {
if ( ! is_array( $headers ) ) {
// Explode the headers out, so this function can take both
// string headers and an array of headers.
$tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
} else {
$tempheaders = $headers;
}
$headers = [];
$cc = [];
$bcc = [];
// If it's actually got contents
if ( ! empty( $tempheaders ) ) {
// Iterate through the raw headers
foreach ( (array) $tempheaders as $header ) {
if ( strpos( $header, ':' ) === false ) {
if ( false !== stripos( $header, 'boundary=' ) ) {
$parts = preg_split( '/boundary=/i', trim( $header ) );
$boundary = trim( str_replace( [ "'", '"' ], '', $parts[1] ) );
}
continue;
}
// Explode them out
list( $name, $content ) = explode( ':', trim( $header ), 2 );
// Cleanup crew
$name = trim( $name );
$content = trim( $content );
switch ( strtolower( $name ) ) {
// Mainly for legacy -- process a From: header if it's there
case 'from':
$bracket_pos = strpos( $content, '<' );
if ( false !== $bracket_pos ) {
// Text before the bracketed email is the "From" name.
if ( $bracket_pos > 0 ) {
$from_name = substr( $content, 0, $bracket_pos - 1 );
$from_name = str_replace( '"', '', $from_name );
$from_name = trim( $from_name );
}
$from_email = substr( $content, $bracket_pos + 1 );
$from_email = str_replace( '>', '', $from_email );
$from_email = trim( $from_email );
// Avoid setting an empty $from_email.
} elseif ( '' !== trim( $content ) ) {
$from_email = trim( $content );
}
break;
case 'content-type':
if ( is_array( $message ) ) {
// Multipart email, ignore the content-type header
break;
}
if ( strpos( $content, ';' ) !== false ) {
list( $type, $charset_content ) = explode( ';', $content );
$content_type = trim( $type );
if ( false !== stripos( $charset_content, 'charset=' ) ) {
$charset = trim( str_replace( [ 'charset=', '"' ], '', $charset_content ) );
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
$boundary = trim( str_replace( [ 'BOUNDARY=', 'boundary=', '"' ], '', $charset_content ) );
$charset = '';
}
// Avoid setting an empty $content_type.
} elseif ( '' !== trim( $content ) ) {
$content_type = trim( $content );
}
break;
case 'cc':
$cc = array_merge( (array) $cc, explode( ',', $content ) );
break;
case 'bcc':
$bcc = array_merge( (array) $bcc, explode( ',', $content ) );
break;
default:
// Add it to our grand headers array
$headers[ trim( $name ) ] = trim( $content );
break;
}
}
}
}
// Empty out the values that may be set
$phpmailer->ClearAllRecipients();
$phpmailer->ClearAttachments();
$phpmailer->ClearCustomHeaders();
$phpmailer->ClearReplyTos();
$phpmailer->Body = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$phpmailer->AltBody = ''; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// From email and name
// If we don't have a name from the input headers
if ( ! isset( $from_name ) ) {
$from_name = 'WordPress';
}
/* If we don't have an email from the input headers default to wordpress@$sitename
* Some hosts will block outgoing mail from this address if it doesn't exist but
* there's no easy alternative. Defaulting to admin_email might appear to be another
* option but some hosts may refuse to relay mail from an unknown domain. See
* https://core.trac.wordpress.org/ticket/5007.
*/
if ( ! isset( $from_email ) ) {
// Get the site domain and get rid of www.
$sitename = isset( $_SERVER['SERVER_NAME'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) ) : ''; // phpcs:ignore WordPress.VIP.SuperGlobalInputUsage.AccessDetected
if ( substr( $sitename, 0, 4 ) === 'www.' ) {
$sitename = substr( $sitename, 4 );
}
$from_email = 'wordpress@' . $sitename;
}
/**
* Filter the email address to send from.
*
* @since 2.2.0
*
* @param string $from_email Email address to send from.
*/
$phpmailer->From = apply_filters( 'wp_mail_from', $from_email ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
/**
* Filter the name to associate with the "from" email address.
*
* @since 2.3.0
*
* @param string $from_name Name associated with the "from" email address.
*/
$phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Set destination addresses
if ( ! is_array( $to ) ) {
$to = explode( ',', $to );
}
foreach ( (array) $to as $recipient ) {
try {
// Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
$recipient_name = '';
if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
if ( count( $matches ) === 3 ) {
$recipient_name = $matches[1];
$recipient = $matches[2];
}
}
$phpmailer->AddAddress( $recipient, $recipient_name );
} catch ( phpmailerException $e ) {
continue;
}
}
// If we don't have a charset from the input headers
if ( ! isset( $charset ) ) {
$charset = get_bloginfo( 'charset' );
}
// Set the content-type and charset
/**
* Filter the default wp_mail() charset.
*
* @since 2.3.0
*
* @param string $charset Default email charset.
*/
$phpmailer->CharSet = apply_filters( 'wp_mail_charset', $charset ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Set mail's subject and body
$phpmailer->Subject = $subject; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
if ( is_string( $message ) ) {
$phpmailer->Body = $message; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Set Content-Type and charset
// If we don't have a content-type from the input headers
if ( ! isset( $content_type ) ) {
$content_type = 'text/plain';
}
/**
* Filter the wp_mail() content type.
*
* @since 2.3.0
*
* @param string $content_type Default wp_mail() content type.
*/
$content_type = apply_filters( 'wp_mail_content_type', $content_type );
$phpmailer->ContentType = $content_type; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Set whether it's plaintext, depending on $content_type
if ( 'text/html' === $content_type ) {
$phpmailer->IsHTML( true );
}
// For backwards compatibility, new multipart emails should use
// the array style $message. This never really worked well anyway
if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) {
$phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
}
} elseif ( is_array( $message ) ) {
foreach ( $message as $type => $bodies ) {
foreach ( (array) $bodies as $body ) {
if ( 'text/html' === $type ) {
$phpmailer->Body = $body; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
} elseif ( 'text/plain' === $type ) {
$phpmailer->AltBody = $body; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
} else {
$phpmailer->AddAttachment( $body, '', 'base64', $type );
}
}
}
}
// Add any CC and BCC recipients
if ( ! empty( $cc ) ) {
foreach ( (array) $cc as $recipient ) {
try {
// Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
$recipient_name = '';
if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
if ( count( $matches ) === 3 ) {
$recipient_name = $matches[1];
$recipient = $matches[2];
}
}
$phpmailer->AddCc( $recipient, $recipient_name );
} catch ( phpmailerException $e ) {
continue;
}
}
}
if ( ! empty( $bcc ) ) {
foreach ( (array) $bcc as $recipient ) {
try {
// Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
$recipient_name = '';
if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
if ( count( $matches ) === 3 ) {
$recipient_name = $matches[1];
$recipient = $matches[2];
}
}
$phpmailer->AddBcc( $recipient, $recipient_name );
} catch ( phpmailerException $e ) {
continue;
}
}
}
// Set to use PHP's mail()
$phpmailer->IsMail();
// Set custom headers
if ( ! empty( $headers ) ) {
foreach ( (array) $headers as $name => $content ) {
$phpmailer->AddCustomHeader( sprintf( '%1$s: %2$s', $name, $content ) );
}
}
if ( ! empty( $attachments ) ) {
foreach ( $attachments as $attachment ) {
try {
$phpmailer->AddAttachment( $attachment );
} catch ( phpmailerException $e ) {
continue;
}
}
}
/**
* Fires after PHPMailer is initialized.
*
* @since 2.2.0
*
* @param PHPMailer &$phpmailer The PHPMailer instance, passed by reference.
*/
do_action_ref_array( 'phpmailer_init', [ &$phpmailer ] );
// Send!
try {
return $phpmailer->Send();
} catch ( phpmailerException $e ) {
return new WP_Error( 'email-error', $e->getMessage() );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment