| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { exit; } |
| 4 |
|
| 5 |
/* *************************************************************** |
| 6 |
Options Page |
| 7 |
**************************************************************** */ |
| 8 |
|
| 9 |
function wdm_wpma_actionlinks ( $actions ) { |
| 10 |
$links = array( |
| 11 |
'<a href="'.admin_url('admin.php?page=wdm_wpma_mail_queue').'">Settings</a>', |
| 12 |
'<a href="'.admin_url('admin.php?page=wdm_wpma_mail_queue-tab-log').'">Log</a>', |
| 13 |
'<a href="'.admin_url('admin.php?page=wdm_wpma_mail_queue-tab-queue').'">Queue</a>', |
| 14 |
'<a href="'.admin_url('admin.php?page=wdm_wpma_mail_queue-tab-faq').'">FAQs</a>', |
| 15 |
); |
| 16 |
return array_merge($actions,$links ); |
| 17 |
} |
| 18 |
add_filter('plugin_action_links_mail-queue/mail-queue.php','wdm_wpma_actionlinks'); |
| 19 |
|
| 20 |
// Options Page |
| 21 |
function wdm_wpma_settings_page_menuitem() { |
| 22 |
add_menu_page('Mail Queue','Mail Queue','manage_options','wdm_wpma_mail_queue','wdm_wpma_settings_page','data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTc4IiBoZWlnaHQ9IjE3OCIgdmlld0JveD0iMCAwIDE3OCAxNzgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNzguNzI0MSA1LjI1MzA5Qzc2LjkwNzQgNC43MDgxIDc0Ljk0MDEgNS4wNTQxMyA3My40MTg0IDYuMTg2MjlDNzEuODk2OCA3LjMxODQ1IDcxIDkuMTAzNDEgNzEgMTEuMDAwMVYxNjdDNzEgMTY4Ljg5NyA3MS44OTY4IDE3MC42ODIgNzMuNDE4NCAxNzEuODE0Qzc0Ljk0MDEgMTcyLjk0NiA3Ni45MDc0IDE3My4yOTIgNzguNzI0MSAxNzIuNzQ3TDE1OC43MjQgMTQ4Ljc0N0MxNjEuMjYyIDE0Ny45ODYgMTYzIDE0NS42NSAxNjMgMTQzVjM1QzE2MyAzMi4zNTA0IDE2MS4yNjIgMzAuMDE0NSAxNTguNzI0IDI5LjI1MzFMNzguNzI0MSA1LjI1MzA5Wk04NS43ODg3IDIyLjM4NDZDODcuNzg1NCAyMS40Mzk0IDkwLjE3MDMgMjIuMjkxOSA5MS4xMTU0IDI0LjI4ODdMMTIyLjg1MiA5MS4zMzc4TDE0My4zMzQgNDQuNDAwMkMxNDQuMjE3IDQyLjM3NTUgMTQ2LjU3NSA0MS40NTAzIDE0OC42IDQyLjMzMzhDMTUwLjYyNSA0My4yMTc0IDE1MS41NSA0NS41NzUgMTUwLjY2NiA0Ny41OTk4TDEyNi42NjYgMTAyLjZDMTI2LjAzOSAxMDQuMDM3IDEyNC42MjkgMTA0Ljk3NiAxMjMuMDYxIDEwNUMxMjEuNDkzIDEwNS4wMjQgMTIwLjA1NiAxMDQuMTI5IDExOS4zODUgMTAyLjcxMUw4My44ODQ2IDI3LjcxMTNDODIuOTM5NCAyNS43MTQ2IDgzLjc5MTkgMjMuMzI5NyA4NS43ODg3IDIyLjM4NDZaIiBmaWxsPSIjYTdhYWFkIi8+CjxwYXRoIGQ9Ik00OSAxM0M1Mi4zMTM3IDEzIDU1IDE1LjY4NjMgNTUgMTlWMTU5QzU1IDE2Mi4zMTQgNTIuMzEzNyAxNjUgNDkgMTY1QzQ1LjY4NjMgMTY1IDQzIDE2Mi4zMTQgNDMgMTU5VjE5QzQzIDE1LjY4NjMgNDUuNjg2MyAxMyA0OSAxM1oiIGZpbGw9IiNhN2FhYWQiLz4KPHBhdGggZD0iTTIxIDIxQzI0LjMxMzcgMjEgMjcgMjMuNjg2MyAyNyAyN1YxNTFDMjcgMTU0LjMxNCAyNC4zMTM3IDE1NyAyMSAxNTdDMTcuNjg2MyAxNTcgMTUgMTU0LjMxNCAxNSAxNTFWMjdDMTUgMjMuNjg2MyAxNy42ODYzIDIxIDIxIDIxWiIgZmlsbD0iI2E3YWFhZCIvPgo8L3N2Zz4='); |
| 23 |
add_submenu_page('wdm_wpma_mail_queue','Settings','Settings','manage_options','wdm_wpma_mail_queue','wdm_wpma_settings_page'); |
| 24 |
$hook_log = add_submenu_page('wdm_wpma_mail_queue','Log','Log','manage_options','wdm_wpma_mail_queue-tab-log','wdm_wpma_settings_page'); |
| 25 |
$hook_queue = add_submenu_page('wdm_wpma_mail_queue','Queue','Queue','manage_options','wdm_wpma_mail_queue-tab-queue','wdm_wpma_settings_page'); |
| 26 |
add_submenu_page('wdm_wpma_mail_queue','FAQ','FAQ','manage_options','wdm_wpma_mail_queue-tab-faq','wdm_wpma_settings_page'); |
| 27 |
if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) { |
| 28 |
add_submenu_page('wdm_wpma_mail_queue','Cron Information','Cron Information','manage_options','wdm_wpma_mail_queue-tab-croninfo','wdm_wpma_settings_page'); |
| 29 |
} |
| 30 |
// Bulk actions run on load-{page}: that hook fires before admin-header.php produces |
| 31 |
// any output, so the post-action redirect (PRG) can still send headers safely. |
| 32 |
add_action('load-'.$hook_log,'wdm_wpma_handle_list_table_actions'); |
| 33 |
add_action('load-'.$hook_queue,'wdm_wpma_handle_list_table_actions'); |
| 34 |
} |
| 35 |
add_action('admin_menu','wdm_wpma_settings_page_menuitem'); |
| 36 |
|
| 37 |
// Add test email hook |
| 38 |
function wdm_wpma_add_testmail_action() { |
| 39 |
|
| 40 |
if ( ! current_user_can( 'manage_options' ) ) { |
| 41 |
wp_die( 'Sorry, you are not allowed to access this page.' ); |
| 42 |
} |
| 43 |
|
| 44 |
check_admin_referer( 'wdm_wpma_add_testmail' ); |
| 45 |
|
| 46 |
global $wpdb; |
| 47 |
$wdm_wpma_options = wdm_wpma_options(); |
| 48 |
|
| 49 |
$tableName = wdm_wpma_table(); |
| 50 |
$data = array( |
| 51 |
'timestamp' => current_time('mysql',false), |
| 52 |
'recipient' => $wdm_wpma_options['email'], |
| 53 |
'subject' => 'Testmail #'.time(), |
| 54 |
'message' => 'This is just a test email sent by the Mail Queue plugin.', |
| 55 |
'status' => 'queue', |
| 56 |
); |
| 57 |
$wpdb->insert($tableName,$data); |
| 58 |
|
| 59 |
wp_safe_redirect(admin_url('admin.php?page=wdm_wpma_mail_queue-tab-queue&testmail_added=1')); |
| 60 |
exit; |
| 61 |
} |
| 62 |
add_action('admin_post_wdm_wpma_add_testmail', 'wdm_wpma_add_testmail_action'); |
| 63 |
|
| 64 |
function wdm_wpma_settings_page_assets () { |
| 65 |
global $wdm_wpma_version; |
| 66 |
$screen = get_current_screen(); |
| 67 |
if ( preg_match( '#wdm_wpma_mail_queue#', $screen->base ) ) { |
| 68 |
wp_enqueue_style( 'wdm_wpma_style', plugins_url( 'assets/css/admin.css', __FILE__ ), /*deps*/array(), /*ver*/$wdm_wpma_version ); |
| 69 |
wp_enqueue_script( 'wdm_wpma_script', plugins_url( 'assets/js/wdm-wpma-admin.js', __FILE__ ), /*deps*/array( 'jquery' ), /*ver*/$wdm_wpma_version, /*in_footer*/true ); |
| 70 |
wp_add_inline_script( 'wdm_wpma_script', wdm_wpma_settings_page_inline_script(), 'before' ); |
| 71 |
} |
| 72 |
} |
| 73 |
add_action( 'admin_enqueue_scripts', 'wdm_wpma_settings_page_assets' ); |
| 74 |
|
| 75 |
// Options Page Script |
| 76 |
function wdm_wpma_settings_page_inline_script () { |
| 77 |
$d = ''; |
| 78 |
$d .= '( function ( global ) {'; |
| 79 |
$d .= '"use strict";'; |
| 80 |
$d .= 'const wpma = global.wpma = global.wpma || {};'; |
| 81 |
$d .= 'wpma.restUrl = "'.esc_url( wp_make_link_relative( rest_url() ) ).'";'; |
| 82 |
$d .= 'wpma.restNonce = "'.esc_html( wp_create_nonce( 'wp_rest' ) ).'";'; |
| 83 |
$d .= '}) ( this );'; |
| 84 |
return $d; |
| 85 |
} |
| 86 |
|
| 87 |
function wdm_wpma_get_pause_on_alert_notice_message() { |
| 88 |
if (!wdm_wpma_is_pause_on_alert_active()) { return ''; } |
| 89 |
|
| 90 |
return 'The Queue was paused automatically because an alert was triggered. It will stay paused until you change the status manually.'; |
| 91 |
} |
| 92 |
|
| 93 |
function wdm_wpma_render_pause_on_alert_notice() { |
| 94 |
$message = wdm_wpma_get_pause_on_alert_notice_message(); |
| 95 |
if (!$message) { return; } |
| 96 |
|
| 97 |
echo '<div class="notice notice-warning"><p><b>The Queue is currently auto-paused.</b> <br />'.esc_html($message).'</p></div>'; |
| 98 |
} |
| 99 |
|
| 100 |
// Runs the list-table bulk actions on load-{page} of the Log and Queue pages, |
| 101 |
// i.e. before any page output — see wdm_wpma_settings_page_menuitem(). |
| 102 |
function wdm_wpma_handle_list_table_actions() { |
| 103 |
if ( ! current_user_can( 'manage_options' ) ) { return; } |
| 104 |
$table = new wdm_wpma_Log_Table(); |
| 105 |
$table->process_bulk_action(); |
| 106 |
} |
| 107 |
|
| 108 |
// PRG helper: after a bulk action always redirect back to a plugin page, carrying |
| 109 |
// the result counts as query args for wdm_wpma_render_bulk_action_notices(). |
| 110 |
function wdm_wpma_bulk_action_redirect( $page, $action, $count_ok, $count_error ) { |
| 111 |
$url = add_query_arg( |
| 112 |
array( |
| 113 |
'page' => $page, |
| 114 |
'wpma_notice' => $action, |
| 115 |
'wpma_ok' => intval($count_ok), |
| 116 |
'wpma_error' => intval($count_error), |
| 117 |
), |
| 118 |
admin_url('admin.php') |
| 119 |
); |
| 120 |
wp_safe_redirect( $url ); |
| 121 |
exit; |
| 122 |
} |
| 123 |
|
| 124 |
// Renders the result notices after a bulk action (delete / resend / sendnow). |
| 125 |
// The counts arrive as query args set by the PRG redirect in process_bulk_action(). |
| 126 |
function wdm_wpma_render_bulk_action_notices() { |
| 127 |
if ( ! isset($_GET['wpma_notice']) || ! isset($_GET['page']) ) { return; } |
| 128 |
if ( strpos( sanitize_key($_GET['page']), 'wdm_wpma_mail_queue' ) !== 0 ) { return; } |
| 129 |
if ( ! current_user_can( 'manage_options' ) ) { return; } |
| 130 |
|
| 131 |
$action = sanitize_key($_GET['wpma_notice']); |
| 132 |
$ok = isset($_GET['wpma_ok']) ? max( 0, intval($_GET['wpma_ok']) ) : 0; |
| 133 |
$error = isset($_GET['wpma_error']) ? max( 0, intval($_GET['wpma_error']) ) : 0; |
| 134 |
|
| 135 |
$success = ''; |
| 136 |
$failure = ''; |
| 137 |
if ($action == 'delete') { |
| 138 |
if ($ok > 0) { $success = $ok.' item(s) have been deleted.'; } |
| 139 |
} else if ($action == 'resend') { |
| 140 |
if ($ok > 0) { $success = $ok.' email(s) have been put again into the <a href="admin.php?page=wdm_wpma_mail_queue-tab-queue">Queue</a>.'; } |
| 141 |
// Covers both skip reasons: attachments gone, and rows that aren't resendable |
| 142 |
// at all (already queued/sending, queue bookkeeping, or no longer existing). |
| 143 |
if ($error > 0) { $failure = $error.' email(s) could not be resent. Only already sent or failed emails without attachments can be resent (attachments are deleted after the send attempt).'; } |
| 144 |
} else if ($action == 'sendnow') { |
| 145 |
if ($ok > 0) { $success = $ok.' email(s) have been sent.'; } |
| 146 |
if ($error > 0) { $failure = $error.' email(s) could not be sent.'; } |
| 147 |
} |
| 148 |
|
| 149 |
if ($success) { echo '<div class="notice notice-success is-dismissible"><p>'.$success.'</p></div>'; } |
| 150 |
if ($failure) { echo '<div class="notice notice-error is-dismissible"><p>'.esc_html($failure).'</p></div>'; } |
| 151 |
} |
| 152 |
add_action('admin_notices','wdm_wpma_render_bulk_action_notices'); |
| 153 |
|
| 154 |
|
| 155 |
// Options Page Settings |
| 156 |
function wdm_wpma_settings_page() { |
| 157 |
|
| 158 |
// Only Admins |
| 159 |
if ( ! current_user_can( 'manage_options' ) ) { return; } |
| 160 |
|
| 161 |
// Settings |
| 162 |
$wdm_wpma_options = wdm_wpma_options(); |
| 163 |
|
| 164 |
// The active tab is identified by the ?page=... URL parameter set by WordPress |
| 165 |
// for each registered admin page (see add_menu_page / add_submenu_page calls above). |
| 166 |
$tab = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; |
| 167 |
|
| 168 |
echo '<div class="wrap">'; |
| 169 |
|
| 170 |
// Options Header |
| 171 |
echo '<h1 class="wdm-wpma-title"><img class="wdm-wpma-logo" src="'.esc_url(plugins_url('assets/img/mail-queue-logo-wordmark.svg', __FILE__)).'" alt="Mail Queue" width="308" height="56" /></h1>'; |
| 172 |
|
| 173 |
if ($tab != 'wdm_wpma_mail_queue-tab-croninfo' ) { |
| 174 |
if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) { |
| 175 |
echo '<div class="notice notice-warning notice-large">'; |
| 176 |
$url = esc_url(get_option('siteurl').'/wp-cron.php'); |
| 177 |
echo '<p><strong>Please note:</strong><br />Your normal WP Cron is disabled. Please make sure you\'re running the Cron manually by calling <a href="'.$url.'" target="_blank">'.$url.'</a> every couple of minutes.</p>'; |
| 178 |
echo '<p><a href="?page=wdm_wpma_mail_queue-tab-croninfo">More information</a></p>'; |
| 179 |
echo '</div>'; |
| 180 |
} |
| 181 |
} |
| 182 |
wdm_wpma_settings_page_navi($tab); // Tabs |
| 183 |
|
| 184 |
// Options Page Content |
| 185 |
if ($tab == 'wdm_wpma_mail_queue') { |
| 186 |
echo '<form action="options.php" method="post">'; |
| 187 |
settings_fields('wdm_wpma_settings'); |
| 188 |
do_settings_sections('wdm_wpma_settings_page'); |
| 189 |
submit_button(); |
| 190 |
echo '</form>'; |
| 191 |
} else if ($tab == 'wdm_wpma_mail_queue-tab-log') { |
| 192 |
// method="get": search/filter/pagination state lives in the URL so it |
| 193 |
// survives pagination; the hidden page field keeps us on this tab. |
| 194 |
echo '<form method="get">'; |
| 195 |
echo '<input type="hidden" name="page" value="wdm_wpma_mail_queue-tab-log" />'; |
| 196 |
$logtable = new wdm_wpma_Log_Table(); |
| 197 |
$logtable->prepare_items(); |
| 198 |
$logtable->display(); |
| 199 |
echo '</form>'; |
| 200 |
} else if ($tab == 'wdm_wpma_mail_queue-tab-queue') { |
| 201 |
$next_cron_timestamp = wp_next_scheduled('wp_mail_queue_hook'); |
| 202 |
if ($wdm_wpma_options['enabled'] === 'paused') { |
| 203 |
// The auto-pause case already shows a warning via wdm_wpma_render_pause_on_alert_notice() |
| 204 |
// (hooked to admin_notices, rendered above the page), so only the manual pause needs a notice here. |
| 205 |
if (!wdm_wpma_is_pause_on_alert_active()) { |
| 206 |
echo '<div class="notice notice-warning"><p><b>The Queue is currently paused.</b> <br />New emails enter the queue but are not being sent. Re-enable the Queue in the <a href="admin.php?page=wdm_wpma_mail_queue">Settings</a> to start sending emails again.</p></div>'; |
| 207 |
} |
| 208 |
} else if ($next_cron_timestamp) { |
| 209 |
if ($next_cron_timestamp > time()) { |
| 210 |
echo '<div class="notice notice-success"><p>Next Sending will be triggered in '.esc_html(human_time_diff($next_cron_timestamp)).' at '.esc_html(wp_date('H:i',$next_cron_timestamp)).'.</p></div>'; |
| 211 |
} |
| 212 |
} else { |
| 213 |
echo '<div class="notice notice-error"><p><b>The Queue is not enabled at the moment.</b> <br />Enable it in the <a href="admin.php?page=wdm_wpma_mail_queue">Settings</a>.</p></div>'; |
| 214 |
} |
| 215 |
if ( isset($_GET['testmail_added']) ) { |
| 216 |
echo '<div class="notice notice-success is-dismissible"><p>The test email has been added to the queue.</p></div>'; |
| 217 |
} |
| 218 |
|
| 219 |
// method="get": search/filter/pagination state lives in the URL so it |
| 220 |
// survives pagination; the hidden page field keeps us on this tab. |
| 221 |
echo '<form method="get">'; |
| 222 |
echo '<input type="hidden" name="page" value="wdm_wpma_mail_queue-tab-queue" />'; |
| 223 |
$queuetable = new wdm_wpma_Log_Table(); |
| 224 |
$queuetable->prepare_items(); |
| 225 |
$queuetable->display(); |
| 226 |
echo '</form>'; |
| 227 |
} else if ($tab == 'wdm_wpma_mail_queue-tab-faq') { |
| 228 |
echo '<div class="wdm-wpma-box">'; |
| 229 |
echo '<h3>How does this Plugin work?</h3>'; |
| 230 |
echo '<p>When the Mail Queue is <b>enabled</b>, this plugin intercepts the <a target="_blank" href="https://developer.wordpress.org/reference/functions/wp_mail/"><i>wp_mail()</i></a> function. Instead of sending emails immediately, it stores them in the database and sends them step by step with a delay during the <i>WP Cron</i>.</p>'; |
| 231 |
echo '<p>The plugin offers three states:</p>'; |
| 232 |
echo '<p><b>Enabled</b>: emails are added to the queue and sent gradually.<br />'; |
| 233 |
echo '<b>Paused</b>: emails are still added to the queue, but no queued emails are sent until you enable the queue again.<br />'; |
| 234 |
echo '<b>Disabled</b>: the plugin does not intercept emails and has no effect on outgoing mail.</p>'; |
| 235 |
echo '<p>Current state: '; |
| 236 |
if ($wdm_wpma_options['enabled'] === '1') { |
| 237 |
echo '<b class="wdm-wpma-ok">The plugin is enabled</b> All emails sent through <a target="_blank" href="https://developer.wordpress.org/reference/functions/wp_mail/"><i>wp_mail()</i></a> are added to the <a href="admin.php?page=wdm_wpma_mail_queue-tab-queue">Queue</a> and sent gradually.'; |
| 238 |
} elseif ($wdm_wpma_options['enabled'] === 'paused') { |
| 239 |
echo '<b class="wdm-wpma-warning">The plugin is paused</b>. New emails are still added to the queue, but queued emails are not being sent at the moment.'; |
| 240 |
} else { |
| 241 |
echo '<b>The plugin is disabled</b>. The plugin currently has no effect on outgoing emails.'; |
| 242 |
} |
| 243 |
echo '</p>'; |
| 244 |
echo '</div>'; |
| 245 |
|
| 246 |
echo '<div class="wdm-wpma-box">'; |
| 247 |
echo '<h3>Does this plugin change the way <b>HOW</b> emails are sent?</h3>'; |
| 248 |
echo '<p>No, don\'t worry. This plugin only affects <b>WHEN</b> emails are sent, not how. It delays the sending (by the Queue), nonetheless all emails are sent through the standard <a target="_blank" href="https://developer.wordpress.org/reference/functions/wp_mail/"><i>wp_mail()</i></a> function.</p>'; |
| 249 |
echo '<p>If you use SMTP for sending, or an external service like Mailgun, everything will still work as expected.</p>'; |
| 250 |
echo '</div>'; |
| 251 |
|
| 252 |
echo '<div class="wdm-wpma-box">'; |
| 253 |
echo '<h3>Does this plugin work, if I have a Caching Plugin installed? E.g. <i>W3 Total Cache</i> or similar?</h3>'; |
| 254 |
echo '<p>If you\'re using a Caching plugin like <i>W3 Total Cache</i>, <i>WP Rocket</i> or any other caching solution which generates static html-files and serves them to visitors, you\'ll have to make sure you\'re calling the <a href="'.esc_url(get_option('siteurl')).'/wp-cron.php" target="_blank">wp-cron file</a> manually every couple of minutes.</p>'; |
| 255 |
echo '<p>Otherwise your normal WP Cron wouldn\'t be called as often as it should be and scheduled messages would be sent with big delays.</p>'; |
| 256 |
echo '</div>'; |
| 257 |
|
| 258 |
echo '<div class="wdm-wpma-box">'; |
| 259 |
echo '<h3>What about Proxy-Caching, e.g. NGINX?</h3>'; |
| 260 |
echo '<p>Same situation here. Please make sure you\'re calling the <a href="'.esc_url(get_option('siteurl')).'/wp-cron.php" target="_blank">WordPress Cron</a> by an external service or your webhoster every couple of minutes.</p>'; |
| 261 |
echo '</div>'; |
| 262 |
|
| 263 |
echo '<div class="wdm-wpma-box">'; |
| 264 |
echo '<h3>My form builder supports attachments. What about them?</h3>'; |
| 265 |
echo '<p>You are covered. All attachments are stored temporarily in the queue until they are sent along with their corresponding emails.</p>'; |
| 266 |
echo '</div>'; |
| 267 |
|
| 268 |
echo '<div class="wdm-wpma-box">'; |
| 269 |
echo '<h3>What are Queue alerts?</h3>'; |
| 270 |
echo '<p>Queue alerts are a simple and effective way to improve the security of your WordPress installation.</p>'; |
| 271 |
echo '<p>Imagine your website starts sending spam through <a target="_blank" href="https://developer.wordpress.org/reference/functions/wp_mail/"><i>wp_mail()</i></a>. The Mail Queue would fill up quickly instead of sending everything at once. This gives you time to react, avoid a lot of trouble and can reduce the damage significantly.</p>'; |
| 272 |
echo '<p>Queue alerts warn you when the queue grows longer than usual. You configure in the settings at which threshold you want to be alerted. This gives you the chance to review the queue and investigate whether something unusual is happening on the website.</p>'; |
| 273 |
echo '<p>Current state: '; |
| 274 |
if ($wdm_wpma_options['alert_enabled'] === '1') { |
| 275 |
echo '<b class="wdm-wpma-ok">Alerts are enabled</b> If more than '.esc_html($wdm_wpma_options['email_amount']).' emails are waiting in the Queue, WordPress will send an alert email to <i>'.esc_html($wdm_wpma_options['email']).'</i>.'; |
| 276 |
} else { |
| 277 |
echo '<b>Alerting is disabled</b>. No alerts will be sent.'; |
| 278 |
} |
| 279 |
echo '</p>'; |
| 280 |
echo '<p>Optionally, you can enable <b>Auto-Pause on Alert</b>. In that case the queue stops sending emails when an alert is triggered until you change the queue status manually.</p>'; |
| 281 |
echo '<p>Current State: '; |
| 282 |
if ($wdm_wpma_options['alert_enabled'] === '1' && $wdm_wpma_options['pause_on_alert'] === '1') { |
| 283 |
echo '<b class="wdm-wpma-ok">Auto-Pause on Alert is enabled</b>. The Queue will be paused automatically when an alert is triggered.'; |
| 284 |
} else { |
| 285 |
echo '<b>Auto-Pause on Alert</b> is disabled. The Queue will not be paused automatically when an alert is triggered.'; |
| 286 |
} |
| 287 |
echo '</p>'; |
| 288 |
echo '<p>Please note: This plugin sends at most one alert every six hours while the queue remains above the configured threshold.</p>'; |
| 289 |
echo '</div>'; |
| 290 |
|
| 291 |
echo '<div class="wdm-wpma-box">'; |
| 292 |
echo '<h3>Can I pause the queue without disabling the plugin?</h3>'; |
| 293 |
echo '<p>Yes. You can set the Mail Queue status to <b>Paused</b> in the settings.</p>'; |
| 294 |
echo '<p>When paused, the plugin still intercepts <a target="_blank" href="https://developer.wordpress.org/reference/functions/wp_mail/"><i>wp_mail()</i></a> and stores outgoing emails in the queue, but no queued emails are sent until you change the status back to <b>Enabled</b>.</p>'; |
| 295 |
echo '<p>This is useful if you want to temporarily stop outgoing delivery without disabling the plugin completely.</p>'; |
| 296 |
echo '</div>'; |
| 297 |
|
| 298 |
echo '<div class="wdm-wpma-box">'; |
| 299 |
echo '<h3>Can I add emails with a high priority to the queue?</h3>'; |
| 300 |
echo '<p>Yes, you can add the custom <i>`X-Mail-Queue-Prio`</i> header set to <i>`High`</i> to your email. High priority emails are still processed through the normal Mail Queue cycle, but they are sent before normal queued emails.</p>'; |
| 301 |
echo '<p><b>Example 1 (add priority to WooCommerce emails):</b></p>'; |
| 302 |
echo '<pre><code>add_filter(\'woocommerce_mail_callback_params\',function ( $array ) { |
| 303 |
$prio_header = \'X-Mail-Queue-Prio: High\'; |
| 304 |
if (is_array($array[3])) { |
| 305 |
$array[3][] = $prio_header; |
| 306 |
} else { |
| 307 |
$array[3] .= $array[3] ? "\r\n" : \'\'; |
| 308 |
$array[3] .= $prio_header; |
| 309 |
} |
| 310 |
return $array; |
| 311 |
},10,1);</code></pre>'; |
| 312 |
echo '<p><b>Example 2 (add priority to Contact Form 7 form emails):</b></p>'; |
| 313 |
echo '<p>When editing a form in Contact Form 7 just add an additional line to the <i>`Additional Headers`</i> field under the <i>`Mail`</i> tab panel.</p>'; |
| 314 |
echo '<pre><code>X-Mail-Queue-Prio: High</code></pre>'; |
| 315 |
echo '</div>'; |
| 316 |
|
| 317 |
echo '<div class="wdm-wpma-box">'; |
| 318 |
echo '<h3>Can I send emails <i>instantly</i> without going through the queue?</h3>'; |
| 319 |
echo '<p>Yes, this is possible (if you absolutely need to do this).</p>'; |
| 320 |
echo '<p>For this you can add the custom <i>`X-Mail-Queue-Prio`</i> header set to <i>`Instant`</i> to your email. These emails are sent immediately and bypass the queue. They still appear in the Mail Queue log so that their delivery remains visible.</p>'; |
| 321 |
echo '<p>Mind that this is a potential security risk and should be considered carefully. Please use only as an exception.</p>'; |
| 322 |
echo '<p><b>Example 1 (instantly send WooCommerce emails):</b></p>'; |
| 323 |
echo '<pre><code>add_filter(\'woocommerce_mail_callback_params\',function ( $array ) { |
| 324 |
$prio_header = \'X-Mail-Queue-Prio: Instant\'; |
| 325 |
if (is_array($array[3])) { |
| 326 |
$array[3][] = $prio_header; |
| 327 |
} else { |
| 328 |
$array[3] .= $array[3] ? "\r\n" : \'\'; |
| 329 |
$array[3] .= $prio_header; |
| 330 |
} |
| 331 |
return $array; |
| 332 |
},10,1);</code></pre>'; |
| 333 |
echo '<p><b>Example 2 (instantly send Contact Form 7 form emails):</b></p>'; |
| 334 |
echo '<p>When editing a form in Contact Form 7 just add an additional line to the <i>`Additional Headers`</i> field under the <i>`Mail`</i> tab panel.</p>'; |
| 335 |
echo '<pre><code>X-Mail-Queue-Prio: Instant</code></pre>'; |
| 336 |
echo '</div>'; |
| 337 |
|
| 338 |
echo '<div class="wdm-wpma-box">'; |
| 339 |
echo '<h3>Can I send queued emails immediately from the backend?</h3>'; |
| 340 |
echo '<p>Yes. In the <a href="admin.php?page=wdm_wpma_mail_queue-tab-queue">Queue</a> tab you can select one or more queued emails and use the <b>Send now</b> bulk action.</p>'; |
| 341 |
echo '<p>This sends the selected queued emails immediately without waiting for the next WP-Cron cycle.</p>'; |
| 342 |
echo '<p>The action is only available for queued emails that are still waiting in the queue.</p>'; |
| 343 |
echo '</div>'; |
| 344 |
|
| 345 |
echo '<div class="wdm-wpma-box">'; |
| 346 |
echo '<h3>What are queue events in the log?</h3>'; |
| 347 |
echo '<p>In addition to email entries the Mail Queue log also shows queue events.</p>'; |
| 348 |
echo '<p>These entries document important queue state changes, for example when the queue was enabled, paused, disabled, or auto-paused automatically after an alert.</p>'; |
| 349 |
echo '<p>They are not outgoing emails. They are informational log entries to help you understand what happened and when.</p>'; |
| 350 |
echo '</div>'; |
| 351 |
|
| 352 |
echo '<div class="wdm-wpma-box">'; |
| 353 |
echo '<h3>How are WordPress password reset emails handled?</h3>'; |
| 354 |
echo '<p>WordPress password reset emails are handled automatically by the plugin.</p>'; |
| 355 |
echo '<p>Normally they are prioritized so they are sent before regular queued emails. If the Mail Queue is paused, password reset emails are sent instantly to reduce the risk of locking users out of their accounts.</p>'; |
| 356 |
echo '<p>You do not need to add a custom code snippet for this behavior.</p>'; |
| 357 |
echo '</div>'; |
| 358 |
|
| 359 |
echo '<div class="wdm-wpma-box">'; |
| 360 |
echo '<h3>Want to put a test email into the Queue?</h3>'; |
| 361 |
echo '<form method="post" action="'.esc_url( admin_url('admin-post.php') ).'">'; |
| 362 |
echo '<input type="hidden" name="action" value="wdm_wpma_add_testmail" />'; |
| 363 |
wp_nonce_field('wdm_wpma_add_testmail'); |
| 364 |
echo '<p><button type="submit" class="button">Sure! Put a Test Email for '.esc_html($wdm_wpma_options['email']).' into the Queue</button></p>'; |
| 365 |
echo '</form>'; |
| 366 |
echo '</div>'; |
| 367 |
|
| 368 |
} else if ($tab == 'wdm_wpma_mail_queue-tab-croninfo') { |
| 369 |
echo '<div class="wdm-wpma-box">'; |
| 370 |
echo '<h3>Information: Your common WP Cron is disabled</h3>'; |
| 371 |
echo '<p>It looks like you deactivated the WP-Cron with <i>define(\'DISABLE_WP_CRON\', true)</i>.</p>'; |
| 372 |
$url = esc_url(get_option('siteurl').'/wp-cron.php'); |
| 373 |
echo '<p>In general, this is no problem at all. We just want to remind you to make sure you\'re running the Cron manually by calling <a href="'.$url.'" target="_blank">'.$url.'</a> every couple of minutes.</p>'; |
| 374 |
echo '</div>'; |
| 375 |
|
| 376 |
|
| 377 |
if (function_exists('_get_cron_array')) { |
| 378 |
$next_tasks = _get_cron_array(); |
| 379 |
if ($next_tasks) { |
| 380 |
$tasks_in_past = false; |
| 381 |
$tasks_of_mailqueue_in_past = false; |
| 382 |
foreach($next_tasks as $key => $val) { |
| 383 |
if (time() > intval($key) + intval($wdm_wpma_options['queue_interval'])) { |
| 384 |
if (is_array($val) && array_key_exists('wp_mail_queue_hook', $val)) { $tasks_of_mailqueue_in_past = intval($key); } |
| 385 |
$tasks_in_past = true; |
| 386 |
} |
| 387 |
} |
| 388 |
if ($tasks_in_past) { |
| 389 |
echo '<div class="wdm-wpma-box">'; |
| 390 |
echo '<h3>Attention: It seems that your WP-Cron is not running. There are some jobs waiting to be completed.</h3>'; |
| 391 |
if ($tasks_of_mailqueue_in_past) { |
| 392 |
echo '<p><b>The Queue has not been able to run for '.esc_html(human_time_diff($tasks_of_mailqueue_in_past,time())).'.</b></p>'; |
| 393 |
} |
| 394 |
echo '</div>'; |
| 395 |
} |
| 396 |
} |
| 397 |
} |
| 398 |
|
| 399 |
|
| 400 |
} |
| 401 |
|
| 402 |
echo '</div>'; |
| 403 |
|
| 404 |
} |
| 405 |
|
| 406 |
if(!class_exists('WP_List_Table')){ |
| 407 |
require_once( ABSPATH . 'wp-admin/includes/screen.php' ); |
| 408 |
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' ); |
| 409 |
} |
| 410 |
|
| 411 |
class wdm_wpma_Log_Table extends WP_List_Table { |
| 412 |
|
| 413 |
const PER_PAGE = 50; |
| 414 |
|
| 415 |
// Log view: one page of rows (paginated + filtered per request), newest first. |
| 416 |
// Terminal/log rows only — the queue-side states (queue/high and the transient |
| 417 |
// 'sending' claim) are excluded so an in-flight row never leaks onto the log tab. |
| 418 |
// Returns array( $items, $total_matching_rows ). |
| 419 |
public function get_log() { |
| 420 |
return $this->query_view( "`status` != 'queue' AND `status` != 'high' AND `status` != 'sending'", '`timestamp` DESC' ); |
| 421 |
} |
| 422 |
|
| 423 |
// Queue view: one page of rows (paginated + filtered per request), high prio first. |
| 424 |
// Includes 'sending' (a row a dispatcher has claimed and is mid-sending) so an |
| 425 |
// in-flight — or, after a crash mid-send, a stuck — row stays visible here. |
| 426 |
// Returns array( $items, $total_matching_rows ). |
| 427 |
public function get_queue() { |
| 428 |
return $this->query_view( "`status` = 'queue' OR `status` = 'high' OR `status` = 'sending'", '`status` ASC, `timestamp` ASC' ); |
| 429 |
} |
| 430 |
|
| 431 |
// Statuses that can appear on the current tab. Doubles as the allowlist the |
| 432 |
// status filter is validated against (an out-of-range value is ignored). |
| 433 |
private function tab_statuses() { |
| 434 |
$page = isset($_GET['page']) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; |
| 435 |
if ( $page === 'wdm_wpma_mail_queue-tab-queue' ) { |
| 436 |
// 'sending' = a row a dispatcher has claimed and is mid-sending (transient); |
| 437 |
// it belongs to the queue side so it stays visible/filterable while in flight. |
| 438 |
return array( 'queue', 'high', 'sending' ); |
| 439 |
} |
| 440 |
return array( 'sent', 'error', 'alert', 'event', 'instant' ); |
| 441 |
} |
| 442 |
|
| 443 |
// Active free-text search term (matched against recipient + subject); '' when none. |
| 444 |
private function get_active_search() { |
| 445 |
if ( ! isset($_REQUEST['s']) ) { return ''; } |
| 446 |
return trim( sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) ); |
| 447 |
} |
| 448 |
|
| 449 |
// Active status filter, validated against the current tab; '' when none/invalid. |
| 450 |
private function get_active_status_filter() { |
| 451 |
if ( ! isset($_GET['filter_status']) ) { return ''; } |
| 452 |
$status = sanitize_key( wp_unslash( $_GET['filter_status'] ) ); |
| 453 |
return in_array( $status, $this->tab_statuses(), /*strict*/true ) ? $status : ''; |
| 454 |
} |
| 455 |
|
| 456 |
// Active date-range bound ('from'|'to') as Y-m-d, format-validated; '' when none/invalid. |
| 457 |
private function get_active_date_filter( $which ) { |
| 458 |
$key = ( $which === 'from' ) ? 'filter_date_from' : 'filter_date_to'; |
| 459 |
if ( empty($_GET[$key]) ) { return ''; } |
| 460 |
$val = sanitize_text_field( wp_unslash( $_GET[$key] ) ); |
| 461 |
return preg_match( '/^\d{4}-\d{2}-\d{2}$/', $val ) ? $val : ''; |
| 462 |
} |
| 463 |
|
| 464 |
// Builds the WHERE clause + prepare() args from $base_where (the tab's fixed |
| 465 |
// status scope) plus the active search/filter request params. |
| 466 |
// Returns array( $where_sql, $args ). $where_sql always has a base fragment, |
| 467 |
// so $args may be empty (no user filters active). |
| 468 |
private function build_list_where( $base_where ) { |
| 469 |
global $wpdb; |
| 470 |
$where = array( '('.$base_where.')' ); |
| 471 |
$args = array(); |
| 472 |
|
| 473 |
$search = $this->get_active_search(); |
| 474 |
if ( $search !== '' ) { |
| 475 |
// Bodies are intentionally not searched — they aren't loaded into the list query. |
| 476 |
$like = '%'.$wpdb->esc_like( $search ).'%'; |
| 477 |
$where[] = '(`recipient` LIKE %s OR `subject` LIKE %s)'; |
| 478 |
$args[] = $like; |
| 479 |
$args[] = $like; |
| 480 |
} |
| 481 |
|
| 482 |
$status = $this->get_active_status_filter(); |
| 483 |
if ( $status !== '' ) { |
| 484 |
$where[] = '`status` = %s'; |
| 485 |
$args[] = $status; |
| 486 |
} |
| 487 |
|
| 488 |
$date_from = $this->get_active_date_filter('from'); |
| 489 |
if ( $date_from !== '' ) { |
| 490 |
$where[] = '`timestamp` >= %s'; |
| 491 |
$args[] = $date_from.' 00:00:00'; |
| 492 |
} |
| 493 |
$date_to = $this->get_active_date_filter('to'); |
| 494 |
if ( $date_to !== '' ) { |
| 495 |
$where[] = '`timestamp` <= %s'; |
| 496 |
$args[] = $date_to.' 23:59:59'; |
| 497 |
} |
| 498 |
|
| 499 |
return array( implode( ' AND ', $where ), $args ); |
| 500 |
} |
| 501 |
|
| 502 |
// Runs the count + single-page query for a view. The message body is |
| 503 |
// deliberately NOT selected (it can be large mediumtext and is lazy-loaded |
| 504 |
// via REST); only its byte length is needed for the list. This keeps memory |
| 505 |
// bounded even when the table holds tens of thousands of large rows (a spam |
| 506 |
// flood is exactly when this page gets opened). |
| 507 |
// Returns array( $items, $total_matching_rows ). |
| 508 |
private function query_view( $base_where, $order_by ) { |
| 509 |
global $wpdb; |
| 510 |
$tableName = wdm_wpma_table(); |
| 511 |
list( $where_sql, $where_args ) = $this->build_list_where( $base_where ); |
| 512 |
|
| 513 |
$count_sql = "SELECT COUNT(*) FROM `$tableName` WHERE $where_sql"; |
| 514 |
$total_items = (int) ( $where_args |
| 515 |
? $wpdb->get_var( $wpdb->prepare( $count_sql, $where_args ) ) |
| 516 |
: $wpdb->get_var( $count_sql ) ); |
| 517 |
|
| 518 |
$offset = ( $this->get_pagenum() - 1 ) * self::PER_PAGE; |
| 519 |
|
| 520 |
$select = "SELECT `id`,`timestamp`,`status`,`recipient`,`subject`,`headers`,`attachments`,`info`,LENGTH(`message`) AS `message_length`"; |
| 521 |
$page_sql = "$select FROM `$tableName` WHERE $where_sql ORDER BY $order_by LIMIT %d OFFSET %d"; |
| 522 |
$page_args = array_merge( $where_args, array( self::PER_PAGE, $offset ) ); |
| 523 |
$items = $wpdb->get_results( $wpdb->prepare( $page_sql, $page_args ), 'ARRAY_A' ); |
| 524 |
|
| 525 |
return array( is_array($items) ? $items : array(), $total_items ); |
| 526 |
} |
| 527 |
|
| 528 |
public function get_columns() { |
| 529 |
$columns = array( |
| 530 |
'cb' => '<label><span class="screen-reader-text">Select all</span><input class="wdm-wpma-select-all" type="checkbox"></label>', |
| 531 |
'timestamp' => 'Time', |
| 532 |
'status' => 'Status', |
| 533 |
'recipient' => 'Recipient', |
| 534 |
'subject' => 'Subject', |
| 535 |
'message' => 'Message', |
| 536 |
'headers' => 'Headers', |
| 537 |
'attachments' => 'Attachments', |
| 538 |
); |
| 539 |
return $columns; |
| 540 |
} |
| 541 |
|
| 542 |
public function prepare_items() { |
| 543 |
$type = isset($_GET['page']) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; |
| 544 |
$columns = $this->get_columns(); |
| 545 |
$hidden = array(); |
| 546 |
$sortable = array(); |
| 547 |
$this->_column_headers = array($columns, $hidden, $sortable); |
| 548 |
// Bulk actions are handled earlier, on load-{page} (see wdm_wpma_handle_list_table_actions()), |
| 549 |
// so a redirect is still possible — do not process them here mid-render. |
| 550 |
// get_log()/get_queue() paginate + filter in SQL and return [items, total]. |
| 551 |
if ($type === 'wdm_wpma_mail_queue-tab-log') { |
| 552 |
list( $data, $total_items ) = $this->get_log(); |
| 553 |
} else if ($type === 'wdm_wpma_mail_queue-tab-queue') { |
| 554 |
list( $data, $total_items ) = $this->get_queue(); |
| 555 |
} else { |
| 556 |
$data = array(); |
| 557 |
$total_items = 0; |
| 558 |
} |
| 559 |
$this->set_pagination_args( array( |
| 560 |
'total_items' => $total_items, |
| 561 |
'per_page' => self::PER_PAGE, |
| 562 |
) ); |
| 563 |
$this->items = $data; |
| 564 |
} |
| 565 |
|
| 566 |
public function column_default( $item, $column_name ) { |
| 567 |
switch( $column_name ) { |
| 568 |
case 'timestamp': |
| 569 |
return esc_html( $item[$column_name] ); |
| 570 |
break; |
| 571 |
case 'subject': |
| 572 |
if ($item['status'] === 'event') { |
| 573 |
return ''; |
| 574 |
} |
| 575 |
return esc_html( $item[$column_name] ); |
| 576 |
break; |
| 577 |
case 'recipient': |
| 578 |
case 'headers': |
| 579 |
$return = wdm_wpma_safe_unserialize($item[$column_name]); |
| 580 |
if (is_array($return)) { |
| 581 |
return esc_html( implode(',',$return) ); |
| 582 |
} else { |
| 583 |
return esc_html( $return ); |
| 584 |
} |
| 585 |
break; |
| 586 |
case 'attachments': |
| 587 |
$return = wdm_wpma_safe_unserialize($item[$column_name]); |
| 588 |
if (is_array($return)) { |
| 589 |
$betterreturn = array(); |
| 590 |
foreach($return as $return_item) { |
| 591 |
array_push($betterreturn,basename($return_item)); |
| 592 |
} |
| 593 |
return implode('<br />',array_map('esc_html',$betterreturn)); |
| 594 |
} else { |
| 595 |
return esc_html( basename($return) ); |
| 596 |
} |
| 597 |
break; |
| 598 |
case 'status': |
| 599 |
$display_status = $item[$column_name] === 'instant' ? 'sent' : $item[$column_name]; |
| 600 |
$info = isset( $item[ 'info' ] ) && $item[ 'info' ] ? $item[ 'info' ] : ''; |
| 601 |
$infodata = is_string($info) ? json_decode($info,/*associative*/true) : null; |
| 602 |
|
| 603 |
if ($item['status'] === 'event' ) { |
| 604 |
$info = ''; |
| 605 |
switch ($item['subject']) { |
| 606 |
case 'autopause-activated': |
| 607 |
$info = 'The Queue was automatically paused because an alert was triggered.'; |
| 608 |
break; |
| 609 |
case 'queue-enabled': |
| 610 |
$info = 'The Queue was manually enabled by the user.'; |
| 611 |
break; |
| 612 |
case 'queue-paused': |
| 613 |
$info = 'The Queue was manually paused by the user.'; |
| 614 |
break; |
| 615 |
case 'queue-disabled': |
| 616 |
$info = 'The Queue was manually disabled by the user.'; |
| 617 |
break; |
| 618 |
default: break; |
| 619 |
} |
| 620 |
$htmlInfo = $info ? '<span class="wdm-wpma-info">'.$info.'</span>' : ''; |
| 621 |
$cssStatus = $htmlInfo ? ' wdm-wpma-status-has-info' : ''; |
| 622 |
return '<span class="wdm-wpma-status wdm-wpma-status-event wdm-wpma-status-event-'.sanitize_title($item['subject']).esc_attr($cssStatus).'">'.$item['subject'].$htmlInfo.'</span>'; |
| 623 |
} |
| 624 |
|
| 625 |
if ($item[$column_name] === 'alert') { |
| 626 |
$alertData = is_array($infodata) ? $infodata : null; |
| 627 |
if ( $alertData ) { |
| 628 |
$display_in_seconds = $alertData['queue_interval'] < 60 || $alertData['queue_interval'] % 60 !== 0; |
| 629 |
$queue_interval_display = $display_in_seconds ? esc_html($alertData['queue_interval']).' second(s)' : esc_html($alertData['queue_interval']/60).' minute(s)'; |
| 630 |
$info = '<strong>Emails in queue</strong>: '.esc_html($alertData['in_queue']); |
| 631 |
$info .= '<br /><strong>Queue settings</strong>: Send max '.esc_html($alertData['queue_amount']).' email(s) every '.$queue_interval_display.'.'; |
| 632 |
$info .= '<br /><strong>Alert settings</strong>: Send alert if more than '.esc_html($alertData['email_amount']).' email(s) in the queue.'; |
| 633 |
} else { |
| 634 |
$info = ''; |
| 635 |
} |
| 636 |
} elseif ($item[$column_name] === 'instant') { |
| 637 |
$info = 'Email was sent instantly, bypassing the queue'; |
| 638 |
} elseif ($item[$column_name] === 'sent') { |
| 639 |
$sentData = is_array($infodata) ? $infodata : null; |
| 640 |
if ($sentData && isset($sentData['send_mode']) && $sentData['send_mode'] === 'manual') { |
| 641 |
$info = 'Email was sent manually from the queue'; |
| 642 |
} elseif ($sentData && isset($sentData['prio']) && $sentData['prio'] === 'high') { |
| 643 |
$info = 'Email was sent with high priority'; |
| 644 |
} else { |
| 645 |
$info = ''; |
| 646 |
} |
| 647 |
} |
| 648 |
$htmlInfo = $info ? '<span class="wdm-wpma-info">'.$info.'</span>' : ''; |
| 649 |
$cssStatus = $htmlInfo ? ' wdm-wpma-status-has-info' : ''; |
| 650 |
return '<span class="wdm-wpma-status wdm-wpma-status-'.sanitize_title($display_status).esc_attr($cssStatus).'">'.$display_status.$htmlInfo.'</span>'; |
| 651 |
break; |
| 652 |
case 'message': |
| 653 |
if ($item['status'] === 'event') { |
| 654 |
return ''; |
| 655 |
} |
| 656 |
// The list query selects LENGTH(message) AS message_length instead of the |
| 657 |
// body (bodies can be large and are lazy-loaded via REST). Fall back to |
| 658 |
// strlen() if a full 'message' column is ever present. |
| 659 |
$messageLen = isset($item['message_length']) |
| 660 |
? intval($item['message_length']) |
| 661 |
: ( isset($item['message']) ? strlen($item['message']) : 0 ); |
| 662 |
if ( $messageLen > 0 ) { |
| 663 |
$return = '<details>'; |
| 664 |
$return .= '<summary class="wdm-wpma-view-source" data-wdm-wpma-list-message-toggle="'.esc_attr($item['id']).'">View message <i>('.esc_html($messageLen).' bytes)</i></summary>'; |
| 665 |
$return .= '<div class="wdm-wpma-email-source" data-wdm-wpma-list-message-content>Loading …</div>'; |
| 666 |
$return .= '</details>'; |
| 667 |
} else { |
| 668 |
$return = '<em>Empty</em>'; |
| 669 |
} |
| 670 |
return $return; |
| 671 |
break; |
| 672 |
default: |
| 673 |
return ''; |
| 674 |
break; |
| 675 |
} |
| 676 |
} |
| 677 |
|
| 678 |
protected function column_cb ( $item ) { |
| 679 |
return '<input type="checkbox" name="id[]" value="'.esc_attr($item['id']).'" />'; |
| 680 |
} |
| 681 |
|
| 682 |
public function get_bulk_actions() { |
| 683 |
if (isset($_GET['page']) && $_GET['page'] == 'wdm_wpma_mail_queue-tab-queue') { |
| 684 |
$actions = array( |
| 685 |
'delete' => __( 'Delete'), |
| 686 |
'sendnow' => __( 'Send now'), |
| 687 |
); |
| 688 |
} elseif (isset($_GET['page']) && $_GET['page'] == 'wdm_wpma_mail_queue-tab-log') { |
| 689 |
$actions = array( |
| 690 |
'delete' => __( 'Delete'), |
| 691 |
'resend' => __( 'Resend'), |
| 692 |
); |
| 693 |
} else { |
| 694 |
$actions = array(); |
| 695 |
} |
| 696 |
|
| 697 |
return $actions; |
| 698 |
} |
| 699 |
|
| 700 |
public function no_items() { |
| 701 |
echo 'No emails found.'; |
| 702 |
} |
| 703 |
|
| 704 |
// Status dropdown + date-range inputs, rendered in the top table nav. |
| 705 |
// All controls submit through the surrounding method="get" list form, so the |
| 706 |
// active filters travel in the URL and survive pagination (WP_List_Table builds |
| 707 |
// its pagination links from the request URL). Values are re-read + validated |
| 708 |
// by the get_active_*() helpers before they touch SQL. |
| 709 |
protected function extra_tablenav( $which ) { |
| 710 |
if ( $which !== 'top' ) { return; } |
| 711 |
|
| 712 |
$statuses = $this->tab_statuses(); |
| 713 |
$sel_status = $this->get_active_status_filter(); |
| 714 |
$date_from = $this->get_active_date_filter('from'); |
| 715 |
$date_to = $this->get_active_date_filter('to'); |
| 716 |
$search = $this->get_active_search(); |
| 717 |
|
| 718 |
// Hide the filters when there's nothing to filter — but only if no filter |
| 719 |
// or search is active. If an active filter/search returned zero rows we must |
| 720 |
// keep the controls (and the Clear filters button) visible so the user can |
| 721 |
// still reset. Mirrors WP_List_Table::search_box()'s show/hide logic, so the |
| 722 |
// search box and the filters appear and disappear together. |
| 723 |
$has_active_filter = ( $sel_status !== '' || $date_from !== '' || $date_to !== '' || $search !== '' ); |
| 724 |
if ( ! $this->has_items() && ! $has_active_filter ) { return; } |
| 725 |
|
| 726 |
// Single row: filters on the left, search on the right. No Filter button — |
| 727 |
// the status dropdown and date inputs carry .wdm-wpma-autofilter and |
| 728 |
// auto-submit the (GET) form on change (see assets/js/wdm-wpma-admin.js); |
| 729 |
// the search input submits on Enter. One "Clear" resets everything. |
| 730 |
echo '<div class="alignleft actions wdm-wpma-filters">'; |
| 731 |
|
| 732 |
echo '<label class="screen-reader-text" for="wdm-wpma-filter-status">Filter by status</label>'; |
| 733 |
echo '<select name="filter_status" id="wdm-wpma-filter-status" class="wdm-wpma-autofilter">'; |
| 734 |
echo '<option value="">All statuses</option>'; |
| 735 |
foreach ( $statuses as $status ) { |
| 736 |
// 'instant' rows display as "sent" in the list — label the option to match. |
| 737 |
$label = $status === 'instant' ? 'Sent (instant)' : ucfirst($status); |
| 738 |
echo '<option value="'.esc_attr($status).'"'.selected($sel_status, $status, false).'>'.esc_html($label).'</option>'; |
| 739 |
} |
| 740 |
echo '</select>'; |
| 741 |
|
| 742 |
echo ' <label class="screen-reader-text" for="wdm-wpma-filter-date-from">From date</label>'; |
| 743 |
echo '<input type="date" name="filter_date_from" id="wdm-wpma-filter-date-from" class="wdm-wpma-autofilter" value="'.esc_attr($date_from).'" />'; |
| 744 |
echo ' <label class="screen-reader-text" for="wdm-wpma-filter-date-to">To date</label>'; |
| 745 |
echo '<input type="date" name="filter_date_to" id="wdm-wpma-filter-date-to" class="wdm-wpma-autofilter" value="'.esc_attr($date_to).'" />'; |
| 746 |
|
| 747 |
echo '<span class="wdm-wpma-search-box">'; |
| 748 |
echo '<label class="screen-reader-text" for="wdm-wpma-search-input">Search emails</label>'; |
| 749 |
echo '<input type="search" name="s" id="wdm-wpma-search-input" value="'.esc_attr($search).'" placeholder="Search recipient or subject" />'; |
| 750 |
echo '<button type="submit" class="button" id="wdm-wpma-search-submit">Search</button>'; |
| 751 |
echo '</span>'; |
| 752 |
|
| 753 |
// One "Clear" for everything (status + dates + search). A link, not a submit: |
| 754 |
// the GET form would otherwise re-send the current values. Shown only when |
| 755 |
// something is active. |
| 756 |
if ( $has_active_filter ) { |
| 757 |
$clear_url = add_query_arg( |
| 758 |
array( 'page' => isset($_GET['page']) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '' ), |
| 759 |
admin_url('admin.php') |
| 760 |
); |
| 761 |
echo ' <a class="button wdm-wpma-clear" href="'.esc_url($clear_url).'">Clear</a>'; |
| 762 |
} |
| 763 |
|
| 764 |
echo '</div>'; |
| 765 |
} |
| 766 |
|
| 767 |
public function process_bulk_action() { |
| 768 |
|
| 769 |
// No action selected (e.g. the page is just being rendered) — nothing to do. |
| 770 |
$current_action = $this->current_action(); |
| 771 |
if ( ! $current_action ) { return; } |
| 772 |
|
| 773 |
// Security check: any dispatched bulk action requires a valid nonce. |
| 774 |
// Read from $_REQUEST — the list forms submit via GET (see the method="get" |
| 775 |
// forms in wdm_wpma_settings_page()), so the WP_List_Table bulk nonce arrives |
| 776 |
// as a query arg, not a POST field. |
| 777 |
$nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_key( wp_unslash( $_REQUEST['_wpnonce'] ) ) : ''; |
| 778 |
if ( ! $nonce || ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) { |
| 779 |
wp_die( 'Security check failed. Please go back, reload the page, and try again.' ); |
| 780 |
} |
| 781 |
|
| 782 |
// get IDs |
| 783 |
$request_ids = isset( $_REQUEST['id'] ) ? wp_parse_id_list( wp_unslash( $_REQUEST['id'] ) ) : array(); |
| 784 |
if ( empty( $request_ids ) ) { return; } |
| 785 |
|
| 786 |
// Default page to return to after the action (PRG) — the tab the action ran on. |
| 787 |
$current_page = isset($_GET['page']) ? sanitize_key($_GET['page']) : 'wdm_wpma_mail_queue'; |
| 788 |
|
| 789 |
// Replay guard: these actions change state (delete / resend / send now) and |
| 790 |
// the list form submits via GET, so the dispatched action is a bookmarkable |
| 791 |
// URL. Without this, revisiting it (Back button, history restore, prefetch) |
| 792 |
// would run it again while the nonce is still valid. Claim the specific |
| 793 |
// action once — keyed on action + selected ids + nonce — and treat any exact |
| 794 |
// repeat as a no-op that just redirects back with a zero result, so the same |
| 795 |
// mails are never deleted, re-queued or re-sent twice. A different selection |
| 796 |
// under the same reusable nonce is a distinct fingerprint and runs normally. |
| 797 |
$action_fingerprint = $current_action . '|' . implode( ',', $request_ids ) . '|' . $nonce; |
| 798 |
if ( ! wdm_wpma_claim_action_once( $action_fingerprint ) ) { |
| 799 |
wdm_wpma_bulk_action_redirect( $current_page, $current_action, 0, 0 ); |
| 800 |
} |
| 801 |
|
| 802 |
global $wpdb; |
| 803 |
$tableName = wdm_wpma_table(); |
| 804 |
|
| 805 |
switch ( $current_action ) { |
| 806 |
case 'delete': |
| 807 |
$count_deleted = 0; |
| 808 |
foreach($request_ids as $id) { |
| 809 |
// Read the attachments column before deleting the row: a deleted queued |
| 810 |
// mail must also lose its attachment folder, or the folder is orphaned forever. |
| 811 |
$attachments = $wpdb->get_var($wpdb->prepare("SELECT `attachments` FROM `$tableName` WHERE `id` = %d", $id)); |
| 812 |
if ( $wpdb->delete($tableName,array('id'=>intval($id)),'%d') ) { |
| 813 |
$count_deleted++; |
| 814 |
wdm_wpma_delete_attachment_folder( $attachments ); |
| 815 |
} |
| 816 |
} |
| 817 |
wdm_wpma_bulk_action_redirect( $current_page, 'delete', $count_deleted, 0 ); |
| 818 |
break; |
| 819 |
case 'resend': |
| 820 |
// Security note: this re-queues the stored body verbatim. That body may have |
| 821 |
// originated from an unauthenticated caller (any wp_mail() the plugin intercepted), |
| 822 |
// so it is only safe because rows can't be edited here — the admin resends the exact |
| 823 |
// bytes, never crafts them. For any "edit before resend" feature this trust boundary |
| 824 |
// must be re-examined (treat the edited content as admin-supplied input). |
| 825 |
$count_resend = 0; |
| 826 |
$count_error = 0; |
| 827 |
foreach($request_ids as $id) { |
| 828 |
$maildata = $wpdb->get_row($wpdb->prepare("SELECT * FROM `$tableName` WHERE `id` = %d", $id)); |
| 829 |
// Skip rows that aren't resendable real mails. Allowlist, not denylist: |
| 830 |
// an unknown/new status must never become silently resendable. |
| 831 |
// - null: deleted or foreign id (PHP 8 fatals on ->attachments otherwise, |
| 832 |
// and an empty row would be dispatched as 'ERROR // ' junk to the alert address) |
| 833 |
// - alert/event: queue bookkeeping, not outgoing mail (mirrors the REST 404 guard) |
| 834 |
// - queue/high/sending: already pending or mid-flight — re-queuing duplicates them |
| 835 |
if ( !$maildata || !in_array( $maildata->status, array( 'sent', 'error', 'instant' ), /*strict*/true ) ) { |
| 836 |
$count_error++; |
| 837 |
continue; |
| 838 |
} |
| 839 |
if (!$maildata->attachments || $maildata->attachments == '') { |
| 840 |
$count_resend++; |
| 841 |
$data = array( |
| 842 |
'timestamp'=> current_time('mysql',false), |
| 843 |
'recipient'=> $maildata->recipient, |
| 844 |
'subject'=> $maildata->subject, |
| 845 |
'message'=> $maildata->message, |
| 846 |
'status' => 'queue', |
| 847 |
'attachments' => '', |
| 848 |
'headers' => $maildata->headers, |
| 849 |
); |
| 850 |
$wpdb->insert($tableName,$data); |
| 851 |
} else { |
| 852 |
// Emails with attachments can't be resent — the attachment files |
| 853 |
// are deleted after the original send attempt. |
| 854 |
$count_error++; |
| 855 |
} |
| 856 |
} |
| 857 |
// If anything was re-queued, land on the Queue tab (that's where the new rows are). |
| 858 |
$target = ($count_resend > 0) ? 'wdm_wpma_mail_queue-tab-queue' : $current_page; |
| 859 |
wdm_wpma_bulk_action_redirect( $target, 'resend', $count_resend, $count_error ); |
| 860 |
break; |
| 861 |
case 'sendnow': |
| 862 |
$count_sent = 0; |
| 863 |
$count_error = 0; |
| 864 |
foreach($request_ids as $id) { |
| 865 |
$mailitem = $wpdb->get_row($wpdb->prepare("SELECT * FROM `$tableName` WHERE `id` = %d", $id), ARRAY_A); |
| 866 |
if ( wdm_wpma_really_send_mail($mailitem,['send_mode'=>'manual']) ) { |
| 867 |
$count_sent++; |
| 868 |
} else { |
| 869 |
$count_error++; |
| 870 |
} |
| 871 |
} |
| 872 |
// If anything was sent, land on the Log tab (that's where sent rows show up). |
| 873 |
$target = ($count_sent > 0) ? 'wdm_wpma_mail_queue-tab-log' : $current_page; |
| 874 |
wdm_wpma_bulk_action_redirect( $target, 'sendnow', $count_sent, $count_error ); |
| 875 |
break; |
| 876 |
default: |
| 877 |
break; |
| 878 |
} |
| 879 |
|
| 880 |
return; |
| 881 |
|
| 882 |
} |
| 883 |
|
| 884 |
} |
| 885 |
|
| 886 |
function wdm_wpma_settings_page_navi($tab) { |
| 887 |
echo '<nav class="nav-tab-wrapper">'; |
| 888 |
echo '<a href="?page=wdm_wpma_mail_queue" class="nav-tab'; if($tab==='wdm_wpma_mail_queue') { echo ' nav-tab-active'; } echo '">Settings</a>'; |
| 889 |
echo '<a href="?page=wdm_wpma_mail_queue-tab-log" class="nav-tab'; if($tab==='wdm_wpma_mail_queue-tab-log') { echo ' nav-tab-active'; } echo '">Log</a>'; |
| 890 |
echo '<a href="?page=wdm_wpma_mail_queue-tab-queue" class="nav-tab'; if($tab==='wdm_wpma_mail_queue-tab-queue') { echo ' nav-tab-active'; } echo '">Queue</a>'; |
| 891 |
echo '<a href="?page=wdm_wpma_mail_queue-tab-faq" class="nav-tab'; if($tab==='wdm_wpma_mail_queue-tab-faq') { echo ' nav-tab-active'; } echo '">FAQ</a>'; |
| 892 |
if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) { |
| 893 |
echo '<a href="?page=wdm_wpma_mail_queue-tab-croninfo" class="nav-tab'; if($tab==='wdm_wpma_mail_queue-tab-croninfo') { echo ' nav-tab-active'; } echo '">Cron Information</a>'; |
| 894 |
} |
| 895 |
echo '</nav>'; |
| 896 |
} |
| 897 |
|
| 898 |
function wdm_wpma_settings_init() { |
| 899 |
register_setting('wdm_wpma_settings','wdm_wpma_settings','wdm_wpma_sanitize_settings'); |
| 900 |
|
| 901 |
add_settings_section('wdm_wpma_settings_section','Mail Queue',null,'wdm_wpma_settings_page'); |
| 902 |
add_settings_field('wdm_wpma_status','Status','wdm_wpma_render_option_status','wdm_wpma_settings_page','wdm_wpma_settings_section'); |
| 903 |
add_settings_field('wdm_wpma_queue','Sending frequency','wdm_wpma_render_option_queue','wdm_wpma_settings_page','wdm_wpma_settings_section'); |
| 904 |
|
| 905 |
add_settings_section('wdm_wpma_settings_section_log','Log',null,'wdm_wpma_settings_page'); |
| 906 |
add_settings_field('wdm_wpma_log','Keep log entries','wdm_wpma_render_option_log','wdm_wpma_settings_page','wdm_wpma_settings_section_log'); |
| 907 |
|
| 908 |
add_settings_section('wdm_wpma_settings_section_alert','Alerts',null,'wdm_wpma_settings_page'); |
| 909 |
add_settings_field('wdm_wpma_alert_status','Alerts enabled','wdm_wpma_render_option_alert_status','wdm_wpma_settings_page','wdm_wpma_settings_section_alert'); |
| 910 |
add_settings_field('wdm_wpma_sensitivity','Alert Sensitivity','wdm_wpma_render_option_sensitivity','wdm_wpma_settings_page','wdm_wpma_settings_section_alert'); |
| 911 |
add_settings_field('wdm_wpma_pause_on_alert','Auto-Pause on Alert','wdm_wpma_render_option_pause_on_alert','wdm_wpma_settings_page','wdm_wpma_settings_section_alert'); |
| 912 |
} |
| 913 |
add_action('admin_init','wdm_wpma_settings_init'); |
| 914 |
|
| 915 |
|
| 916 |
// Sanitize and validate plugin settings. Rebuilds the array from an allowlist: |
| 917 |
// only known keys survive, each validated — unexpected keys from a crafted |
| 918 |
// POST are dropped. tableName is deliberately NOT accepted from input (the |
| 919 |
// settings form never submits it); it is carried over from the stored |
| 920 |
// settings, so a forged request cannot repoint the plugin at another table. |
| 921 |
// Also compares old and new settings to detect manual changes of the "enabled" |
| 922 |
// status and clear the "pause on alert" state if necessary. |
| 923 |
function wdm_wpma_sanitize_settings ($settings) { |
| 924 |
$settings = is_array($settings) ? $settings : array(); |
| 925 |
|
| 926 |
$old_settings = get_option('wdm_wpma_settings', array()); |
| 927 |
if (!is_array($old_settings)) { $old_settings = array(); } |
| 928 |
|
| 929 |
$old_enabled = isset($old_settings['enabled']) ? $old_settings['enabled'] : '0'; |
| 930 |
$new_enabled = isset($settings['enabled']) ? $settings['enabled'] : '0'; |
| 931 |
if ( !in_array($new_enabled, array('0', '1', 'paused'), true) ) { |
| 932 |
$new_enabled = '0'; |
| 933 |
} |
| 934 |
|
| 935 |
$clean = array(); |
| 936 |
$clean['enabled'] = $new_enabled; |
| 937 |
|
| 938 |
$clean['pause_on_alert'] = (isset($settings['pause_on_alert']) && (string) $settings['pause_on_alert'] === '1') ? '1' : '0'; |
| 939 |
$clean['alert_enabled'] = (isset($settings['alert_enabled']) && (string) $settings['alert_enabled'] === '1') ? '1' : '0'; |
| 940 |
|
| 941 |
// Positive integers; on a missing key fall back to the stored value (the |
| 942 |
// sanitizer also runs on programmatic update_option() calls that may not |
| 943 |
// carry the full form field set), then to the shipped default. |
| 944 |
$int_defaults = array('queue_amount' => '1', 'queue_interval' => '5', 'email_amount' => '10', 'clear_queue' => '14'); |
| 945 |
foreach ($int_defaults as $key => $default) { |
| 946 |
if (isset($settings[$key])) { |
| 947 |
$raw = $settings[$key]; |
| 948 |
} elseif (isset($old_settings[$key])) { |
| 949 |
$raw = $old_settings[$key]; |
| 950 |
} else { |
| 951 |
$raw = $default; |
| 952 |
} |
| 953 |
$clean[$key] = (string) max(1, intval($raw)); |
| 954 |
} |
| 955 |
|
| 956 |
$unit = isset($settings['queue_interval_unit']) ? $settings['queue_interval_unit'] : (isset($old_settings['queue_interval_unit']) ? $old_settings['queue_interval_unit'] : 'minutes'); |
| 957 |
$clean['queue_interval_unit'] = in_array($unit, array('minutes', 'seconds'), true) ? $unit : 'minutes'; |
| 958 |
|
| 959 |
$email = isset($settings['email']) ? sanitize_email($settings['email']) : ''; |
| 960 |
if ($email === '') { |
| 961 |
$old_email = isset($old_settings['email']) ? sanitize_email($old_settings['email']) : ''; |
| 962 |
$email = $old_email !== '' ? $old_email : sanitize_email(get_option('admin_email')); |
| 963 |
} |
| 964 |
$clean['email'] = $email; |
| 965 |
|
| 966 |
// Carried over, never taken from input. Same charset filter as |
| 967 |
// wdm_wpma_table(); empty result → omit, the defaults fill it in. |
| 968 |
if (isset($old_settings['tableName'])) { |
| 969 |
$tableName = preg_replace('/[^A-Za-z0-9_]/', '', (string) $old_settings['tableName']); |
| 970 |
if ($tableName !== '') { $clean['tableName'] = $tableName; } |
| 971 |
} |
| 972 |
|
| 973 |
if ($new_enabled !== 'paused' || ($new_enabled === 'paused' && $old_enabled !== 'paused')) { |
| 974 |
wdm_wpma_clear_pause_on_alert_state(); |
| 975 |
} |
| 976 |
|
| 977 |
if ($new_enabled !== $old_enabled) { |
| 978 |
if ($new_enabled === '1') { |
| 979 |
wdm_wpma_push_queue_event(['name' => 'queue-enabled']); |
| 980 |
} elseif ($new_enabled === 'paused') { |
| 981 |
wdm_wpma_push_queue_event(['name' => 'queue-paused']); |
| 982 |
} else { |
| 983 |
wdm_wpma_push_queue_event(['name' => 'queue-disabled']); |
| 984 |
} |
| 985 |
} |
| 986 |
|
| 987 |
return $clean; |
| 988 |
} |
| 989 |
|
| 990 |
function wdm_wpma_render_option_status() { |
| 991 |
$wdm_wpma_options = wdm_wpma_options(); |
| 992 |
|
| 993 |
echo '<select name="wdm_wpma_settings[enabled]">'; |
| 994 |
echo '<option value="1" '.($wdm_wpma_options['enabled'] === '1' ? 'selected' : '').'>Enabled</option>'; |
| 995 |
echo '<option value="paused" '.($wdm_wpma_options['enabled'] === 'paused' ? 'selected' : '').'>Paused</option>'; |
| 996 |
echo '<option value="0" '.(!in_array($wdm_wpma_options['enabled'], ['1','paused'], /*strict*/true) ? 'selected' : '').'>Disabled</option>'; |
| 997 |
echo '</select>'; |
| 998 |
|
| 999 |
if ($wdm_wpma_options['enabled'] === 'paused') { |
| 1000 |
echo ' <span class="wdm-wpma-warning"> ← The Queue is paused. </span> No emails are currently being sent, but all emails are still being added to the Queue and can be sent later by changing the status to "Enabled".'; |
| 1001 |
} elseif ($wdm_wpma_options['enabled'] !== '1') { |
| 1002 |
echo ' <span class="wdm-wpma-warning"> ← Check this to enable the Queue. </span> Otherwise this plugin won\'t have any effect on your website.'; |
| 1003 |
} |
| 1004 |
} |
| 1005 |
|
| 1006 |
function wdm_wpma_render_option_alert_status() { |
| 1007 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1008 |
if ($wdm_wpma_options['alert_enabled'] === '1') { |
| 1009 |
echo '<input type="checkbox" name="wdm_wpma_settings[alert_enabled]" value="1" checked />'; |
| 1010 |
} else { |
| 1011 |
echo '<input type="checkbox" name="wdm_wpma_settings[alert_enabled]" value="1" />'; |
| 1012 |
} |
| 1013 |
} |
| 1014 |
|
| 1015 |
function wdm_wpma_render_option_queue() { |
| 1016 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1017 |
if ($wdm_wpma_options['queue_interval_unit'] === 'seconds') { |
| 1018 |
$number = intval($wdm_wpma_options['queue_interval']); |
| 1019 |
} else { |
| 1020 |
$number = intval($wdm_wpma_options['queue_interval']) / 60; |
| 1021 |
} |
| 1022 |
|
| 1023 |
echo 'Send max. <input name="wdm_wpma_settings[queue_amount]" type="number" min="1" value="'.esc_attr($wdm_wpma_options['queue_amount']).'" /> email(s) every <input name="wdm_wpma_settings[queue_interval]" type="number" min="1" value="'.esc_attr($number).'" />'; |
| 1024 |
|
| 1025 |
if ($wdm_wpma_options['queue_interval_unit'] === 'seconds') { |
| 1026 |
echo '<select name="wdm_wpma_settings[queue_interval_unit]"><option value="minutes">minute(s)</option><option selected value="seconds">second(s)</option></select>'; |
| 1027 |
} else { |
| 1028 |
echo '<select name="wdm_wpma_settings[queue_interval_unit]"><option selected value="minutes">minute(s)</option><option value="seconds">second(s)</option></select>'; |
| 1029 |
} |
| 1030 |
|
| 1031 |
echo ' by <i><a href="https://developer.wordpress.org/plugins/cron/" target="_blank">WP Cron</a></i>. '; |
| 1032 |
|
| 1033 |
// Shown/hidden by JS (assets/js/wdm-wpma-admin.js) when the configured rate |
| 1034 |
// exceeds 20 mails/minute (minutes unit) or 1 mail per 2 seconds (seconds unit). |
| 1035 |
echo '<p id="wdm-wpma-rate-notice" class="wdm-wpma-rate-notice" hidden><b>That\'s a lot of email.</b> One of this plugin\'s jobs is to act as a safety net: if your site is ever compromised or a form gets abused, the queue is what keeps mass spam from leaving your server unnoticed. At this rate that safety net has little effect. Consider a slower rate unless you really need it.</p>'; |
| 1036 |
} |
| 1037 |
|
| 1038 |
function wdm_wpma_render_option_log() { |
| 1039 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1040 |
echo 'for <input name="wdm_wpma_settings[clear_queue]" type="number" min="1" value="'.esc_attr(intval($wdm_wpma_options['clear_queue']) / 24).'" /> days.'; |
| 1041 |
} |
| 1042 |
|
| 1043 |
function wdm_wpma_render_option_sensitivity() { |
| 1044 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1045 |
echo 'Send alert to <input type="text" name="wdm_wpma_settings[email]" value="'.esc_attr(sanitize_email($wdm_wpma_options['email'])).'" /> if more than <input name="wdm_wpma_settings[email_amount]" type="number" min="1" value="'.esc_attr(intval($wdm_wpma_options['email_amount'])).'" /> emails are in the <a href="admin.php?page=wdm_wpma_mail_queue-tab-queue">Queue</a>.'; |
| 1046 |
} |
| 1047 |
|
| 1048 |
|
| 1049 |
|
| 1050 |
function wdm_wpma_render_option_pause_on_alert() { |
| 1051 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1052 |
$is_enabled = isset($wdm_wpma_options['pause_on_alert']) && $wdm_wpma_options['pause_on_alert'] === '1'; |
| 1053 |
|
| 1054 |
echo '<select name="wdm_wpma_settings[pause_on_alert]">'; |
| 1055 |
echo '<option value="0" '.(!$is_enabled ? 'selected' : '').'>Disabled</option>'; |
| 1056 |
echo '<option value="1" '.($is_enabled ? 'selected' : '').'>Enabled</option>'; |
| 1057 |
echo '</select>'; |
| 1058 |
|
| 1059 |
if (!$is_enabled) { |
| 1060 |
echo ' <span> ← Automatically pause the Queue if an alert is triggered. </span> This could help to prevent e.g. spam emails being sent in case your website is compromised.'; |
| 1061 |
} else { |
| 1062 |
echo ' <span> ← The Queue will stay paused until you manually change the Mail Queue status to "Enabled".</span>'; |
| 1063 |
} |
| 1064 |
} |
| 1065 |
|
| 1066 |
|
| 1067 |
|
| 1068 |
|
| 1069 |
|
| 1070 |
/* *************************************************************** |
| 1071 |
Alert WordPress User if last email in log could not be sent |
| 1072 |
**************************************************************** */ |
| 1073 |
function wdm_wpma_check_log_for_errors() { |
| 1074 |
|
| 1075 |
global $wpdb; |
| 1076 |
$wdm_wpma_options = wdm_wpma_options(); |
| 1077 |
$currentScreen = function_exists('get_current_screen') ? get_current_screen() : null; |
| 1078 |
|
| 1079 |
if ($currentScreen && preg_match('#wdm_wpma_mail_queue#', $currentScreen->base) && $wdm_wpma_options['enabled'] === 'paused' && wdm_wpma_is_pause_on_alert_active()) { |
| 1080 |
wdm_wpma_render_pause_on_alert_notice(); |
| 1081 |
} |
| 1082 |
|
| 1083 |
if ( !in_array($wdm_wpma_options['enabled'], ['1', 'paused'], true) ) { return; } |
| 1084 |
|
| 1085 |
$tableName = wdm_wpma_table(); |
| 1086 |
// Only the newest non-queue/non-event row is needed, and only its status + info. |
| 1087 |
// LIMIT 1 lets MySQL reverse-walk the PRIMARY key and stop at the first match |
| 1088 |
// instead of get_row() pulling the whole matching set into memory; selecting |
| 1089 |
// just the two used columns avoids fetching the mediumtext body. This runs on |
| 1090 |
// admin_notices for every wp-admin page, so it must stay cheap under a flood. |
| 1091 |
$last_mail = $wpdb->get_row("SELECT `status`,`info` FROM `$tableName` WHERE `status` != 'queue' AND `status` != 'high' AND `status` != 'event' ORDER BY `id` DESC LIMIT 1",'ARRAY_A'); |
| 1092 |
if (!$last_mail) { return; } |
| 1093 |
|
| 1094 |
if ($last_mail['status'] == 'error') { |
| 1095 |
if (current_user_can('manage_options')) { |
| 1096 |
$notice = '<div class="notice notice-error is-dismissible">'; |
| 1097 |
$notice .= '<h1>Attention: Your website has problems sending e-mails</h1>'; |
| 1098 |
$notice .= '<p>This is an important message from your <i>Mail Queue</i> plugin. Please take a look at your <a href="admin.php?page=wdm_wpma_mail_queue-tab-log">Mail Log</a>. The last email(s) couldn\'t be sent properly.</p>'; |
| 1099 |
$notice .= '<p>Last error message was: <b>'.esc_html($last_mail['info']).'</b></p>'; |
| 1100 |
$notice .= '</div>'; |
| 1101 |
echo $notice; |
| 1102 |
} else if (current_user_can('edit_posts')) { |
| 1103 |
$notice = '<div class="notice notice-error is-dismissible">'; |
| 1104 |
$notice .= '<h1>Attention: Your website has problems sending e-mails</h1>'; |
| 1105 |
$notice .= '<p>Please contact your Administrator. It seems that WordPress is not able to send emails.</p>'; |
| 1106 |
$notice .= '<p>Last error message: <b>'.esc_html($last_mail['info']).'</b></p>'; |
| 1107 |
$notice .= '</div>'; |
| 1108 |
echo $notice; |
| 1109 |
} |
| 1110 |
} |
| 1111 |
|
| 1112 |
// notices for the plugin options page |
| 1113 |
if ($currentScreen && $currentScreen->base == 'toplevel_page_wdm_wpma_mail_queue') { |
| 1114 |
$wpMailOmittingPlugins = [ |
| 1115 |
'mailpoet/mailpoet.php' => 'MailPoet', |
| 1116 |
]; |
| 1117 |
$wpMailOmittingPluginsInstalled = []; |
| 1118 |
foreach (array_keys($wpMailOmittingPlugins) as $plugin) { |
| 1119 |
if(is_plugin_active($plugin)) { |
| 1120 |
$wpMailOmittingPluginsInstalled[] = $plugin; |
| 1121 |
} |
| 1122 |
} |
| 1123 |
if (count($wpMailOmittingPluginsInstalled) > 0) { |
| 1124 |
$notice = '<div class="notice notice-warning is-dismissible">'; |
| 1125 |
$notice .= '<p>'; |
| 1126 |
$notice .= '<strong>Please note:</strong>'; |
| 1127 |
$notice .= '<br />This plugin is not supported when using in combination with plugins that do not use the standard <i>wp_mail()</i> function.'; |
| 1128 |
$notice .= '</p>'; |
| 1129 |
$notice .= '<p>'; |
| 1130 |
$notice .= 'It seems you are using the following plugin(s) that do not use <i>wp_mail()</i>:'; |
| 1131 |
$notice .= '<br />'.implode(', ', array_map(function($plugin) use ($wpMailOmittingPlugins) { return $wpMailOmittingPlugins[$plugin]; },$wpMailOmittingPluginsInstalled)); |
| 1132 |
$notice .= '</p>'; |
| 1133 |
$notice .= '<p><a href="'.get_admin_url(null,'admin.php?page=wdm_wpma_mail_queue-tab-faq').'">More information</a></p>'; |
| 1134 |
$notice .= '</div>'; |
| 1135 |
echo $notice; |
| 1136 |
} |
| 1137 |
} |
| 1138 |
} |
| 1139 |
add_action('admin_notices', 'wdm_wpma_check_log_for_errors'); |
| 1140 |
|