PluginProbe
Mail Queue / 1.4.3
Mail Queue v1.4.3
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.3, at mail-queue.php

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