Mailgun for WordPress - Version 1.5.8.3

Version Description

(2017-06-13): = - Fix a bug causing only the last header value to be used when multiple headers of the same type are specified (https://wordpress.org/support/topic/bug-with-mg_parse_headers/) - Added pt_BR translations (thanks @emersonbroga)

Download this release

Release Info

Developer Mailgun
Plugin Icon 128x128 Mailgun for WordPress
Version 1.5.8.3
Comparing to
See all releases

Code changes from version 1.5.8.2 to 1.5.8.3

CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
  Changelog
2
  =========
3
 
 
 
 
 
4
  1.5.8.2 (2017-02-27):
5
  - Fix a bug causing empty tags to be sent with messages (#51)
6
  - Add `mg_mutate_message_body` hook to allow other plugins to modify the message body before send
1
  Changelog
2
  =========
3
 
4
+ 1.5.8.3 (2017-06-13):
5
+ - Fix a bug causing only the last header value to be used when multiple headers of the same type are specified (https://wordpress.org/support/topic/bug-with-mg_parse_headers/)
6
+ - Added `pt_BR` translations (thanks @emersonbroga)
7
+
8
  1.5.8.2 (2017-02-27):
9
  - Fix a bug causing empty tags to be sent with messages (#51)
10
  - Add `mg_mutate_message_body` hook to allow other plugins to modify the message body before send
admin.php ADDED
@@ -0,0 +1,431 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * mailgun-wordpress-plugin - Sending mail from Wordpress using Mailgun
5
+ * Copyright (C) 2016 Mailgun, et al.
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation; either version 2 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along
18
+ * with this program; if not, write to the Free Software Foundation, Inc.,
19
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
+ */
21
+
22
+ class MailgunAdmin extends Mailgun
23
+ {
24
+ /**
25
+ * @var array Array of "safe" option defaults.
26
+ */
27
+ private $defaults;
28
+
29
+ /**
30
+ * Setup backend functionality in WordPress.
31
+ *
32
+ * @return none
33
+ *
34
+ * @since 0.1
35
+ */
36
+ public function __construct()
37
+ {
38
+ Mailgun::__construct();
39
+
40
+ // Load localizations if available
41
+ load_plugin_textdomain('mailgun', false, 'mailgun/languages');
42
+
43
+ // Activation hook
44
+ register_activation_hook($this->plugin_file, array(&$this, 'init'));
45
+
46
+ if (!defined('MAILGUN_USEAPI') || !MAILGUN_USEAPI) {
47
+ // Hook into admin_init and register settings and potentially register an admin_notice
48
+ add_action('admin_init', array(&$this, 'admin_init'));
49
+
50
+ // Activate the options page
51
+ add_action('admin_menu', array(&$this, 'admin_menu'));
52
+ }
53
+
54
+ // Register an AJAX action for testing mail sending capabilities
55
+ add_action('wp_ajax_mailgun-test', array(&$this, 'ajax_send_test'));
56
+ }
57
+
58
+ /**
59
+ * Initialize the default options during plugin activation.
60
+ *
61
+ * @return none
62
+ *
63
+ * @since 0.1
64
+ */
65
+ public function init()
66
+ {
67
+ $sitename = strtolower($_SERVER['SERVER_NAME']);
68
+ if (substr($sitename, 0, 4) == 'www.') {
69
+ $sitename = substr($sitename, 4);
70
+ }
71
+
72
+ $this->defaults = array(
73
+ 'useAPI' => '1',
74
+ 'apiKey' => '',
75
+ 'domain' => '',
76
+ 'username' => '',
77
+ 'password' => '',
78
+ 'secure' => '1',
79
+ 'track-clicks' => '',
80
+ 'track-opens' => '',
81
+ 'campaign-id' => '',
82
+ 'override-from' => '0',
83
+ 'tag' => $sitename,
84
+ );
85
+ if (!$this->options) {
86
+ $this->options = $this->defaults;
87
+ add_option('mailgun', $this->options);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Add the options page.
93
+ *
94
+ * @return none
95
+ *
96
+ * @since 0.1
97
+ */
98
+ public function admin_menu()
99
+ {
100
+ if (current_user_can('manage_options')) {
101
+ $this->hook_suffix = add_options_page(__('Mailgun', 'mailgun'), __('Mailgun', 'mailgun'), 'manage_options', 'mailgun', array(&$this, 'options_page'));
102
+ add_options_page(__('Mailgun Lists', 'mailgun'), __('Mailgun Lists', 'mailgun'), 'manage_options', 'mailgun-lists', array(&$this, 'lists_page'));
103
+ add_action("admin_print_scripts-{$this->hook_suffix}", array(&$this, 'admin_js'));
104
+ add_filter("plugin_action_links_{$this->plugin_basename}", array(&$this, 'filter_plugin_actions'));
105
+ add_action("admin_footer-{$this->hook_suffix}", array(&$this, 'admin_footer_js'));
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Enqueue javascript required for the admin settings page.
111
+ *
112
+ * @return none
113
+ *
114
+ * @since 0.1
115
+ */
116
+ public function admin_js()
117
+ {
118
+ wp_enqueue_script('jquery');
119
+ }
120
+
121
+ /**
122
+ * Output JS to footer for enhanced admin page functionality.
123
+ *
124
+ * @since 0.1
125
+ */
126
+ public function admin_footer_js()
127
+ {
128
+ ?>
129
+ <script type="text/javascript">
130
+ /* <![CDATA[ */
131
+ var mailgunApiOrNot = function() {
132
+ if (jQuery("#mailgun-api").val() == 1) {
133
+ jQuery(".mailgun-smtp").hide();
134
+ jQuery(".mailgun-api").show();
135
+ } else {
136
+ jQuery(".mailgun-api").hide();
137
+ jQuery(".mailgun-smtp").show();
138
+ }
139
+
140
+ }
141
+ var formModified = false;
142
+ jQuery().ready(function() {
143
+ mailgunApiOrNot();
144
+ jQuery('#mailgun-api').change(function() {
145
+ mailgunApiOrNot();
146
+ });
147
+ jQuery('#mailgun-test').click(function(e) {
148
+ e.preventDefault();
149
+ if ( formModified ) {
150
+ var doTest = confirm('<?php _e('The Mailgun plugin configuration has changed since you last saved. Do you wish to test anyway?\n\nClick "Cancel" and then "Save Changes" if you wish to save your changes.', 'mailgun'); ?>');
151
+ if ( ! doTest ) {
152
+ return false;
153
+ }
154
+ }
155
+ jQuery(this).val('<?php _e('Testing...', 'mailgun'); ?>');
156
+ jQuery("#mailgun-test-result").text('');
157
+ jQuery.get(
158
+ ajaxurl,
159
+ {
160
+ action: 'mailgun-test',
161
+ _wpnonce: '<?php echo wp_create_nonce(); ?>'
162
+ }
163
+ )
164
+ .complete(function() {
165
+ jQuery("#mailgun-test").val('<?php _e('Test Configuration', 'mailgun'); ?>');
166
+ })
167
+ .success(function(data) {
168
+ alert(
169
+ 'Mailgun ' + data.method + ' Test ' + data.message
170
+ + '; status "' + data.error + '"'
171
+ );
172
+ })
173
+ .error(function() {
174
+ alert('Mailgun Test <?php _e('Failure', 'mailgun'); ?>');
175
+ });
176
+ });
177
+ jQuery("#mailgun-form").change(function() {
178
+ formModified = true;
179
+ });
180
+ });
181
+ /* ]]> */
182
+ </script>
183
+ <?php
184
+
185
+ }
186
+
187
+ /**
188
+ * Output the options page.
189
+ *
190
+ * @return none
191
+ *
192
+ * @since 0.1
193
+ */
194
+ public function options_page()
195
+ {
196
+ if (!@include 'options-page.php') {
197
+ printf(__('<div id="message" class="updated fade"><p>The options page for the <strong>Mailgun</strong> plugin cannot be displayed. The file <strong>%s</strong> is missing. Please reinstall the plugin.</p></div>', 'mailgun'), dirname(__FILE__).'/options-page.php');
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Output the lists page.
203
+ *
204
+ * @return none
205
+ *
206
+ * @since 0.1
207
+ */
208
+ public function lists_page()
209
+ {
210
+ if (!@include 'lists-page.php') {
211
+ printf(__('<div id="message" class="updated fade"><p>The lists page for the <strong>Mailgun</strong> plugin cannot be displayed. The file <strong>%s</strong> is missing. Please reinstall the plugin.</p></div>', 'mailgun'), dirname(__FILE__).'/lists-page.php');
212
+ }
213
+ }
214
+
215
+ // /options-general.php?page=mailgun-lists
216
+
217
+ /**
218
+ * Wrapper function hooked into admin_init to register settings
219
+ * and potentially register an admin notice if the plugin hasn't
220
+ * been configured yet.
221
+ *
222
+ * @return none
223
+ *
224
+ * @since 0.1
225
+ */
226
+ public function admin_init()
227
+ {
228
+ $this->register_settings();
229
+ $apiKey = $this->get_option('apiKey');
230
+ $useAPI = $this->get_option('useAPI');
231
+ $password = $this->get_option('password');
232
+
233
+ add_action('admin_notices', array(&$this, 'admin_notices'));
234
+ }
235
+
236
+ /**
237
+ * Whitelist the mailgun options.
238
+ *
239
+ * @since 0.1
240
+ *
241
+ * @return none
242
+ */
243
+ public function register_settings()
244
+ {
245
+ register_setting('mailgun', 'mailgun', array(&$this, 'validation'));
246
+ }
247
+
248
+ /**
249
+ * Data validation callback function for options.
250
+ *
251
+ * @param array $options An array of options posted from the options page
252
+ *
253
+ * @return array
254
+ *
255
+ * @since 0.1
256
+ */
257
+ public function validation($options)
258
+ {
259
+ $apiKey = trim($options['apiKey']);
260
+ $username = trim($options['username']);
261
+ if (!empty($apiKey)) {
262
+ $pos = strpos($apiKey, 'key-');
263
+ if ($pos === false || $pos > 4) {
264
+ $apiKey = "key-{$apiKey}";
265
+ }
266
+
267
+ $pos = strpos($apiKey, 'api:');
268
+ if ($pos !== false && $pos == 0) {
269
+ $apiKey = substr($apiKey, 4);
270
+ }
271
+ $options['apiKey'] = $apiKey;
272
+ }
273
+
274
+ if (!empty($username)) {
275
+ $username = preg_replace('/@.+$/', '', $username);
276
+ $options['username'] = $username;
277
+ }
278
+
279
+ foreach ($options as $key => $value) {
280
+ $options[$key] = trim($value);
281
+ }
282
+
283
+ if (empty($options['override-from'])) {
284
+ $options['override-from'] = $this->defaults['override-from'];
285
+ }
286
+ // alternatively:
287
+ // foreach ($defaults as $key => $value) {
288
+ // if (empty($options[$key])) {
289
+ // $options[$key] = $value;
290
+ // }
291
+ // }
292
+
293
+ $this->options = $options;
294
+
295
+ return $options;
296
+ }
297
+
298
+ /**
299
+ * Function to output an admin notice when the plugin has not
300
+ * been configured yet.
301
+ *
302
+ * @return none
303
+ *
304
+ * @since 0.1
305
+ */
306
+ public function admin_notices()
307
+ {
308
+ $screen = get_current_screen();
309
+ if (!current_user_can('manage_options') || $screen->id == $this->hook_suffix
310
+ ) {
311
+ return;
312
+ }
313
+
314
+ if ((!$this->get_option('apiKey') && $this->get_option('useAPI') === '1')
315
+ || (!$this->get_option('password') && $this->get_option('useAPI') === '0')
316
+ ) {
317
+ ?>
318
+ <div id='mailgun-warning' class='notice notice-warning fade'><p><strong><?php _e('Mailgun is almost ready. ', 'mailgun'); ?></strong><?php printf(__('You must <a href="%1$s">configure Mailgun</a> for it to work.', 'mailgun'), menu_page_url('mailgun', false)); ?></p></div>
319
+ <?php
320
+
321
+ }
322
+
323
+ if ($this->get_option('override-from') === '1'
324
+ && (!$this->get_option('from-name')
325
+ || !$this->get_option('from-address'))
326
+ ) {
327
+ ?>
328
+ <div id='mailgun-warning' class='notice notice-warning fade'><p><strong><?php _e('Mailgun is almost ready. ', 'mailgun'); ?></strong><?php printf(__('"Override From" option requires that "From Name" and "From Address" be set to work properly! <a href="%1$s">Configure Mailgun now</a>.', 'mailgun'), menu_page_url('mailgun', false)); ?></p></div>
329
+ <?php
330
+
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Add a settings link to the plugin actions.
336
+ *
337
+ * @param array $links Array of the plugin action links
338
+ *
339
+ * @return array
340
+ *
341
+ * @since 0.1
342
+ */
343
+ public function filter_plugin_actions($links)
344
+ {
345
+ $settings_link = '<a href="'.menu_page_url('mailgun', false).'">'.__('Settings', 'mailgun').'</a>';
346
+ array_unshift($links, $settings_link);
347
+
348
+ return $links;
349
+ }
350
+
351
+ /**
352
+ * AJAX callback function to test mail sending functionality.
353
+ *
354
+ * @return string
355
+ *
356
+ * @since 0.1
357
+ */
358
+ public function ajax_send_test()
359
+ {
360
+ nocache_headers();
361
+ header('Content-Type: application/json');
362
+
363
+ if (!current_user_can('manage_options') || !wp_verify_nonce($_GET['_wpnonce'])) {
364
+ die(
365
+ json_encode(
366
+ array(
367
+ 'message' => __('Unauthorized', 'mailgun'),
368
+ 'method' => null,
369
+ 'error' => __('Unauthorized', 'mailgun'),
370
+ )
371
+ )
372
+ );
373
+ }
374
+
375
+ $useAPI = (defined('MAILGUN_USEAPI') && MAILGUN_USEAPI) ? MAILGUN_USEAPI : $this->get_option('useAPI');
376
+ $secure = (defined('MAILGUN_SECURE') && MAILGUN_SECURE) ? MAILGUN_SECURE : $this->get_option('secure');
377
+ if ((bool) $useAPI) {
378
+ $method = __('HTTP API', 'mailgun');
379
+ } else {
380
+ $method = ((bool) $secure) ? __('Secure SMTP', 'mailgun') : __('SMTP', 'mailgun');
381
+ }
382
+
383
+ $admin_email = get_option('admin_email');
384
+ $result = wp_mail(
385
+ $admin_email,
386
+ __('Mailgun WordPress Plugin Test', 'mailgun'),
387
+ 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 method used to send this email was: %s.", 'mailgun'), $method),
388
+ array('Content-Type: text/plain')
389
+ );
390
+
391
+ if ((bool) $useAPI) {
392
+ if (!function_exists('mg_api_last_error')) {
393
+ if (!include dirname(__FILE__).'/wp-mail-api.php') {
394
+ self::deactivate_and_die(dirname(__FILE__).'/wp-mail-api.php');
395
+ }
396
+ }
397
+
398
+ $error_msg = mg_api_last_error();
399
+ } else {
400
+ if (!function_exists('mg_smtp_last_error')) {
401
+ if (!include dirname(__FILE__).'/wp-mail-smtp.php') {
402
+ self::deactivate_and_die(dirname(__FILE__).'/wp-mail-smtp.php');
403
+ }
404
+ }
405
+
406
+ $error_msg = mg_smtp_last_error();
407
+ }
408
+
409
+ if ($result) {
410
+ die(
411
+ json_encode(
412
+ array(
413
+ 'message' => __('Success', 'mailgun'),
414
+ 'method' => $method,
415
+ 'error' => __('Success', 'mailgun'),
416
+ )
417
+ )
418
+ );
419
+ } else {
420
+ die(
421
+ json_encode(
422
+ array(
423
+ 'message' => __('Failure', 'mailgun'),
424
+ 'method' => $method,
425
+ 'error' => $error_msg,
426
+ )
427
+ )
428
+ );
429
+ }
430
+ }
431
+ }
includes/admin.php CHANGED
@@ -389,20 +389,8 @@ class MailgunAdmin extends Mailgun
389
  );
390
 
391
  if ((bool) $useAPI) {
392
- if (!function_exists('mg_api_last_error')) {
393
- if (!include dirname(__FILE__).'/wp-mail-api.php') {
394
- self::deactivate_and_die(dirname(__FILE__).'/wp-mail-api.php');
395
- }
396
- }
397
-
398
  $error_msg = mg_api_last_error();
399
  } else {
400
- if (!function_exists('mg_smtp_last_error')) {
401
- if (!include dirname(__FILE__).'/wp-mail-smtp.php') {
402
- self::deactivate_and_die(dirname(__FILE__).'/wp-mail-smtp.php');
403
- }
404
- }
405
-
406
  $error_msg = mg_smtp_last_error();
407
  }
408
 
389
  );
390
 
391
  if ((bool) $useAPI) {
 
 
 
 
 
 
392
  $error_msg = mg_api_last_error();
393
  } else {
 
 
 
 
 
 
394
  $error_msg = mg_smtp_last_error();
395
  }
396
 
includes/mg-filter.php CHANGED
@@ -240,11 +240,15 @@ function mg_parse_headers($headers = array())
240
  $name = trim($name);
241
  $value = trim($value);
242
 
243
- $new_headers[$name] = array(
 
 
 
 
244
  'value' => $value,
245
  'boundary' => $boundary,
246
  'parts' => $parts,
247
- );
248
  }
249
  }
250
 
@@ -268,10 +272,18 @@ function mg_dump_headers($headers = null)
268
  }
269
 
270
  $header_string = '';
271
- foreach ($headers as $name => $content) {
272
- // XXX - Is it actually okay to discard `parts` and `boundary`?
273
- $header_string .= sprintf("%s: %s\r\n", $name, $content['value']);
 
 
 
 
 
 
 
274
  }
275
 
276
  return $header_string;
277
  }
 
240
  $name = trim($name);
241
  $value = trim($value);
242
 
243
+ if ( !isset($new_headers[$name]) ) {
244
+ $new_headers[$name] = array();
245
+ }
246
+
247
+ array_push($new_headers[$name], array(
248
  'value' => $value,
249
  'boundary' => $boundary,
250
  'parts' => $parts,
251
+ ));
252
  }
253
  }
254
 
272
  }
273
 
274
  $header_string = '';
275
+ foreach ($headers as $name => $values) {
276
+ $header_string .= sprintf("%s: ", $name);
277
+ $header_values = array();
278
+
279
+ foreach ($values as $content) {
280
+ // XXX - Is it actually okay to discard `parts` and `boundary`?
281
+ array_push($header_values, $content['value']);
282
+ }
283
+
284
+ $header_string .= sprintf("%s\r\n", implode(", ", $header_values));
285
  }
286
 
287
  return $header_string;
288
  }
289
+
includes/wp-mail-api.php CHANGED
@@ -226,7 +226,7 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
226
  $body['recipient-variables'] = $rcpt_data['rcpt_vars'];
227
  }
228
 
229
- $body['o:tag'] = array();
230
  $body['o:tracking-clicks'] = !empty($mailgun['track-clicks']) ? $mailgun['track-clicks'] : 'no';
231
  $body['o:tracking-opens'] = empty($mailgun['track-opens']) ? 'no' : 'yes';
232
 
@@ -237,7 +237,7 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
237
  }
