Skip to content

Commit

Permalink
Fix bug with sending emails (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksandr-mykhailenko authored Jun 20, 2023
1 parent fda6e57 commit 7c09945
Show file tree
Hide file tree
Showing 6 changed files with 21 additions and 346 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Changelog
=========
1.9.5 (2023-06-20)
- Fix bug with sending emails

1.9.4 (2023-06-10)
- Fixed bug `Fatal error on lists page when another plugin redeclare wp_mail()`

Expand Down
24 changes: 7 additions & 17 deletions includes/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -427,23 +427,13 @@ public function ajax_send_test()
), JSON_THROW_ON_ERROR)
);
}
if (function_exists('mg_wp_mail')) {
$result = mg_wp_mail(
$admin_email,
__('Mailgun WordPress Plugin Test', 'mailgun'),
sprintf(__("This is a test email generated by the Mailgun WordPress plugin.\n\nIf you have received this message, the requested test has succeeded.\n\nThe sending region is set to %s.\n\nThe method used to send this email was: %s.",
'mailgun'), $region, $method),
['Content-Type: text/plain']
);
} else {
$result = wp_mail(
$admin_email,
__('Mailgun WordPress Plugin Test', 'mailgun'),
sprintf(__("This is a test email generated by the Mailgun WordPress plugin.\n\nIf you have received this message, the requested test has succeeded.\n\nThe sending region is set to %s.\n\nThe method used to send this email was: %s.",
'mailgun'), $region, $method),
['Content-Type: text/plain']
);
}
$result = wp_mail(
$admin_email,
__('Mailgun WordPress Plugin Test', 'mailgun'),
sprintf(__("This is a test email generated by the Mailgun WordPress plugin.\n\nIf you have received this message, the requested test has succeeded.\n\nThe sending region is set to %s.\n\nThe method used to send this email was: %s.",
'mailgun'), $region, $method),
['Content-Type: text/plain']
);



Expand Down
324 changes: 0 additions & 324 deletions includes/wp-mail-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -459,330 +459,6 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = [])
return false;
}

return true;
}
} else {
add_filter('wp_mail', 'mg_wp_mail', 10, 5);

function mg_wp_mail($to, $subject, $message, $headers = '', $attachments = [])
{
$mailgun = get_option('mailgun');
$region = (defined('MAILGUN_REGION') && MAILGUN_REGION) ? MAILGUN_REGION : $mailgun['region'];
$apiKey = (defined('MAILGUN_APIKEY') && MAILGUN_APIKEY) ? MAILGUN_APIKEY : $mailgun['apiKey'];
$domain = (defined('MAILGUN_DOMAIN') && MAILGUN_DOMAIN) ? MAILGUN_DOMAIN : $mailgun['domain'];

if (empty($apiKey) || empty($domain)) {
return false;
}

// If a region is not set via defines or through the options page, default to US region.
if (!($region)) {
error_log('[Mailgun] No region configuration was found! Defaulting to US region.');
$region = 'us';
}

// 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
[$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':
if (strpos($content, '<') !== false) {
$from_name = substr($content, 0, strpos($content, '<') - 1);
$from_name = str_replace('"', '', $from_name);
$from_name = trim($from_name);

$from_email = substr($content, strpos($content, '<') + 1);
$from_email = str_replace('>', '', $from_email);
$from_email = trim($from_email);
} else {
$from_email = trim($content);
}
break;
case 'content-type':
if (strpos($content, ';') !== false) {
[$type, $charset] = explode(';', $content);
$content_type = trim($type);
if (false !== stripos($charset, 'charset=')) {
$charset = trim(str_replace(['charset=', '"'], '', $charset));
} elseif (false !== stripos($charset, 'boundary=')) {
$boundary = trim(str_replace(['BOUNDARY=', 'boundary=', '"'], '', $charset));
$charset = '';
}
} else {
$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;
}
}
}
}

if (!isset($from_name)) {
$from_name = null;
}

if (!isset($from_email)) {
$from_email = null;
}

