PluginProbe ʕ •ᴥ•ʔ
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin / 4.9.0
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin v4.9.0
4.9.0 0.9.6 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.2 1.7.0 1.7.1 1.8.0 1.8.1 1.9.0 2.0.0 2.0.1 2.1.1 2.2.1 2.3.1 2.4.0 2.5.0 2.5.1 2.6.0 2.7.0 2.8.0 2.9.0 3.0.1 3.0.2 3.0.3 3.1.0 3.10.0 3.11.0 3.11.1 3.2.0 3.2.1 3.3.0 3.4.0 3.5.0 3.5.1 3.5.2 3.6.1 3.7.0 3.8.0 3.8.2 3.9.0 4.0.1 4.1.0 4.1.1 4.2.0 4.3.0 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.8.0 trunk 0.10.0 0.10.1 0.11.1 0.11.2 0.3.1 0.3.2 0.4 0.4.1 0.4.2 0.5.0 0.5.1 0.5.2 0.6 0.7 0.8 0.8.2 0.8.3 0.8.4 0.8.5 0.8.6 0.8.7 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5
wp-mail-smtp / src / Admin / DebugEvents / DebugEvents.php
wp-mail-smtp / src / Admin / DebugEvents Last commit date
DebugEvents.php 6 days ago Event.php 6 days ago EventsCollection.php 6 days ago Migration.php 6 days ago Table.php 6 days ago
DebugEvents.php
474 lines
1 <?php
2
3 namespace WPMailSMTP\Admin\DebugEvents;
4
5 use WP_Error;
6 use WPMailSMTP\Admin\Area;
7 use WPMailSMTP\Options;
8 use WPMailSMTP\Tasks\DebugEventsCleanupTask;
9 use WPMailSMTP\WP;
10
11 /**
12 * Debug Events class.
13 *
14 * @since 3.0.0
15 */
16 class DebugEvents {
17
18 /**
19 * Transient name for the error debug events.
20 *
21 * @since 3.9.0
22 *
23 * @var string
24 */
25 const ERROR_DEBUG_EVENTS_TRANSIENT = 'wp_mail_smtp_error_debug_events_transient';
26
27 /**
28 * Register hooks.
29 *
30 * @since 3.0.0
31 */
32 public function hooks() {
33
34 // Process AJAX requests.
35 add_action( 'wp_ajax_wp_mail_smtp_debug_event_preview', [ $this, 'process_ajax_debug_event_preview' ] );
36 add_action( 'wp_ajax_wp_mail_smtp_delete_all_debug_events', [ $this, 'process_ajax_delete_all_debug_events' ] );
37
38 // Initialize screen options for the Debug Events page.
39 add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-tools', [ $this, 'screen_options' ] );
40 add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 );
41 add_filter( 'set_screen_option_wp_mail_smtp_debug_events_per_page', [ $this, 'set_screen_options' ], 10, 3 );
42
43 // Cancel previous debug events cleanup task if retention period option was changed.
44 add_filter( 'wp_mail_smtp_options_set', [ $this, 'maybe_cancel_debug_events_cleanup_task' ] );
45
46 // Detect debug events log retention period constant change.
47 if ( Options::init()->is_const_defined( 'debug_events', 'retention_period' ) ) {
48 add_action( 'admin_init', [ $this, 'detect_debug_events_retention_period_constant_change' ] );
49 }
50 }
51
52 /**
53 * Detect debug events retention period constant change.
54 *
55 * @since 3.6.0
56 */
57 public function detect_debug_events_retention_period_constant_change() {
58
59 if ( ! WP::in_wp_admin() ) {
60 return;
61 }
62
63 if ( Options::init()->is_const_changed( 'debug_events', 'retention_period' ) ) {
64 ( new DebugEventsCleanupTask() )->cancel();
65 }
66 }
67
68 /**
69 * Cancel previous debug events cleanup task if retention period option was changed.
70 *
71 * @since 3.6.0
72 *
73 * @param array $options Currently processed options passed to a filter hook.
74 *
75 * @return array
76 */
77 public function maybe_cancel_debug_events_cleanup_task( $options ) {
78
79 if ( isset( $options['debug_events']['retention_period'] ) ) {
80 // If this option has changed, cancel the recurring cleanup task and init again.
81 if ( Options::init()->is_option_changed( $options['debug_events']['retention_period'], 'debug_events', 'retention_period' ) ) {
82 ( new DebugEventsCleanupTask() )->cancel();
83 }
84 }
85
86 return $options;
87 }
88
89 /**
90 * Process AJAX request for deleting all debug event entries.
91 *
92 * @since 3.0.0
93 */
94 public function process_ajax_delete_all_debug_events() {
95
96 if (
97 empty( $_POST['nonce'] ) ||
98 ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' )
99 ) {
100 wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) );
101 }
102
103 if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) {
104 wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) );
105 }
106
107 if ( ! self::is_valid_db() ) {
108 wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) );
109 }
110
111 global $wpdb;
112
113 $table = self::get_table_name();
114
115 $sql = "TRUNCATE TABLE `$table`;";
116
117 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
118 $result = $wpdb->query( $sql );
119
120 if ( $result !== false ) {
121 wp_send_json_success( esc_html__( 'All debug event entries were deleted successfully.', 'wp-mail-smtp' ) );
122 }
123
124 wp_send_json_error(
125 sprintf( /* translators: %s - WPDB error message. */
126 esc_html__( 'There was an issue while trying to delete all debug event entries. Error message: %s', 'wp-mail-smtp' ),
127 $wpdb->last_error
128 )
129 );
130 }
131
132 /**
133 * Process AJAX request for debug event preview.
134 *
135 * @since 3.0.0
136 */
137 public function process_ajax_debug_event_preview() {
138
139 if (
140 empty( $_POST['nonce'] ) ||
141 ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' )
142 ) {
143 wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) );
144 }
145
146 if ( ! current_user_can( wp_mail_smtp()->get_capability_manage_options() ) ) {
147 wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) );
148 }
149
150 if ( ! self::is_valid_db() ) {
151 wp_send_json_error( esc_html__( 'For some reason the database table was not installed correctly. Please contact plugin support team to diagnose and fix the issue.', 'wp-mail-smtp' ) );
152 }
153
154 $event_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : false;
155
156 if ( empty( $event_id ) ) {
157 wp_send_json_error( esc_html__( 'No Debug Event ID provided!', 'wp-mail-smtp' ) );
158 }
159
160 $event = new Event( $event_id );
161
162 wp_send_json_success(
163 [
164 'title' => $event->get_title(),
165 'content' => $event->get_details_html(),
166 ]
167 );
168 }
169
170 /**
171 * Add the debug event to the DB.
172 *
173 * @since 3.0.0
174 *
175 * @param string $message The event's message.
176 * @param int $type The event's type.
177 *
178 * @return bool|int
179 */
180 public static function add( $message = '', $type = 0 ) {
181
182 if ( ! self::is_valid_db() ) {
183 return false;
184 }
185
186 if ( ! in_array( $type, array_keys( Event::get_types() ), true ) ) {
187 return false;
188 }
189
190 if ( $type === Event::TYPE_DEBUG && ! self::is_debug_enabled() ) {
191 return false;
192 }
193
194 try {
195 $event = new Event();
196 $event->set_type( $type );
197 $event->set_content( $message );
198 $event->set_initiator();
199
200 return $event->save()->get_id();
201 } catch ( \Exception $exception ) {
202 return false;
203 }
204 }
205
206 /**
207 * Save the debug message.
208 *
209 * @since 3.0.0
210 * @since 3.5.0 Returns Event ID.
211 *
212 * @param string $message The debug message.
213 *
214 * @return bool|int
215 */
216 public static function add_debug( $message = '' ) {
217
218 return self::add( $message, Event::TYPE_DEBUG );
219 }
220
221 /**
222 * Add a debug event subject to a throttle window — only logs if no other event
223 * with the same throttle key has been logged within the TTL.
224 *
225 * Useful for background errors that can fire on every page load (e.g. expired
226 * OAuth token refresh failures) to avoid flooding the events table.
227 *
228 * @since 4.9.0
229 *
230 * @param string $message The event's message.
231 * @param string $throttle_key Unique key identifying this event's throttle bucket.
232 * Automatically prefixed with `wp_mail_smtp_` before
233 * being used as a transient key.
234 * @param int $ttl Throttle window in seconds. Default 5 minutes.
235 * @param int $type The event's type. Default Event::TYPE_ERROR (0).
236 *
237 * @return bool|int Event ID on success, false if throttled or save failed.
238 */
239 public static function add_throttled( $message, $throttle_key, $ttl = 5 * MINUTE_IN_SECONDS, $type = 0 ) {
240
241 $transient_key = 'wp_mail_smtp_' . $throttle_key;
242
243 if ( get_transient( $transient_key ) ) {
244 return false;
245 }
246
247 $event_id = self::add( $message, $type );
248
249 if ( $event_id !== false ) {
250 set_transient( $transient_key, time(), $ttl );
251 }
252
253 return $event_id;
254 }
255
256 /**
257 * Get the debug message from the provided debug event IDs.
258 *
259 * @since 3.0.0
260 *
261 * @param array|string|int $ids A single or a list of debug event IDs.
262 *
263 * @return array
264 */
265 public static function get_debug_messages( $ids ) {
266
267 global $wpdb;
268
269 if ( empty( $ids ) ) {
270 return [];
271 }
272
273 if ( ! self::is_valid_db() ) {
274 return [];
275 }
276
277 // Convert to a string.
278 if ( is_array( $ids ) ) {
279 $ids = implode( ',', $ids );
280 }
281
282 $ids = explode( ',', (string) $ids );
283 $ids = array_map( 'intval', $ids );
284 $placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
285
286 $table = self::get_table_name();
287
288 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
289 $events_data = $wpdb->get_results(
290 $wpdb->prepare( "SELECT id, content, initiator, event_type, created_at FROM {$table} WHERE id IN ( {$placeholders} )", $ids )
291 );
292 // phpcs:enable
293
294 if ( empty( $events_data ) ) {
295 return [];
296 }
297
298 return array_map(
299 function ( $event_item ) {
300 $event = new Event( $event_item );
301
302 return $event->get_short_details();
303 },
304 $events_data
305 );
306 }
307
308 /**
309 * Returns the number of error debug events in a given time span.
310 *
311 * By default it returns the number of error debug events in the last 30 days.
312 *
313 * @since 3.9.0
314 *
315 * @param string $span_of_time The time span to count the events for. Default '-30 days'.
316 *
317 * @return int|WP_Error The number of error debug events or WP_Error on failure.
318 */
319 public static function get_error_debug_events_count( $span_of_time = '-30 days' ) {
320
321 $timestamp = strtotime( $span_of_time );
322
323 if ( ! $timestamp || $timestamp > time() ) {
324 return new WP_Error( 'wp_mail_smtp_admin_debug_events_get_error_debug_events_count_invalid_time', 'Invalid time span.' );
325 }
326
327 if ( ! self::is_valid_db() ) {
328 return 0;
329 }
330
331 $transient_key = self::ERROR_DEBUG_EVENTS_TRANSIENT . '_' . sanitize_title_with_dashes( $span_of_time );
332 $cached_error_events_count = get_transient( $transient_key );
333
334 if ( $cached_error_events_count !== false ) {
335 return (int) $cached_error_events_count;
336 }
337
338 global $wpdb;
339
340 // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
341 $sql = $wpdb->prepare(
342 'SELECT COUNT(*) FROM `%1$s` WHERE event_type = %2$d AND created_at >= "%3$s"',
343 self::get_table_name(),
344 Event::TYPE_ERROR,
345 gmdate( WP::datetime_mysql_format(), $timestamp )
346 );
347 // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.UnquotedComplexPlaceholder
348
349 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
350 $error_events_count = (int) $wpdb->get_var( $sql );
351
352 set_transient( $transient_key, $error_events_count, HOUR_IN_SECONDS );
353
354 return $error_events_count;
355 }
356
357 /**
358 * Register the screen options for the debug events page.
359 *
360 * @since 3.0.0
361 */
362 public function screen_options() {
363
364 $screen = get_current_screen();
365
366 if (
367 ! is_object( $screen ) ||
368 strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-tools' ) === false ||
369 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
370 ! isset( $_GET['tab'] ) || $_GET['tab'] !== 'debug-events'
371 ) {
372 return;
373 }
374
375 add_screen_option(
376 'per_page',
377 [
378 'label' => esc_html__( 'Number of events per page:', 'wp-mail-smtp' ),
379 'option' => 'wp_mail_smtp_debug_events_per_page',
380 'default' => EventsCollection::PER_PAGE,
381 ]
382 );
383 }
384
385 /**
386 * Set the screen options for the debug events page.
387 *
388 * @since 3.0.0
389 *
390 * @param bool $keep Whether to save or skip saving the screen option value.
391 * @param string $option The option name.
392 * @param int $value The number of items to use.
393 *
394 * @return bool|int
395 */
396 public function set_screen_options( $keep, $option, $value ) {
397
398 if ( 'wp_mail_smtp_debug_events_per_page' === $option ) {
399 return (int) $value;
400 }
401
402 return $keep;
403 }
404
405 /**
406 * Whether the email debug for debug events is enabled or not.
407 *
408 * @since 3.0.0
409 *
410 * @return bool
411 */
412 public static function is_debug_enabled() {
413
414 return (bool) Options::init()->get( 'debug_events', 'email_debug' );
415 }
416
417 /**
418 * Get the debug events page URL.
419 *
420 * @since 3.0.0
421 *
422 * @return string
423 */
424 public static function get_page_url() {
425
426 return add_query_arg(
427 [
428 'tab' => 'debug-events',
429 ],
430 wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' )
431 );
432 }
433
434 /**
435 * Get the DB table name.
436 *
437 * @since 3.0.0
438 *
439 * @return string Table name, prefixed.
440 */
441 public static function get_table_name() {
442
443 global $wpdb;
444
445 return $wpdb->prefix . 'wpmailsmtp_debug_events';
446 }
447
448 /**
449 * Whether the DB table exists.
450 *
451 * @since 3.0.0
452 *
453 * @return bool
454 */
455 public static function is_valid_db() {
456
457 global $wpdb;
458
459 static $is_valid = null;
460
461 // Return cached value only if table already exists.
462 if ( $is_valid === true ) {
463 return true;
464 }
465
466 $table = self::get_table_name();
467
468 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
469 $is_valid = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $table ) );
470
471 return $is_valid;
472 }
473 }
474