238
 
239
  // campaign-id now refers to a list of tags which will be appended to the site tag
240
- if (!empty($mailgun['campaign-id'])) {
241
  $tags = explode(',', str_replace(' ', '', $mailgun['campaign-id']));
242
  if (empty($body['o:tag'])) {
243
  $body['o:tag'] = $tags;
@@ -325,9 +325,6 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
325
 
326
  $payload = null;
327
 
328
- // Allow other plugins to apply body changes before writing the payload.
329
- $body = apply_filters('mg_mutate_message_body', $body);
330
-
331
  // Iterate through pre-built params and build payload:
332
  foreach ($body as $key => $value) {
333
  if (is_array($value)) {
@@ -348,9 +345,6 @@ function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
348
  }
349
  }
350
 
351
- // Allow other plugins to apply attachent changes before writing to the payload.
352
- $attachments = apply_filters('mg_mutate_attachments', $attachments);
353
-
354
  // If we have attachments, add them to the payload.
355
  if (!empty($attachments)) {
356
  $i = 0;
226
  $body['recipient-variables'] = $rcpt_data['rcpt_vars'];
227
  }
228
 
229
+ $body['o:tag'] = '';
230
  $body['o:tracking-clicks'] = !empty($mailgun['track-clicks']) ? $mailgun['track-clicks'] : 'no';
231
  $body['o:tracking-opens'] = empty($mailgun['track-opens']) ? 'no' : 'yes';
232
 
237
  }
238
 
239
  // campaign-id now refers to a list of tags which will be appended to the site tag
240
+ if (isset($mailgun['campaign-id'])) {
241
  $tags = explode(',', str_replace(' ', '', $mailgun['campaign-id']));
242
  if (empty($body['o:tag'])) {
243
  $body['o:tag'] = $tags;
325
 
326
  $payload = null;
327
 
 
 
 
328
  // Iterate through pre-built params and build payload:
329
  foreach ($body as $key => $value) {
330
  if (is_array($value)) {
345
  }
346
  }
347
 
 
 
 
348
  // If we have attachments, add them to the payload.
349
  if (!empty($attachments)) {
350
  $i = 0;
includes/wp-mail-smtp.php CHANGED
@@ -107,7 +107,7 @@ function mg_smtp_mail_filter(array $args)
107
  $mg_headers = mg_parse_headers($headers);
108
 
109
  // Filter the `From:` header
110
- $from_header = (isset($mg_headers['From'])) ? $mg_headers['From'] : null;
111
 
112
  list($from_name, $from_addr) = array(null, null);
113
  if (!is_null($from_header)) {
@@ -140,10 +140,11 @@ function mg_smtp_mail_filter(array $args)
140
  $from_addr = mg_detect_from_address($from_addr);
141
 
142
  $from_header['value'] = sprintf('%s <%s>', $from_name, $from_addr);
143
- $mg_headers['From'] = $from_header;
144
 
145
  // Header compaction
146
  $headers = mg_dump_headers($mg_headers);
147
 
148
  return compact('to', 'subject', 'message', 'headers', 'attachments');
149
  }
 
107
  $mg_headers = mg_parse_headers($headers);
108
 
109
  // Filter the `From:` header
110
+ $from_header = (isset($mg_headers['From'])) ? $mg_headers['From'][0] : null;
111
 
112
  list($from_name, $from_addr) = array(null, null);
113
  if (!is_null($from_header)) {
140
  $from_addr = mg_detect_from_address($from_addr);
141
 
142
  $from_header['value'] = sprintf('%s <%s>', $from_name, $from_addr);
143
+ $mg_headers['From'] = array($from_header);
144
 
145
  // Header compaction
146
  $headers = mg_dump_headers($mg_headers);
147
 
148
  return compact('to', 'subject', 'message', 'headers', 'attachments');
149
  }
150
+
mailgun.php CHANGED
@@ -4,7 +4,7 @@
4
  * Plugin Name: Mailgun
5
  * Plugin URI: http://wordpress.org/extend/plugins/mailgun/
6
  * Description: Mailgun integration for WordPress
7
- * Version: 1.5.8.2
8
  * Author: Mailgun
9
  * Author URI: http://www.mailgun.com/
10
  * License: GPLv2 or later
4
  * Plugin Name: Mailgun
5
  * Plugin URI: http://wordpress.org/extend/plugins/mailgun/
6
  * Description: Mailgun integration for WordPress
7
+ * Version: 1.5.8.3
8
  * Author: Mailgun
9
  * Author URI: http://www.mailgun.com/
10
  * License: GPLv2 or later
readme.txt CHANGED
@@ -4,8 +4,8 @@ Mailgun for WordPress
4
  Contributors: Mailgun, sivel, lookahead.io, m35dev
5
  Tags: mailgun, smtp, http, api, mail, email
6
  Requires at least: 3.3
7
- Tested up to: 4.7.1
8
- Stable tag: 1.5.8.2
9
  License: GPLv2 or later
10
 
11
 
@@ -101,16 +101,20 @@ MAILGUN_FROM_ADDRESS Type: string
101
 
102
  == Changelog ==
103
 
 
 
 
 
104
  = 1.5.8.2 (2017-02-27): =
105
- - Fix a bug causing empty tags to be sent with messages (#51)
106
- - Add `mg_mutate_message_body` hook to allow other plugins to modify the message body before send
107
- - Add `mg_mutate_attachments` hook to allow other plugins to modify the message attachments before send
108
- - Fix a bug causing the AJAX test to fail incorrectly.
109
 
110
  = 1.5.8.1 (2017-02-06): =
111
- - Fix "Undefined property: MailgunAdmin::$hook_suffix" (#48)
112
- - Fix "Undefined variable: from_name on every email process" (API and SMTP) (#49)
113
- - Admin code now loads only on admin user access
114
 
115
  = 1.5.8 (2017-01-23): =
116
  * Rewrite a large chunk of old SMTP code
4
  Contributors: Mailgun, sivel, lookahead.io, m35dev
5
  Tags: mailgun, smtp, http, api, mail, email
6
  Requires at least: 3.3
7
+ Tested up to: 4.8
8
+ Stable tag: 1.5.8.3
9
  License: GPLv2 or later
10
 
11
 
101
 
102
  == Changelog ==
103
 
104
+ = 1.5.8.3 (2017-06-13): =
105
+ - Fix a bug causing only the last header value to be used when multiple headers of the same type are specified (https://wordpress.org/support/topic/bug-with-mg_parse_headers/)
106
+ - Added `pt_BR` translations (thanks @emersonbroga)
107
+
108
  = 1.5.8.2 (2017-02-27): =
109
+ * Fix a bug causing empty tags to be sent with messages (#51)
110
+ * Add `mg_mutate_message_body` hook to allow other plugins to modify the message body before send
111
+ * Add `mg_mutate_attachments` hook to allow other plugins to modify the message attachments before send
112
+ * Fix a bug causing the AJAX test to fail incorrectly.
113
 
114
  = 1.5.8.1 (2017-02-06): =
115
+ * Fix "Undefined property: MailgunAdmin::$hook_suffix" (#48)
116
+ * Fix "Undefined variable: from_name on every email process" (API and SMTP) (#49)
117
+ * Admin code now loads only on admin user access
118
 
119
  = 1.5.8 (2017-01-23): =
120
  * Rewrite a large chunk of old SMTP code
wp-mail-api.php ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * mailgun-wordpress-plugin - Sending mail from Wordpress using Mailgun
5
+ * Copyright (C) 2016 Mailgun, et al.
6
+ *
7
+ * This program is free software; you can redistribute it and/or modify
8
+ * it under the terms of the GNU General Public License as published by
9
+ * the Free Software Foundation; either version 2 of the License, or
10
+ * (at your option) any later version.
11
+ *
12
+ * This program is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License along
18
+ * with this program; if not, write to the Free Software Foundation, Inc.,
19
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
+ */
21
+
22
+ // Include MG filter functions
23
+ if (!include dirname(__FILE__).'/mg-filter.php') {
24
+ Mailgun::deactivate_and_die(dirname(__FILE__).'/mg-filter.php');
25
+ }
26
+
27
+ /**
28
+ * mg_api_last_error is a compound getter/setter for the last error that was
29
+ * encountered during a Mailgun API call.
30
+ *
31
+ * @param string $error OPTIONAL
32
+ *
33
+ * @return string Last error that occurred.
34
+ *
35
+ * @since 1.5.0
36
+ */
37
+ function mg_api_last_error($error = null)
38
+ {
39
+ static $last_error;
40
+
41
+ if (null === $error) {
42
+ return $last_error;
43
+ } else {
44
+ $tmp = $last_error;
45
+ $last_error = $error;
46
+
47
+ return $tmp;
48
+ }
49
+ }
50
+
51
+ /*
52
+ * Wordpress filter to mutate a `To` header to use recipient variables.
53
+ * Uses the `mg_use_recipient_vars_syntax` filter to apply the actual
54
+ * change. Otherwise, just a list of `To` addresses will be returned.
55
+ *
56
+ * @param string|array $to_addrs Array or comma-separated list of email addresses to mutate.
57
+ *
58
+ * @return array Array containing list of `To` addresses and recipient vars array
59
+ *
60
+ * @since 1.5.7
61
+ */
62
+ add_filter('mg_mutate_to_rcpt_vars', 'mg_mutate_to_rcpt_vars_cb');
63
+ function mg_mutate_to_rcpt_vars_cb($to_addrs)
64
+ {
65
+ if (is_string($to_addrs)) {
66
+ $to_addrs = explode(',', $to_addrs);
67
+ }
68
+
69
+ if (has_filter('mg_use_recipient_vars_syntax')) {
70
+ $use_rcpt_vars = apply_filters('mg_use_recipient_vars_syntax', null);
71
+ if ($use_rcpt_vars) {
72
+ $vars = array();
73
+
74
+ $idx = 0;
75
+ foreach ($to_addrs as $addr) {
76
+ $rcpt_vars[$addr] = array('batch_msg_id' => $idx);
77
+ $idx++;
78
+ }
79
+
80
+ // TODO: Also add folding to prevent hitting the 998 char limit on headers.
81
+ return array(
82
+ 'to' => '%recipient%',
83
+ 'rcpt_vars' => json_encode($rcpt_vars),
84
+ );
85
+ }
86
+ }
87
+
88
+ return array(
89
+ 'to' => $to_addrs,
90
+ 'rcpt_vars' => null,
91
+ );
92
+ }
93
+
94
+ /**
95
+ * wp_mail function to be loaded in to override the core wp_mail function
96
+ * from wp-includes/pluggable.php.
97
+ *
98
+ * Based off of the core wp_mail function, but with modifications required to
99
+ * send email using the Mailgun HTTP API
100
+ *
101
+ * @param string|array $to Array or comma-separated list of email addresses to send message.
102
+ * @param string $subject Email subject
103
+ * @param string $message Message contents
104
+ * @param string|array $headers Optional. Additional headers.
105
+ * @param string|array $attachments Optional. Files to attach.
106
+ *
107
+ * @return bool Whether the email contents were sent successfully.
108
+ *
109
+ * @since 0.1
110
+ */
111
+ function wp_mail($to, $subject, $message, $headers = '', $attachments = array())
112
+ {
113
+ // Compact the input, apply the filters, and extract them back out
114
+ extract(apply_filters('wp_mail', compact('to', 'subject', 'message', 'headers', 'attachments')));
115
+
116
+ $mailgun = get_option('mailgun');
117
+ $apiKey = (defined('MAILGUN_APIKEY') && MAILGUN_APIKEY) ? MAILGUN_APIKEY : $mailgun['apiKey'];
118
+ $domain = (defined('MAILGUN_DOMAIN') && MAILGUN_DOMAIN) ? MAILGUN_DOMAIN : $mailgun['domain'];
119
+
120
+ if (empty($apiKey) || empty($domain)) {
121
+ return false;
122
+ }
123
+
124
+ if (!is_array($attachments)) {
125
+ $attachments = explode("\n", str_replace("\r\n", "\n", $attachments));
126
+ }
127
+
128
+ // Headers
129
+ if (empty($headers)) {
130
+ $headers = array();
131
+ } else {
132
+ if (!is_array($headers)) {
133
+ // Explode the headers out, so this function can take both
134
+ // string headers and an array of headers.
135
+ $tempheaders = explode("\n", str_replace("\r\n", "\n", $headers));
136
+ } else {
137
+ $tempheaders = $headers;
138
+ }
139
+ $headers = array();
140
+ $cc = array();
141
+ $bcc = array();
142
+
143
+ // If it's actually got contents
144
+ if (!empty($tempheaders)) {
145
+ // Iterate through the raw headers
146
+ foreach ((array) $tempheaders as $header) {
147
+ if (strpos($header, ':') === false) {
148
+ if (false !== stripos($header, 'boundary=')) {
149
+ $parts = preg_split('/boundary=/i', trim($header));
150
+ $boundary = trim(str_replace(array("'", '"'), '', $parts[1]));
151
+ }
152
+ continue;
153
+ }
154
+ // Explode them out
155
+ list($name, $content) = explode(':', trim($header), 2);
156
+
157
+ // Cleanup crew
158
+ $name = trim($name);
159
+ $content = trim($content);
160
+
161
+ switch (strtolower($name)) {
162
+ // Mainly for legacy -- process a From: header if it's there
163
+ case 'from':
164
+ if (strpos($content, '<') !== false) {
165
+ // So... making my life hard again?
166
+ $from_name = substr($content, 0, strpos($content, '<') - 1);
167
+ $from_name = str_replace('"', '', $from_name);
168
+ $from_name = trim($from_name);
169
+
170
+ $from_email = substr($content, strpos($content, '<') + 1);
171
+ $from_email = str_replace('>', '', $from_email);
172
+ $from_email = trim($from_email);
173
+ } else {
174
+ $from_email = trim($content);
175
+ }
176
+ break;
177
+ case 'content-type':
178
+ if (strpos($content, ';') !== false) {
179
+ list($type, $charset) = explode(';', $content);
180
+ $content_type = trim($type);
181
+ if (false !== stripos($charset, 'charset=')) {
182
+ $charset = trim(str_replace(array('charset=', '"'), '', $charset));
183
+ } elseif (false !== stripos($charset, 'boundary=')) {
184
+ $boundary = trim(str_replace(array('BOUNDARY=', 'boundary=', '"'), '', $charset));
185
+ $charset = '';
186
+ }
187
+ } else {
188
+ $content_type = trim($content);
189
+ }
190
+ break;
191
+ case 'cc':
192
+ $cc = array_merge((array) $cc, explode(',', $content));
193
+ break;
194
+ case 'bcc':
195
+ $bcc = array_merge((array) $bcc, explode(',', $content));
196
+ break;
197
+ default:
198
+ // Add it to our grand headers array
199
+ $headers[trim($name)] = trim($content);
200
+ break;
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ if (!isset($from_name)) {
207
+ $from_name = null;
208
+ }
209
+
210
+ if (!isset($from_email)) {
211
+ $from_email = null;
212
+ }
213
+
214
+ $from_name = mg_detect_from_name($from_name);
215
+ $from_email = mg_detect_from_address($from_email);
216
+
217
+ $body = array(
218
+ 'from' => "{$from_name} <{$from_email}>",
219
+ 'to' => $to,
220
+ 'subject' => $subject,
221
+ 'text' => $message,
222
+ );
223
+
224
+ $rcpt_data = apply_filters('mg_mutate_to_rcpt_vars', $to);
225
+ if (!is_null($rcpt_data['rcpt_vars'])) {
226
+ $body['recipient-variables'] = $rcpt_data['rcpt_vars'];
227
+ }
228
+
229
+ $body['o:tag'] = array();
230
+ $body['o:tracking-clicks'] = !empty($mailgun['track-clicks']) ? $mailgun['track-clicks'] : 'no';
231
+ $body['o:tracking-opens'] = empty($mailgun['track-opens']) ? 'no' : 'yes';
232
+
233
+ // this is the wordpress site tag
234
+ if (isset($mailgun['tag'])) {
235
+ $tags = explode(',', str_replace(' ', '', $mailgun['tag']));
236
+ $body['o:tag'] = $tags;
237
+ }
238
+
239
+ // campaign-id now refers to a list of tags which will be appended to the site tag
240
+ if (!empty($mailgun['campaign-id'])) {
241
+ $tags = explode(',', str_replace(' ', '', $mailgun['campaign-id']));
242
+ if (empty($body['o:tag'])) {
243
+ $body['o:tag'] = $tags;
244
+ } elseif (is_array($body['o:tag'])) {
245
+ $body['o:tag'] = array_merge($body['o:tag'], $tags);
246
+ } else {
247
+ $body['o:tag'] .= ','.$tags;
248
+ }
249
+ }
250
+
251
+ if (!empty($cc) && is_array($cc)) {
252
+ $body['cc'] = implode(', ', $cc);
253
+ }
254
+
255
+ if (!empty($bcc) && is_array($bcc)) {
256
+ $body['bcc'] = implode(', ', $bcc);
257
+ }
258
+
259
+ // If we are not given a Content-Type from the supplied headers, use
260
+ // text/html and *attempt* to strip tags and provide a text/plain
261
+ // version.
262
+ if (!isset($content_type)) {
263
+ // Try to figure out the content type with mime_content_type.
264
+ $tmppath = tempnam(sys_get_temp_dir(), 'mg');
265
+ $tmp = fopen($tmppath, 'w+');
266
+
267
+ fwrite($tmp, $message);
268
+ fclose($tmp);
269
+
270
+ // Get mime type with mime_content_type
271
+ $content_type = get_mime_content_type($tmppath, 'text/plain');
272
+
273
+ // Remove the tmpfile
274
+ unlink($tmppath);
275
+ }
276
+
277
+ // Allow external content type filter to function normally
278
+ if (has_filter('wp_mail_content_type')) {
279
+ $content_type = apply_filters(
280
+ 'wp_mail_content_type',
281
+ $content_type
282
+ );
283
+ }
284
+
285
+ if ('text/plain' === $content_type) {
286
+ $body['text'] = $message;
287
+ } else {
288
+ // Unknown Content-Type??
289
+ $body['text'] = $message;
290
+ $body['html'] = $message;
291
+ }
292
+
293
+ // If we don't have a charset from the input headers
294
+ if (!isset($charset)) {
295
+ $charset = get_bloginfo('charset');
296
+ }
297
+
298
+ // Set the content-type and charset
299
+ $charset = apply_filters('wp_mail_charset', $charset);
300
+ if (isset($headers['Content-Type'])) {
301
+ if (!strstr($headers['Content-Type'], 'charset')) {
302
+ $headers['Content-Type'] = rtrim($headers['Content-Type'], '; ')."; charset={$charset}";
303
+ }
304
+ }
305
+
306
+ // Set custom headers
307
+ if (!empty($headers)) {
308
+ foreach ((array) $headers as $name => $content) {
309
+ $body["h:{$name}"] = $content;
310
+ }
311
+
312
+ // TODO: Can we handle this?
313
+ //if ( false !== stripos( $content_type, 'multipart' ) && ! empty($boundary) )
314
+ // $phpmailer->AddCustomHeader( sprintf( "Content-Type: %s;\n\t boundary=\"%s\"", $content_type, $boundary ) );
315
+ }
316
+
317
+ /*
318
+ * Deconstruct post array and create POST payload.
319
+ * This entire routine is because wp_remote_post does
320
+ * not support files directly.
321
+ */
322
+
323
+ // First, generate a boundary for the multipart message.
324
+ $boundary = base_convert(uniqid('boundary', true), 10, 36);
325
+
326
+ $payload = null;
327
+
328
+ // Allow other plugins to apply body changes before writing the payload.
329
+ $body = apply_filters('mg_mutate_message_body', $body);
330
+
331
+ // Iterate through pre-built params and build payload:
332
+ foreach ($body as $key => $value) {
333
+ if (is_array($value)) {
334
+ $parent_key = $key;
335
+ foreach ($value as $key => $value) {
336
+ $payload .= '--'.$boundary;
337
+ $payload .= "\r\n";
338
+ $payload .= 'Content-Disposition: form-data; name="'.$parent_key."\"\r\n\r\n";
339
+ $payload .= $value;
340
+ $payload .= "\r\n";
341
+ }
342
+ } else {
343
+ $payload .= '--'.$boundary;
344
+ $payload .= "\r\n";
345
+ $payload .= 'Content-Disposition: form-data; name="'.$key.'"'."\r\n\r\n";
346
+ $payload .= $value;
347
+ $payload .= "\r\n";
348
+ }
349
+ }
350
+
351
+ // Allow other plugins to apply attachent changes before writing to the payload.
352
+ $attachments = apply_filters('mg_mutate_attachments', $attachments);
353
+
354
+ // If we have attachments, add them to the payload.
355
+ if (!empty($attachments)) {
356
+ $i = 0;
357
+ foreach ($attachments as $attachment) {
358
+ if (!empty($attachment)) {
359
+ $payload .= '--'.$boundary;
360
+ $payload .= "\r\n";
361
+ $payload .= 'Content-Disposition: form-data; name="attachment['.$i.']"; filename="'.basename($attachment).'"'."\r\n\r\n";
362
+ $payload .= file_get_contents($attachment);
363
+ $payload .= "\r\n";
364
+ $i++;
365
+ }
366
+ }
367
+ }
368
+
369
+ $payload .= '--'.$boundary.'--';
370
+
371
+ $data = array(
372
+ 'body' => $payload,
373
+ 'headers' => array(
374
+ 'Authorization' => 'Basic '.base64_encode("api:{$apiKey}"),
375
+ 'Content-Type' => 'multipart/form-data; boundary='.$boundary,
376
+ ),
377
+ );
378
+
379
+ $url = "https://api.mailgun.net/v3/{$domain}/messages";
380
+
381
+ // TODO: Mailgun only supports 1000 recipients per request, since we are
382
+ // overriding this function, let's add looping here to handle that
383
+ $response = wp_remote_post($url, $data);
384
+ if (is_wp_error($response)) {
385
+ // Store WP error in last error.
386
+ mg_api_last_error($response->get_error_message());
387
+
388
+ return false;
389
+ }
390
+
391
+ $response_code = wp_remote_retrieve_response_code($response);
392
+ $response_body = json_decode(wp_remote_retrieve_body($response));
393
+
394
+ // Mailgun API should *always* return a `message` field, even when
395
+ // $response_code != 200, so a lack of `message` indicates something
396
+ // is broken.
397
+ if ((int) $response_code != 200 && !isset($response_body->message)) {
398
+ // Store response code and HTTP response message in last error.
399
+ $response_message = wp_remote_retrieve_response_message($response);
400
+ $errmsg = "$response_code - $response_message";
401
+ mg_api_last_error($errmsg);
402
+
403
+ return false;
404
+ }
405
+
406
+ // Not sure there is any additional checking that needs to be done here, but why not?
407
+ if ($response_body->message != 'Queued. Thank you.') {
408
+ mg_api_last_error($response_body->message);
409
+
410
+ return false;
411
+ }
412
+
413
+ return true;
414
+ }