$from_name = mg_detect_from_name($from_name);
$from_email = mg_detect_from_address($from_email);
$fromString = "{$from_name} <{$from_email}>";

$body = [
'from' => $fromString,
'h:Sender' => $from_email,
'to' => $to,
'subject' => $subject,
];


$rcpt_data = apply_filters('mg_mutate_to_rcpt_vars', $to);
if (!is_null($rcpt_data['rcpt_vars'])) {
$body['recipient-variables'] = $rcpt_data['rcpt_vars'];
}

$body['o:tag'] = [];
$body['o:tracking-clicks'] = !empty($mailgun['track-clicks']) ? $mailgun['track-clicks'] : 'no';
$body['o:tracking-opens'] = empty($mailgun['track-opens']) ? 'no' : 'yes';

// this is the wordpress site tag
if (isset($mailgun['tag'])) {
$tags = explode(',', str_replace(' ', '', $mailgun['tag']));
$body['o:tag'] = $tags;
}

// campaign-id now refers to a list of tags which will be appended to the site tag
if (!empty($mailgun['campaign-id'])) {
$tags = explode(',', str_replace(' ', '', $mailgun['campaign-id']));
if (empty($body['o:tag'])) {
$body['o:tag'] = $tags;
} elseif (is_array($body['o:tag'])) {
$body['o:tag'] = array_merge($body['o:tag'], $tags);
} else {
$body['o:tag'] .= ',' . implode(',', $tags);
}
}

/**
* Filter tags.
*
* @param array $tags Mailgun tags.
* @param string $to To address.
* @param string $subject Subject line.
* @param string $message Message content.
* @param array $headers Headers array.
* @param array $attachments Attachments array.
* @param string $region Mailgun region.
* @param string $domain Mailgun domain.
*
* @return array Mailgun tags.
*/
$body['o:tag'] = apply_filters('mailgun_tags', $body['o:tag'], $to, $subject, $message, $headers, $attachments, $region, $domain);

if (!empty($cc) && is_array($cc)) {
$body['cc'] = implode(', ', $cc);
}

if (!empty($bcc) && is_array($bcc)) {
$body['bcc'] = implode(', ', $bcc);
}

// If we are not given a Content-Type in the supplied headers,
// write the message body to a file and try to determine the mimetype
// using get_mime_content_type.
if (!isset($content_type)) {
$tmppath = tempnam(get_temp_dir(), 'mg');
$tmp = fopen($tmppath, 'w+');

fwrite($tmp, $message);
fclose($tmp);

$content_type = get_mime_content_type($tmppath, 'text/plain');

unlink($tmppath);
}

// Allow external content type filter to function normally
if (has_filter('wp_mail_content_type')) {
$content_type = apply_filters(
'wp_mail_content_type',
$content_type
);
}

if ('text/plain' === $content_type) {
$body['text'] = $message;
} else if ('text/html' === $content_type) {
$body['html'] = $message;
} else {
$body['text'] = $message;
$body['html'] = $message;
}

// Some plugins, such as WooCommerce (@see WC_Email::handle_multipart()), to handle multipart/alternative with html
// and plaintext messages hooks into phpmailer_init action to override AltBody property directly in $phpmailer,
// so we should allow them to do this, and then get overridden plain text body from $phpmailer.
// Partly, this logic is taken from original wp_mail function.
if (false !== stripos($content_type, 'multipart')) {
global $phpmailer;

// (Re)create it, if it's gone missing.
if (!($phpmailer instanceof PHPMailer\PHPMailer\PHPMailer)) {
require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
require_once ABSPATH . WPINC . '/PHPMailer/SMTP.php';
require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
$phpmailer = new PHPMailer\PHPMailer\PHPMailer(true);

$phpmailer::$validator = static function ($email) {
return (bool)is_email($email);
};
}

/**
* Fires after PHPMailer is initialized.
*
* @param PHPMailer $phpmailer The PHPMailer instance (passed by reference).
*/
do_action_ref_array('phpmailer_init', [&$phpmailer]);

$plainTextMessage = $phpmailer->AltBody;

if ($plainTextMessage) {
$body['text'] = $plainTextMessage;
}
}

