PluginProbe
wpForo Forum / 3.1.2
wpForo Forum v3.1.2
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / admin / tools-tabs / email-queue.php

email-queue.php in wpForo Forum 3.1.2, at admin/tools-tabs/email-queue.php

402 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if( ! defined( 'ABSPATH' ) ) exit;
4 if( ! current_user_can( 'administrator' ) ) exit;
5
6 // Handle AJAX actions
7 if( isset( WPF()->email_queue ) && wpfval( $_POST, 'wpforo_email_queue_action' ) && wp_verify_nonce( wpfval( $_POST, '_wpnonce' ), 'wpforo_email_queue' ) ) {
8 $action = sanitize_text_field( $_POST['wpforo_email_queue_action'] );
9 $ids = isset( $_POST['email_ids'] ) ? array_map( 'intval', (array) $_POST['email_ids'] ) : [];
10
11 switch( $action ) {
12 case 'retry':
13 if( ! empty( $ids ) ) {
14 $count = WPF()->email_queue->retry_failed( $ids );
15 WPF()->notice->add( sprintf( __( '%d email(s) queued for retry', 'wpforo' ), $count ), 'success' );
16 }
17 break;
18
19 case 'delete':
20 if( ! empty( $ids ) ) {
21 $count = WPF()->email_queue->delete_items( $ids );
22 WPF()->notice->add( sprintf( __( '%d email(s) deleted', 'wpforo' ), $count ), 'success' );
23 }
24 break;
25
26 case 'process_now':
27 $processed = WPF()->email_queue->process_batch();
28 WPF()->notice->add( sprintf( __( '%d email(s) processed', 'wpforo' ), $processed ), 'success' );
29 break;
30
31 case 'clear_history':
32 $deleted = WPF()->email_queue->clear_sent_history();
33 WPF()->notice->add( sprintf( __( '%d sent email(s) cleared from history', 'wpforo' ), $deleted ), 'success' );
34 break;
35
36 case 'retry_all':
37 $failed_items = WPF()->email_queue->get_queue_items( 'failed', 1, 1000 );
38 if( ! empty( $failed_items['items'] ) ) {
39 $ids = wp_list_pluck( $failed_items['items'], 'id' );
40 $count = WPF()->email_queue->retry_failed( $ids );
41 WPF()->notice->add( sprintf( __( '%d failed email(s) queued for retry', 'wpforo' ), $count ), 'success' );
42 }
43 break;
44 }
45 }
46
47 // Self-heal check - process stalled queue if needed
48 $self_healed = 0;
49 if( isset( WPF()->email_queue ) ) {
50 $self_healed = WPF()->email_queue->maybe_process_stalled_queue();
51 if( $self_healed > 0 ) {
52 WPF()->notice->add( sprintf( __( 'Self-healed: processed %d stalled email(s)', 'wpforo' ), $self_healed ), 'success' );
53 }
54 }
55
56 // Get current data
57 $stats = isset( WPF()->email_queue ) ? WPF()->email_queue->get_stats() : [];
58 $view = sanitize_text_field( wpfval( $_GET, 'view' ) ?: 'pending' );
59 $page = max( 1, intval( wpfval( $_GET, 'paged' ) ?: 1 ) );
60 $per_page = 20;
61
62 $queue_data = isset( WPF()->email_queue ) ? WPF()->email_queue->get_queue_items( $view, $page, $per_page ) : [ 'items' => [], 'total' => 0, 'total_pages' => 0 ];
63
64 $cron_status = $stats['cron'] ?? [];
65 ?>
66
67 <style>
68 .wpf-email-queue-stats {
69 display: flex;
70 gap: 15px;
71 margin-bottom: 20px;
72 flex-wrap: wrap;
73 }
74 .wpf-email-queue-stat {
75 background: #fff;
76 border: 1px solid #ccd0d4;
77 border-radius: 4px;
78 padding: 15px 25px;
79 min-width: 120px;
80 text-align: center;
81 }
82 .wpf-email-queue-stat-value {
83 font-size: 28px;
84 font-weight: 600;
85 line-height: 1.2;
86 }
87 .wpf-email-queue-stat-label {
88 font-size: 12px;
89 color: #666;
90 text-transform: uppercase;
91 margin-top: 5px;
92 }
93 .wpf-email-queue-stat.pending .wpf-email-queue-stat-value { color: #0073aa; }
94 .wpf-email-queue-stat.failed .wpf-email-queue-stat-value { color: #dc3232; }
95 .wpf-email-queue-stat.sent .wpf-email-queue-stat-value { color: #46b450; }
96
97 .wpf-cron-status {
98 padding: 10px 15px;
99 border-radius: 4px;
100 margin-bottom: 20px;
101 display: flex;
102 align-items: center;
103 gap: 10px;
104 }
105 .wpf-cron-status.healthy { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
106 .wpf-cron-status.disabled { background: #fff3cd; border: 1px solid #ffc107; color: #856404; }
107 .wpf-cron-status.stalled { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
108 .wpf-cron-status.unknown { background: #e2e3e5; border: 1px solid #d6d8db; color: #383d41; }
109
110 .wpf-email-queue-views {
111 display: flex;
112 gap: 10px;
113 margin-bottom: 15px;
114 }
115 .wpf-email-queue-views a {
116 text-decoration: none;
117 padding: 5px 15px;
118 background: #f1f1f1;
119 border-radius: 4px;
120 }
121 .wpf-email-queue-views a.current {
122 background: #0073aa;
123 color: #fff;
124 }
125 .wpf-email-queue-views a span {
126 font-weight: 600;
127 }
128
129 .wpf-email-queue-table {
130 width: 100%;
131 border-collapse: collapse;
132 background: #fff;
133 border: 1px solid #ccd0d4;
134 }
135 .wpf-email-queue-table th,
136 .wpf-email-queue-table td {
137 padding: 10px 12px;
138 text-align: left;
139 border-bottom: 1px solid #ccd0d4;
140 }
141 .wpf-email-queue-table th {
142 background: #f9f9f9;
143 font-weight: 600;
144 }
145 .wpf-email-queue-table tr:hover {
146 background: #f7f7f7;
147 }
148 .wpf-email-queue-table .email-subject {
149 max-width: 300px;
150 overflow: hidden;
151 text-overflow: ellipsis;
152 white-space: nowrap;
153 }
154 .wpf-email-queue-table .email-error {
155 color: #dc3232;
156 font-size: 12px;
157 max-width: 200px;
158 overflow: hidden;
159 text-overflow: ellipsis;
160 white-space: nowrap;
161 }
162 .wpf-email-queue-table .status-badge {
163 display: inline-block;
164 padding: 2px 8px;
165 border-radius: 3px;
166 font-size: 11px;
167 font-weight: 600;
168 text-transform: uppercase;
169 }
170 .wpf-email-queue-table .status-badge.pending { background: #e5f3ff; color: #0073aa; }
171 .wpf-email-queue-table .status-badge.processing { background: #fff8e5; color: #b26200; }
172 .wpf-email-queue-table .status-badge.sent { background: #e5f5e7; color: #1e7e34; }
173 .wpf-email-queue-table .status-badge.failed { background: #fce4e4; color: #c0392b; }
174
175 .wpf-email-queue-actions {
176 display: flex;
177 gap: 10px;
178 margin-bottom: 15px;
179 flex-wrap: wrap;
180 }
181 .wpf-email-queue-pagination {
182 display: flex;
183 gap: 5px;
184 align-items: center;
185 margin-top: 15px;
186 }
187 .wpf-email-queue-pagination a {
188 padding: 5px 10px;
189 background: #f1f1f1;
190 text-decoration: none;
191 border-radius: 3px;
192 }
193 .wpf-email-queue-pagination a.current {
194 background: #0073aa;
195 color: #fff;
196 }
197 .wpf-email-queue-empty {
198 text-align: center;
199 padding: 40px;
200 color: #666;
201 background: #f9f9f9;
202 border-radius: 4px;
203 }
204 </style>
205
206 <div class="wpf-tool-box" style="margin-top: 15px;">
207 <h3><?php _e( 'Email Queue', 'wpforo' ); ?></h3>
208
209 <?php WPF()->notice->show(); ?>
210
211 <?php if( ! isset( WPF()->email_queue ) ): ?>
212 <p style="color: #dc3232;"><?php _e( 'Email Queue is not initialized.', 'wpforo' ); ?></p>
213 <?php else: ?>
214
215 <!-- Cron Status -->
216 <div class="wpf-cron-status <?php echo esc_attr( $cron_status['status'] ?? 'unknown' ); ?>">
217 <?php if( ( $cron_status['status'] ?? '' ) === 'healthy' ): ?>
218 <span class="dashicons dashicons-yes-alt"></span>
219 <?php elseif( ( $cron_status['status'] ?? '' ) === 'stalled' ): ?>
220 <span class="dashicons dashicons-warning"></span>
221 <?php elseif( ( $cron_status['status'] ?? '' ) === 'disabled' ): ?>
222 <span class="dashicons dashicons-info"></span>
223 <?php else: ?>
224 <span class="dashicons dashicons-editor-help"></span>
225 <?php endif; ?>
226 <span>
227 <strong><?php _e( 'Cron Status:', 'wpforo' ); ?></strong>
228 <?php echo esc_html( $cron_status['message'] ?? __( 'Unknown', 'wpforo' ) ); ?>
229 <?php if( ! empty( $cron_status['last_run_human'] ) ): ?>
230 &nbsp;|&nbsp; <?php _e( 'Last run:', 'wpforo' ); ?> <?php echo esc_html( $cron_status['last_run_human'] ); ?>
231 <?php endif; ?>
232 </span>
233 </div>
234
235 <!-- Statistics -->
236 <div class="wpf-email-queue-stats">
237 <div class="wpf-email-queue-stat pending">
238 <div class="wpf-email-queue-stat-value"><?php echo intval( $stats['pending'] ?? 0 ); ?></div>
239 <div class="wpf-email-queue-stat-label"><?php _e( 'Pending', 'wpforo' ); ?></div>
240 </div>
241 <div class="wpf-email-queue-stat failed">
242 <div class="wpf-email-queue-stat-value"><?php echo intval( $stats['failed'] ?? 0 ); ?></div>
243 <div class="wpf-email-queue-stat-label"><?php _e( 'Failed', 'wpforo' ); ?></div>
244 </div>
245 <div class="wpf-email-queue-stat sent">
246 <div class="wpf-email-queue-stat-value"><?php echo intval( $stats['sent_today'] ?? 0 ); ?></div>
247 <div class="wpf-email-queue-stat-label"><?php _e( 'Sent Today', 'wpforo' ); ?></div>
248 </div>
249 <div class="wpf-email-queue-stat sent">
250 <div class="wpf-email-queue-stat-value"><?php echo intval( $stats['sent_total'] ?? 0 ); ?></div>
251 <div class="wpf-email-queue-stat-label"><?php _e( 'Total Sent', 'wpforo' ); ?></div>
252 </div>
253 </div>
254
255 <!-- View Tabs -->
256 <?php
257 $base_url = admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'tools' ) . '&tab=email_queue' );
258 $views = [
259 'pending' => [ 'label' => __( 'Pending', 'wpforo' ), 'count' => $stats['pending'] ?? 0 ],
260 'failed' => [ 'label' => __( 'Failed', 'wpforo' ), 'count' => $stats['failed'] ?? 0 ],
261 'sent' => [ 'label' => __( 'Sent', 'wpforo' ), 'count' => $stats['sent_total'] ?? 0 ],
262 ];
263 ?>
264 <div class="wpf-email-queue-views">
265 <?php foreach( $views as $view_key => $view_data ): ?>
266 <a href="<?php echo esc_url( add_query_arg( 'view', $view_key, $base_url ) ); ?>"
267 class="<?php echo $view === $view_key ? 'current' : ''; ?>">
268 <?php echo esc_html( $view_data['label'] ); ?> <span>(<?php echo intval( $view_data['count'] ); ?>)</span>
269 </a>
270 <?php endforeach; ?>
271 </div>
272
273 <!-- Bulk Actions -->
274 <form method="POST" id="wpf-email-queue-form">
275 <?php wp_nonce_field( 'wpforo_email_queue' ); ?>
276
277 <div class="wpf-email-queue-actions">
278 <?php if( $view === 'pending' && ( $stats['pending'] ?? 0 ) > 0 ): ?>
279 <button type="submit" name="wpforo_email_queue_action" value="process_now" class="button button-primary">
280 <span class="dashicons dashicons-controls-play" style="vertical-align: middle; margin-top: -2px;"></span>
281 <?php _e( 'Process Now', 'wpforo' ); ?>
282 </button>
283 <?php endif; ?>
284
285 <?php if( $view === 'failed' && ( $stats['failed'] ?? 0 ) > 0 ): ?>
286 <button type="submit" name="wpforo_email_queue_action" value="retry_all" class="button">
287 <span class="dashicons dashicons-update" style="vertical-align: middle; margin-top: -2px;"></span>
288 <?php _e( 'Retry All Failed', 'wpforo' ); ?>
289 </button>
290 <?php endif; ?>
291
292 <?php if( $view === 'sent' && ( $stats['sent_total'] ?? 0 ) > 0 ): ?>
293 <button type="submit" name="wpforo_email_queue_action" value="clear_history" class="button"
294 onclick="return confirm('<?php esc_attr_e( 'Are you sure you want to clear all sent email history?', 'wpforo' ); ?>');">
295 <span class="dashicons dashicons-trash" style="vertical-align: middle; margin-top: -2px;"></span>
296 <?php _e( 'Clear History', 'wpforo' ); ?>
297 </button>
298 <?php endif; ?>
299
300 <?php if( ! empty( $queue_data['items'] ) && $view !== 'sent' ): ?>
301 <button type="submit" name="wpforo_email_queue_action" value="<?php echo $view === 'failed' ? 'retry' : 'delete'; ?>" class="button">
302 <?php if( $view === 'failed' ): ?>
303 <span class="dashicons dashicons-update" style="vertical-align: middle; margin-top: -2px;"></span>
304 <?php _e( 'Retry Selected', 'wpforo' ); ?>
305 <?php else: ?>
306 <span class="dashicons dashicons-trash" style="vertical-align: middle; margin-top: -2px;"></span>
307 <?php _e( 'Delete Selected', 'wpforo' ); ?>
308 <?php endif; ?>
309 </button>
310 <?php endif; ?>
311 </div>
312
313 <!-- Email List -->
314 <?php if( empty( $queue_data['items'] ) ): ?>
315 <div class="wpf-email-queue-empty">
316 <?php
317 switch( $view ) {
318 case 'pending':
319 _e( 'No pending emails in the queue.', 'wpforo' );
320 break;
321 case 'failed':
322 _e( 'No failed emails.', 'wpforo' );
323 break;
324 case 'sent':
325 _e( 'No sent emails in history.', 'wpforo' );
326 break;
327 }
328 ?>
329 </div>
330 <?php else: ?>
331 <table class="wpf-email-queue-table">
332 <thead>
333 <tr>
334 <?php if( $view !== 'sent' ): ?>
335 <th style="width: 30px;"><input type="checkbox" id="wpf-select-all-emails"></th>
336 <?php endif; ?>
337 <th><?php _e( 'Email', 'wpforo' ); ?></th>
338 <th><?php _e( 'Subject', 'wpforo' ); ?></th>
339 <th><?php _e( 'Context', 'wpforo' ); ?></th>
340 <th><?php _e( 'Status', 'wpforo' ); ?></th>
341 <?php if( $view === 'failed' ): ?>
342 <th><?php _e( 'Attempts', 'wpforo' ); ?></th>
343 <th><?php _e( 'Error', 'wpforo' ); ?></th>
344 <?php endif; ?>
345 <th><?php _e( 'Created', 'wpforo' ); ?></th>
346 <?php if( $view === 'sent' ): ?>
347 <th><?php _e( 'Sent', 'wpforo' ); ?></th>
348 <?php endif; ?>
349 </tr>
350 </thead>
351 <tbody>
352 <?php foreach( $queue_data['items'] as $item ): ?>
353 <tr>
354 <?php if( $view !== 'sent' ): ?>
355 <td><input type="checkbox" name="email_ids[]" value="<?php echo intval( $item['id'] ); ?>"></td>
356 <?php endif; ?>
357 <td><?php echo esc_html( $item['email'] ); ?></td>
358 <td class="email-subject" title="<?php echo esc_attr( $item['subject'] ); ?>">
359 <?php echo esc_html( $item['subject'] ); ?>
360 </td>
361 <td><?php echo esc_html( $item['context'] ); ?></td>
362 <td><span class="status-badge <?php echo esc_attr( $item['status'] ); ?>"><?php echo esc_html( $item['status'] ); ?></span></td>
363 <?php if( $view === 'failed' ): ?>
364 <td><?php echo intval( $item['attempts'] ); ?>/<?php echo intval( $item['max_attempts'] ); ?></td>
365 <td class="email-error" title="<?php echo esc_attr( $item['error_message'] ); ?>">
366 <?php echo esc_html( $item['error_message'] ); ?>
367 </td>
368 <?php endif; ?>
369 <td><?php echo esc_html( $item['created_at'] ); ?></td>
370 <?php if( $view === 'sent' ): ?>
371 <td><?php echo esc_html( $item['processed_at'] ); ?></td>
372 <?php endif; ?>
373 </tr>
374 <?php endforeach; ?>
375 </tbody>
376 </table>
377
378 <!-- Pagination -->
379 <?php if( $queue_data['total_pages'] > 1 ): ?>
380 <div class="wpf-email-queue-pagination">
381 <?php for( $i = 1; $i <= $queue_data['total_pages']; $i++ ): ?>
382 <a href="<?php echo esc_url( add_query_arg( [ 'view' => $view, 'paged' => $i ], $base_url ) ); ?>"
383 class="<?php echo $page === $i ? 'current' : ''; ?>">
384 <?php echo $i; ?>
385 </a>
386 <?php endfor; ?>
387 </div>
388 <?php endif; ?>
389 <?php endif; ?>
390 </form>
391
392 <script type="text/javascript">
393 jQuery(document).ready(function($) {
394 $('#wpf-select-all-emails').on('change', function() {
395 $('input[name="email_ids[]"]').prop('checked', $(this).prop('checked'));
396 });
397 });
398 </script>
399
400 <?php endif; ?>
401 </div>
402