PluginProbe
Mail Queue / 1.4
Mail Queue v1.4
1.6.1 1.6.0 1.5.1 trunk 1.0 1.1 1.2 1.3 1.3.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.5.0
mail-queue / mail-queue.php

mail-queue.php in Mail Queue 1.4, at mail-queue.php

375 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Plugin Name: Mail Queue
5 * Plugin URI: https://www.webdesign-muenchen.de/wordpress-plugin-mail-queue/
6 * Description: Take Control and improve Security of wp_mail(). Queue outgoing emails, log their sendings and get alerted, if your website wants to send more emails than usual.
7 * Version: 1.4
8 * Requires at least: 5.9
9 * Requires PHP: 7.2
10 * Author: WDM
11 * Author URI: https://www.webdesign-muenchen.de
12 * License: GPLv3 or later
13 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
14 */
15
16 if (!defined('ABSPATH')) { exit; }
17
18
19 /* ***************************************************************
20 PLUGIN DEFAULT SETTINGS
21 **************************************************************** */
22 function wdm_wpma_get_settings() {
23 $defaults = array(
24 'enabled' => '0',
25 'alert_enabled' => '0',
26 'email' => get_option('admin_email'),
27 'email_amount' => '10',
28 'queue_amount' => '1',
29 'queue_interval' => '5',
30 'queue_interval_unit' => 'minutes',
31 'clear_queue' => '14',
32 'tableName' => 'mail_queue',
33 'triggercount' => 0,
34 );
35 $args = get_option('wdm_wpma_settings');
36 $options = wp_parse_args($args,$defaults);
37
38 if ($options['queue_interval_unit'] == 'seconds') {
39 $options['queue_interval'] = intval($options['queue_interval']);
40 if ($options['queue_interval'] < 10) { $options['queue_interval'] = 10; } // Minimum Interval 10 Seconds
41 } else {
42 $options['queue_interval'] = intval($options['queue_interval']) * 60;
43 }
44
45 $options['clear_queue'] = intval($options['clear_queue']) * 24;
46 return $options;
47 }
48
49
50
51
52
53 /* ***************************************************************
54 Overwrite wp_mail() if Plugin enabled and no Cron is running
55 **************************************************************** */
56 $wdm_wpma_mailid = 0;
57 $wdm_wpma_options = wdm_wpma_get_settings(); // Get Settings
58
59 if ($wdm_wpma_options['enabled'] == '1' && wp_doing_cron() == false) {
60 add_filter( 'pre_wp_mail' , 'wdm_wpma_prewpmail', 10, 2);
61 }
62
63 // pre WP Mail Filter
64 function wdm_wpma_prewpmail($null, $atts) {
65
66 global $wpdb, $wdm_wpma_options;
67
68 // Mail Variables
69 $to = $atts['to'];
70 $subject = $atts['subject'];
71 $message = $atts['message'];
72 $headers = $atts['headers'];
73 $attachments = $atts['attachments'];
74 $status = 'queue';
75
76 // Make sure that $headers always is an array
77 if ($headers) {
78 if (!is_array($headers)) {
79 $headers = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
80 }
81 } else {
82 $headers = [];
83 }
84
85 // Loop through email headers
86 // - Instant Sending or Prio Mail?
87 // - Track if ContentType header is set
88 $hasContentTypeHeader = false;
89 $hasFromHeader = false;
90 foreach($headers as $index => $val) {
91 $val = trim($val);
92 if (preg_match("#^X-Mail-Queue-Prio: +Instant *$#i",$val)) {
93 array_splice($headers,$index,1);
94 $status = 'instant';
95 break;
96 } else if (preg_match("#^X-Mail-Queue-Prio: +High *$#i",$val)) {
97 array_splice($headers,$index,1);
98 $status = 'high';
99 break;
100 } else if (preg_match('#^Content-Type:#i',$val)) {
101 $hasContentTypeHeader = true;
102 } else if (preg_match('#^From:#i',$val)) {
103 $hasFromHeader = true;
104 }
105 }
106
107 // For all emails that are stored in the queue to be sent later:
108 // Store custom filtered values in headers if available.
109 // Support the following hooks used in wp_mail:
110 // - wp_mail_content_type
111 // - wp_mail_charset
112 // - wp_mail_from
113 // - wp_mail_from_name
114 if ($status !== 'instant') {
115 if (!$hasContentTypeHeader) {
116 $contentType = apply_filters('wp_mail_content_type','text/plain');
117 if ( $contentType ) {
118 if (stripos($contentType,'multipart') === false) {
119 $charset = apply_filters('wp_mail_charset',get_bloginfo('charset'));
120 } else {
121 $charset = '';
122 }
123 $headers[] = 'Content-Type: '.$contentType.($charset ? '; charset="'.$charset.'"' : '');
124 }
125 }
126 if (!$hasFromHeader) {
127 $from_Email = apply_filters('wp_mail_from','');
128 if ($from_Email) {
129 $fromName = apply_filters('wp_mail_from_name','');
130 if ($fromName) {
131 $headers[] = $fromName.' <'.$from_Email.'>';
132 } else {
133 $headers[] = $from_Email;
134 }
135 }
136 }
137 }
138
139
140 // Write email in Queue
141 $tableName = $wpdb->prefix.$wdm_wpma_options['tableName'];
142 $data = array(
143 'timestamp'=> current_time('mysql',false),
144 'recipient'=> maybe_serialize($to),
145 'subject'=> $subject,
146 'message'=> $message,
147 'status' => $status,
148 'attachments' => ''
149 );
150 if (isset($headers) && $headers) { $data['headers'] = maybe_serialize($headers); }
151
152 // store attachments in /attachments/ Folder, to address them later
153 if (isset($attachments) && $attachments && $attachments != '') {
154
155 $subfolder = time().'-'.rand(0,999999);
156 $foldercreated = wp_mkdir_p(plugin_dir_path(__FILE__).'attachments/'.$subfolder);
157 if (!$foldercreated) {
158 error_log('Could not create Subfolder for Email attachment');
159 $data['info'] = 'Error: Could not store attachments';
160 } else {
161 if (!is_array($attachments)) { $attachments = array($attachments); }
162 $newattachments = array();
163 global $wp_filesystem;
164 if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){
165 include_once(ABSPATH . 'wp-admin/includes/file.php');
166 WP_Filesystem();
167 }
168 foreach($attachments as $item) {
169 $newfile = plugin_dir_path(__FILE__).'attachments/'.$subfolder.'/'.basename($item);
170 $wp_filesystem->copy($item,$newfile);
171 array_push($newattachments,$newfile);
172 }
173 $data['attachments'] = maybe_serialize($newattachments);
174 }
175 }
176 $wpdb->insert($tableName,$data);
177
178 if ($status == 'instant') {
179 return null;
180 } else {
181 // Fake Submit by returning 'True'
182 return true;
183 }
184
185 }
186
187
188
189 // show wp_mail() errors
190 function wdm_wpma_mail_failed( $wp_error ) {
191 global $wpdb,$wdm_wpma_options,$wdm_wpma_mailid;
192 if (isset($wdm_wpma_mailid) && $wdm_wpma_mailid != 0) {
193 $tableName = $wpdb->prefix.$wdm_wpma_options['tableName'];
194 $wpMailFailedError = isset( $wp_error->errors ) && isset( $wp_error->errors['wp_mail_failed'][0] ) ? implode( '; ', $wp_error->errors['wp_mail_failed'] ) : '<em>Unknown</em>';
195 $wpdb->update($tableName,array('timestamp'=>current_time('mysql',false),'status'=>'error', 'info'=>$wpMailFailedError),array('id'=>intval($wdm_wpma_mailid)),array('%s', '%s', '%s'),'%d');
196 }
197 return error_log(print_r($wp_error, true));
198 }
199 add_action('wp_mail_failed','wdm_wpma_mail_failed',10,1);
200
201
202
203
204 /* ***************************************************************
205 CRON
206 **************************************************************** */
207 function wdm_wpma_search_mail_from_queue() {
208
209 global $wpdb,$wdm_wpma_options,$wdm_wpma_mailid;
210 if ($wdm_wpma_options['enabled'] != '1') { return; }
211 $tableName = $wpdb->prefix.$wdm_wpma_options['tableName'];
212
213 // Triggercount to avoid multiple runs
214 $wdm_wpma_options['triggercount']++;
215 if ($wdm_wpma_options['triggercount'] > 1) { return; }
216
217 // Mails in Queue?
218 $mailjobs = $wpdb->get_results("SELECT * FROM `$tableName` WHERE `status` = 'queue' OR `status` = 'high' ORDER BY `status` ASC, `id`",'ARRAY_A');
219 $mailsInQueue = is_array($mailjobs) ? count($mailjobs) : 0;
220
221 // Alert Admin, if too many mails in the Queue.
222 if ($wdm_wpma_options['alert_enabled'] == '1' && $mailsInQueue > intval($wdm_wpma_options['email_amount'])) {
223
224 // Last alerts older than 6 hours?
225 $alerts = $wpdb->get_results("SELECT * FROM `$tableName` WHERE `status` = 'alert' AND `timestamp` > NOW() - INTERVAL 6 HOUR",'ARRAY_A');
226
227 // If no alerts, then send one
228 if (!$alerts) {
229 $alertMessage = 'Hi,';
230 $alertMessage .= "\n\n";
231 $alertMessage .= 'this is an important message from your WordPress website '.esc_url(get_option('siteurl')).'.';
232 $alertMessage .= "\n";
233 $alertMessage .= "\n".'The Mail Queue Plugin has detected that your website tries to send more emails than expected (currently '.$mailsInQueue.').';
234 $alertMessage .= "\n".'Please take a close look at the email queue, because it contains more messages than the specified limit.';
235 $alertMessage .= "\n";
236 $alertMessage .= "\n".'In case this is the usual amount of emails, you can adjust the threshold for alerts in the settings of your Mail Queue Plugin.';
237 $alertMessage .= "\n\n";
238 $alertMessage .= "-- ";
239 $alertMessage .= "\n";
240 $alertMessage .= admin_url();
241 $alertSubject = '🔴 WordPress Mail Queue Alert - '.esc_html(get_option('blogname'));
242 $data = array(
243 'timestamp'=> current_time('mysql',false),
244 'recipient'=> sanitize_email($wdm_wpma_options['email']),
245 'subject' => $alertSubject,
246 'message' => $alertMessage,
247 'status' => 'alert',
248 'info' => json_encode([
249 'in_queue' => strval( $mailsInQueue ),
250 'email_amount' => intval($wdm_wpma_options['email_amount']),
251 'queue_amount' => intval($wdm_wpma_options['queue_amount']),
252 'queue_interval' => intval($wdm_wpma_options['queue_interval']),
253 ]),
254 );
255 $wpdb->insert($tableName,$data);
256 wp_mail($wdm_wpma_options['email'],$alertSubject,$alertMessage);
257 }
258
259 }
260
261 // Send Mails in Queue
262 if ($mailsInQueue > 0) {
263 $results = array_slice($mailjobs,0,intval($wdm_wpma_options['queue_amount']));
264 if ($results && count($results) > 0) {
265 foreach($results as $index => $item) {
266 if ($item['recipient'] && $item['recipient'] != '') { $to = maybe_unserialize($item['recipient']); } else { $to = $wdm_wpma_options['email']; $item['subject'] = 'ERROR // '.$item['subject']; }
267 if ($item['headers'] && $item['headers'] != '') { $headers = maybe_unserialize($item['headers']); } else { $headers = ''; }
268 if ($item['attachments'] && $item['attachments'] != '') { $attachments = maybe_unserialize($item['attachments']); } else { $attachments = ''; }
269 $wdm_wpma_mailid = $item['id'];
270
271 remove_filter('pre_wp_mail','wdm_wpma_prewpmail',10);
272 $sendstatus = wp_mail($to,$item['subject'],$item['message'],$headers,$attachments); // Finally sends the email for real
273 add_filter( 'pre_wp_mail' , 'wdm_wpma_prewpmail', 10, 2);
274 if ($sendstatus) {
275 $wpdb->update($tableName,array('timestamp'=>current_time('mysql',false),'status'=>'sent'),array('id'=>$item['id']),'%s','%d');
276 }
277 if (is_array($attachments)) {
278 global $wp_filesystem;
279 if ( ! is_a( $wp_filesystem, 'WP_Filesystem_Base') ){
280 include_once(ABSPATH . 'wp-admin/includes/file.php');
281 WP_Filesystem();
282 }
283 $attachmentfolder = pathinfo($attachments[0]);
284 $wp_filesystem->delete($attachmentfolder['dirname'],true,'d');
285 }
286 }
287 }
288 }
289
290 // Delete old logs
291 $wpdb->query("DELETE FROM `$tableName` WHERE `status` != 'queue' AND `timestamp` < NOW() - INTERVAL ".esc_sql($wdm_wpma_options['clear_queue'])." HOUR");
292
293 }
294 add_action('wp_mail_queue_hook','wdm_wpma_search_mail_from_queue');
295
296 // Custom Cron Interval
297 function wdm_wpma_cron_interval( $schedules ) {
298 global $wdm_wpma_options;
299 $schedules['wdm_wpma_interval'] = array(
300 'interval' => $wdm_wpma_options['queue_interval'],
301 'display' => esc_html__('WP Mail Queue'), );
302 return $schedules;
303 }
304 add_filter('cron_schedules','wdm_wpma_cron_interval');
305
306 // Set or Remove Cron
307 $next_wpma_cron_timestamp = wp_next_scheduled('wp_mail_queue_hook');
308 if ($next_wpma_cron_timestamp && $wdm_wpma_options['enabled'] != '1') {
309 wp_unschedule_event($next_wpma_cron_timestamp,'wp_mail_queue_hook');
310 } else if (!$next_wpma_cron_timestamp && $wdm_wpma_options['enabled'] == '1') {
311 wp_schedule_event(time(),'wdm_wpma_interval','wp_mail_queue_hook');
312 }
313
314
315
316 /* ***************************************************************
317 Install/Uninstall
318 **************************************************************** */
319
320
321 /* Delete plugin options and database table */
322 function wdm_wpma_uninstall () {
323 global $wpdb;
324
325 $optionName = 'wdm_wpma_settings';
326 delete_option( $optionName );
327
328 $tableName = $wpdb->prefix.'mail_queue';
329 $wpdb->query( "DROP TABLE IF EXISTS $tableName" );
330 }
331
332 /* Delete Cron when Plugin deactivated */
333 function wdm_wpma_deactivate() {
334 wp_clear_scheduled_hook( 'wp_mail_queue_hook' );
335 }
336
337 /* Create MySQL Table on Activation: https://codex.wordpress.org/Creating_Tables_with_Plugins */
338 function wdm_wpma_installDatabaseTables() {
339 global $wpdb;
340
341 $tableName = $wpdb->prefix.'mail_queue';
342
343 $charset_collate = $wpdb->get_charset_collate();
344
345 $sql = "CREATE TABLE $tableName (
346 id mediumint(9) NOT NULL AUTO_INCREMENT,
347 timestamp TIMESTAMP NOT NULL,
348 status varchar(55) DEFAULT '' NOT NULL,
349 recipient varchar(255) DEFAULT '' NOT NULL,
350 subject varchar(255) DEFAULT '' NOT NULL,
351 message text NOT NULL,
352 headers text NOT NULL,
353 attachments text NOT NULL,
354 info varchar(255) DEFAULT '' NOT NULL,
355 PRIMARY KEY (id)
356 ) $charset_collate;";
357
358 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
359 dbDelta( $sql );
360
361 register_uninstall_hook( __FILE__, 'wdm_wpma_uninstall' );
362
363 register_deactivation_hook( __FILE__, 'wdm_wpma_deactivate' );
364
365 }
366 register_activation_hook( __FILE__, 'wdm_wpma_installDatabaseTables' );
367
368
369 /* ***************************************************************
370 Options Page
371 **************************************************************** */
372 if (is_admin()) {
373 require_once( plugin_dir_path( __FILE__ ) . 'mail-queue-options.php' );
374 }
375