// If we don't have a charset from the input headers
if (!isset($charset)) {
$charset = get_bloginfo('charset');
}

// Set the content-type and charset
$charset = apply_filters('wp_mail_charset', $charset);
if (isset($headers['Content-Type'])) {
if (!strstr($headers['Content-Type'], 'charset')) {
$headers['Content-Type'] = rtrim($headers['Content-Type'], '; ') . "; charset={$charset}";
}
}

// Set custom headers
if (!empty($headers)) {
foreach ((array)$headers as $name => $content) {
$body["h:{$name}"] = $content;
}
}

/*
* Deconstruct post array and create POST payload.
* This entire routine is because wp_remote_post does
* not support files directly.
*/

$payload = '';

// First, generate a boundary for the multipart message.
$boundary = sha1(uniqid('', true));

// Allow other plugins to apply body changes before creating the payload.
$body = apply_filters('mg_mutate_message_body', $body);
if (($body_payload = mg_build_payload_from_body($body, $boundary)) != null) {
$payload .= $body_payload;
}

// Allow other plugins to apply attachment changes before writing to the payload.
$attachments = apply_filters('mg_mutate_attachments', $attachments);
if (($attachment_payload = mg_build_attachments_payload($attachments, $boundary)) != null) {
$payload .= $attachment_payload;
}

$payload .= '--' . $boundary . '--';

$data = [
'body' => $payload,
'headers' => [
'Authorization' => 'Basic ' . base64_encode("api:{$apiKey}"),
'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
],
];

$endpoint = mg_api_get_region($region);
$endpoint = ($endpoint) ? $endpoint : 'https://api.mailgun.net/v3/';
$url = $endpoint . "{$domain}/messages";

// TODO: Mailgun only supports 1000 recipients per request, since we are
// overriding this function, let's add looping here to handle that
$response = wp_remote_post($url, $data);
if (is_wp_error($response)) {
// Store WP error in last error.
mg_api_last_error($response->get_error_message());

return false;
}

$response_code = wp_remote_retrieve_response_code($response);
$response_body = json_decode(wp_remote_retrieve_body($response));

// Mailgun API should *always* return a `message` field, even when
// $response_code != 200, so a lack of `message` indicates something
// is broken.
if ((int)$response_code != 200 || !isset($response_body->message)) {
// Store response code and HTTP response message in last error.
$response_message = wp_remote_retrieve_response_message($response);
$errmsg = "$response_code - $response_message";
mg_api_last_error($errmsg);

return false;
}

// Not sure there is any additional checking that needs to be done here, but why not?
if ($response_body->message !== 'Queued. Thank you.') {
mg_api_last_error($response_body->message);

return false;
}

return true;
}
}
Expand Down
4 changes: 2 additions & 2 deletions mailgun.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: Mailgun
* Plugin URI: http://wordpress.org/extend/plugins/mailgun/
* Description: Mailgun integration for WordPress
* Version: 1.9.4
* Version: 1.9.5
* Tested up to: 6.1
* Author: Mailgun
* Author URI: http://www.mailgun.com/
Expand Down Expand Up @@ -81,7 +81,7 @@ public function __construct()
// When using SMTP, we also need to inject a `wp_mail` filter to make "from" settings
// work properly. Fixed issues with 1.5.7+
if ($this->get_option('useAPI') || (defined('MAILGUN_USEAPI') && MAILGUN_USEAPI)) {
if (!function_exists('wp_mail') || !function_exists('mg_wp_mail')) {
if (!function_exists('wp_mail')) {
if (!include_once(__DIR__ . '/includes/wp-mail-api.php')) {
$this->deactivate_and_die(__DIR__ . '/includes/wp-mail-api.php');
}
Expand Down
Loading

0 comments on commit 7c09945

Please sign in to comment.