first commit
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
namespace WPMailSMTP\Admin\DebugEvents;
|
||||
|
||||
use WPMailSMTP\Admin\Area;
|
||||
use WPMailSMTP\Options;
|
||||
use WPMailSMTP\Tasks\DebugEventsCleanupTask;
|
||||
use WPMailSMTP\WP;
|
||||
|
||||
/**
|
||||
* Debug Events class.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class DebugEvents {
|
||||
|
||||
/**
|
||||
* Register hooks.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function hooks() {
|
||||
|
||||
// Process AJAX requests.
|
||||
add_action( 'wp_ajax_wp_mail_smtp_debug_event_preview', [ $this, 'process_ajax_debug_event_preview' ] );
|
||||
add_action( 'wp_ajax_wp_mail_smtp_delete_all_debug_events', [ $this, 'process_ajax_delete_all_debug_events' ] );
|
||||
|
||||
// Initialize screen options for the Debug Events page.
|
||||
add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-tools', [ $this, 'screen_options' ] );
|
||||
add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 );
|
||||
add_filter( 'set_screen_option_wp_mail_smtp_debug_events_per_page', [ $this, 'set_screen_options' ], 10, 3 );
|
||||
|
||||
// Cancel previous debug events cleanup task if retention period option was changed.
|
||||
add_filter( 'wp_mail_smtp_options_set', [ $this, 'maybe_cancel_debug_events_cleanup_task' ] );
|
||||
|
||||
// Detect debug events log retention period constant change.
|
||||
if ( Options::init()->is_const_defined( 'debug_events', 'retention_period' ) ) {
|
||||
add_action( 'admin_init', [ $this, 'detect_debug_events_retention_period_constant_change' ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect debug events retention period constant change.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*/
|
||||
public function detect_debug_events_retention_period_constant_change() {
|
||||
|
||||
if ( ! WP::in_wp_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Options::init()->is_const_changed( 'debug_events', 'retention_period' ) ) {
|
||||
( new DebugEventsCleanupTask() )->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel previous debug events cleanup task if retention period option was changed.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @param array $options Currently processed options passed to a filter hook.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function maybe_cancel_debug_events_cleanup_task( $options ) {
|
||||
|
||||
if ( isset( $options['debug_events']['retention_period'] ) ) {
|
||||
// If this option has changed, cancel the recurring cleanup task and init again.
|
||||
if ( Options::init()->is_option_changed( $options['debug_events']['retention_period'], 'debug_events', 'retention_period' ) ) {
|
||||
( new DebugEventsCleanupTask() )->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process AJAX request for deleting all debug event entries.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function process_ajax_delete_all_debug_events() {
|
||||
|
||||
if (
|
||||
empty( $_POST['nonce'] ) ||
|
||||
! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' )
|
||||
) {
|
||||
wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = self::get_table_name();
|
||||
|
||||
$sql = "TRUNCATE TABLE `$table`;";
|
||||
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$result = $wpdb->query( $sql );
|
||||
|
||||
if ( $result !== false ) {
|
||||
wp_send_json_success( esc_html__( 'All debug event entries were deleted successfully.', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
wp_send_json_error(
|
||||
sprintf( /* translators: %s - WPDB error message. */
|
||||
esc_html__( 'There was an issue while trying to delete all debug event entries. Error message: %s', 'wp-mail-smtp' ),
|
||||
$wpdb->last_error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process AJAX request for debug event preview.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function process_ajax_debug_event_preview() {
|
||||
|
||||
if (
|
||||
empty( $_POST['nonce'] ) ||
|
||||
! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_debug_events' )
|
||||
) {
|
||||
wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
$event_id = isset( $_POST['id'] ) ? intval( $_POST['id'] ) : false;
|
||||
|
||||
if ( empty( $event_id ) ) {
|
||||
wp_send_json_error( esc_html__( 'No Debug Event ID provided!', 'wp-mail-smtp' ) );
|
||||
}
|
||||
|
||||
$event = new Event( $event_id );
|
||||
|
||||
wp_send_json_success(
|
||||
[
|
||||
'title' => $event->get_title(),
|
||||
'content' => $event->get_details_html(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the debug event to the DB.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $message The event's message.
|
||||
* @param int $type The event's type.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public static function add( $message = '', $type = 0 ) {
|
||||
|
||||
if ( ! in_array( $type, array_keys( Event::get_types() ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $type === Event::TYPE_DEBUG && ! self::is_debug_enabled() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$event = new Event();
|
||||
$event->set_type( $type );
|
||||
$event->set_content( $message );
|
||||
$event->set_initiator();
|
||||
|
||||
return $event->save()->get_id();
|
||||
} catch ( \Exception $exception ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the debug message.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @since 3.5.0 Returns Event ID.
|
||||
*
|
||||
* @param string $message The debug message.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public static function add_debug( $message = '' ) {
|
||||
|
||||
return self::add( $message, Event::TYPE_DEBUG );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the debug message from the provided debug event IDs.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array|string|int $ids A single or a list of debug event IDs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_debug_messages( $ids ) {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $ids ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ( ! self::is_valid_db() ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convert to a string.
|
||||
if ( is_array( $ids ) ) {
|
||||
$ids = implode( ',', $ids );
|
||||
}
|
||||
|
||||
$ids = explode( ',', (string) $ids );
|
||||
$ids = array_map( 'intval', $ids );
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $ids ), '%d' ) );
|
||||
|
||||
$table = self::get_table_name();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
|
||||
$events_data = $wpdb->get_results(
|
||||
$wpdb->prepare( "SELECT id, content, initiator, event_type, created_at FROM {$table} WHERE id IN ( {$placeholders} )", $ids )
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
if ( empty( $events_data ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $event_item ) {
|
||||
$event = new Event( $event_item );
|
||||
|
||||
return $event->get_short_details();
|
||||
},
|
||||
$events_data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the screen options for the debug events page.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function screen_options() {
|
||||
|
||||
$screen = get_current_screen();
|
||||
|
||||
if (
|
||||
! is_object( $screen ) ||
|
||||
strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-tools' ) === false ||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
! isset( $_GET['tab'] ) || $_GET['tab'] !== 'debug-events'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_screen_option(
|
||||
'per_page',
|
||||
[
|
||||
'label' => esc_html__( 'Number of events per page:', 'wp-mail-smtp' ),
|
||||
'option' => 'wp_mail_smtp_debug_events_per_page',
|
||||
'default' => EventsCollection::PER_PAGE,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the screen options for the debug events page.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param bool $keep Whether to save or skip saving the screen option value.
|
||||
* @param string $option The option name.
|
||||
* @param int $value The number of items to use.
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
public function set_screen_options( $keep, $option, $value ) {
|
||||
|
||||
if ( 'wp_mail_smtp_debug_events_per_page' === $option ) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
||||
return $keep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the email debug for debug events is enabled or not.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_debug_enabled() {
|
||||
|
||||
return (bool) Options::init()->get( 'debug_events', 'email_debug' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the debug events page URL.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_page_url() {
|
||||
|
||||
return add_query_arg(
|
||||
[
|
||||
'tab' => 'debug-events',
|
||||
],
|
||||
wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-tools' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DB table name.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string Table name, prefixed.
|
||||
*/
|
||||
public static function get_table_name() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
return $wpdb->prefix . 'wpmailsmtp_debug_events';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the DB table exists.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_db() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
static $is_valid = null;
|
||||
|
||||
// Return cached value only if table already exists.
|
||||
if ( $is_valid === true ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$table = self::get_table_name();
|
||||
|
||||
$is_valid = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
<?php
|
||||
|
||||
namespace WPMailSMTP\Admin\DebugEvents;
|
||||
|
||||
use WPMailSMTP\WP;
|
||||
|
||||
/**
|
||||
* Debug Event class.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class Event {
|
||||
|
||||
/**
|
||||
* This is an error event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
const TYPE_ERROR = 0;
|
||||
|
||||
/**
|
||||
* This is a debug event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
const TYPE_DEBUG = 1;
|
||||
|
||||
/**
|
||||
* The event's ID.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $id = 0;
|
||||
|
||||
/**
|
||||
* The event's content.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content = '';
|
||||
|
||||
/**
|
||||
* The event's initiator - who called the `wp_mail` function?
|
||||
* JSON encoded string.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $initiator = '';
|
||||
|
||||
/**
|
||||
* The event's type.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $event_type = 0;
|
||||
|
||||
/**
|
||||
* The date and time when this event was created.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $created_at;
|
||||
|
||||
/**
|
||||
* Retrieve a particular event when constructing the object.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param int|object $id_or_row The event ID or object with event attributes.
|
||||
*/
|
||||
public function __construct( $id_or_row = null ) {
|
||||
|
||||
$this->populate_event( $id_or_row );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and prepare the event data.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param int|object $id_or_row The event ID or object with event attributes.
|
||||
*/
|
||||
private function populate_event( $id_or_row ) {
|
||||
|
||||
$event = null;
|
||||
|
||||
if ( is_numeric( $id_or_row ) ) {
|
||||
// Get by ID.
|
||||
$collection = new EventsCollection( [ 'id' => (int) $id_or_row ] );
|
||||
$events = $collection->get();
|
||||
|
||||
if ( $events->valid() ) {
|
||||
$event = $events->current();
|
||||
}
|
||||
} elseif (
|
||||
is_object( $id_or_row ) &&
|
||||
isset(
|
||||
$id_or_row->id,
|
||||
$id_or_row->content,
|
||||
$id_or_row->initiator,
|
||||
$id_or_row->event_type,
|
||||
$id_or_row->created_at
|
||||
)
|
||||
) {
|
||||
$event = $id_or_row;
|
||||
}
|
||||
|
||||
if ( $event !== null ) {
|
||||
foreach ( get_object_vars( $event ) as $key => $value ) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event ID as per our DB table.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_id() {
|
||||
|
||||
return (int) $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event title.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title() {
|
||||
|
||||
/* translators: %d the event ID. */
|
||||
return sprintf( esc_html__( 'Event #%d', 'wp-mail-smtp' ), $this->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_content() {
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's type.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_type() {
|
||||
|
||||
return (int) $this->event_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all event types.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_types() {
|
||||
|
||||
return [
|
||||
self::TYPE_ERROR => esc_html__( 'Error', 'wp-mail-smtp' ),
|
||||
self::TYPE_DEBUG => esc_html__( 'Debug', 'wp-mail-smtp' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get human readable type name.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type_name() {
|
||||
|
||||
$types = self::get_types();
|
||||
|
||||
return isset( $types[ $this->get_type() ] ) ? $types[ $this->get_type() ] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date/time when this event was created.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @throws \Exception Emits exception on incorrect date.
|
||||
*
|
||||
* @return \DateTime
|
||||
*/
|
||||
public function get_created_at() {
|
||||
|
||||
$timezone = new \DateTimeZone( 'UTC' );
|
||||
$date = false;
|
||||
|
||||
if ( ! empty( $this->created_at ) ) {
|
||||
$date = \DateTime::createFromFormat( WP::datetime_mysql_format(), $this->created_at, $timezone );
|
||||
}
|
||||
|
||||
if ( $date === false ) {
|
||||
$date = new \DateTime( 'now', $timezone );
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the date/time when this event was created in a nicely formatted string.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_created_at_formatted() {
|
||||
|
||||
try {
|
||||
$date = $this->get_created_at();
|
||||
} catch ( \Exception $e ) {
|
||||
$date = null;
|
||||
}
|
||||
|
||||
if ( empty( $date ) ) {
|
||||
return esc_html__( 'N/A', 'wp-mail-smtp' );
|
||||
}
|
||||
|
||||
return esc_html(
|
||||
date_i18n(
|
||||
WP::datetime_format(),
|
||||
strtotime( get_date_from_gmt( $date->format( WP::datetime_mysql_format() ) ) )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's initiator raw data.
|
||||
* Who called the `wp_mail` function?
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_initiator_raw() {
|
||||
|
||||
return json_decode( $this->initiator, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's initiator name.
|
||||
* Which plugin/theme (or WP core) called the `wp_mail` function?
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_initiator() {
|
||||
|
||||
$initiator = (array) $this->get_initiator_raw();
|
||||
|
||||
if ( empty( $initiator['file'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return WP::get_initiator_name( $initiator['file'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's initiator file path.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_initiator_file_path() {
|
||||
|
||||
$initiator = (array) $this->get_initiator_raw();
|
||||
|
||||
if ( empty( $initiator['file'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $initiator['file'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's initiator file line.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_initiator_file_line() {
|
||||
|
||||
$initiator = (array) $this->get_initiator_raw();
|
||||
|
||||
if ( empty( $initiator['line'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $initiator['line'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event's initiator backtrace.
|
||||
*
|
||||
* @since 3.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_initiator_backtrace() {
|
||||
|
||||
$initiator = (array) $this->get_initiator_raw();
|
||||
|
||||
if ( empty( $initiator['backtrace'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $initiator['backtrace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event preview HTML.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_details_html() {
|
||||
|
||||
$initiator = $this->get_initiator();
|
||||
$initiator_backtrace = $this->get_initiator_backtrace();
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="wp-mail-smtp-debug-event-preview">
|
||||
<div class="wp-mail-smtp-debug-event-preview-subtitle">
|
||||
<span><?php esc_html_e( 'Debug Event Details', 'wp-mail-smtp' ); ?></span>
|
||||
</div>
|
||||
<div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-type">
|
||||
<span class="debug-event-label"><?php esc_html_e( 'Type', 'wp-mail-smtp' ); ?></span>
|
||||
<span class="debug-event-value"><?php echo esc_html( $this->get_type_name() ); ?></span>
|
||||
</div>
|
||||
<div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-date">
|
||||
<span class="debug-event-label"><?php esc_html_e( 'Date', 'wp-mail-smtp' ); ?></span>
|
||||
<span class="debug-event-value"><?php echo esc_html( $this->get_created_at_formatted() ); ?></span>
|
||||
</div>
|
||||
<div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-content">
|
||||
<span class="debug-event-label"><?php esc_html_e( 'Content', 'wp-mail-smtp' ); ?></span>
|
||||
<div class="debug-event-value">
|
||||
<?php echo wp_kses( str_replace( [ "\r\n", "\r", "\n" ], '<br>', $this->get_content() ), [ 'br' => [] ] ); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ( ! empty( $initiator ) ) : ?>
|
||||
<div class="wp-mail-smtp-debug-event-row wp-mail-smtp-debug-event-preview-caller">
|
||||
<span class="debug-event-label"><?php esc_html_e( 'Source', 'wp-mail-smtp' ); ?></span>
|
||||
<div class="debug-event-value">
|
||||
<span class="debug-event-initiator"><?php echo esc_html( $initiator ); ?></span>
|
||||
<p class="debug-event-code">
|
||||
<?php
|
||||
printf( /* Translators: %1$s the path of a file, %2$s the line number in the file. */
|
||||
esc_html__( '%1$s (line: %2$s)', 'wp-mail-smtp' ),
|
||||
esc_html( $this->get_initiator_file_path() ),
|
||||
esc_html( $this->get_initiator_file_line() )
|
||||
);
|
||||
?>
|
||||
|
||||
<?php if ( ! empty( $initiator_backtrace ) ) : ?>
|
||||
<br><br>
|
||||
<b><?php esc_html_e( 'Backtrace:', 'wp-mail-smtp' ); ?></b>
|
||||
<br>
|
||||
<?php
|
||||
foreach ( $initiator_backtrace as $i => $item ) {
|
||||
printf(
|
||||
/* translators: %1$d - index number; %2$s - function name; %3$s - file path; %4$s - line number. */
|
||||
esc_html__( '[%1$d] %2$s called at [%3$s:%4$s]', 'wp-mail-smtp' ),
|
||||
$i,
|
||||
isset( $item['class'] ) ? esc_html( $item['class'] . $item['type'] . $item['function'] ) : esc_html( $item['function'] ),
|
||||
isset( $item['file'] ) ? esc_html( $item['file'] ) : '', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
isset( $item['line'] ) ? esc_html( $item['line'] ) : '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
);
|
||||
echo '<br>';
|
||||
}
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the short details about this event (event content and the initiator's name).
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_details() {
|
||||
|
||||
$result = [];
|
||||
|
||||
if ( ! empty( $this->get_initiator() ) ) {
|
||||
$result[] = sprintf(
|
||||
/* Translators: %s - Email initiator/source name. */
|
||||
esc_html__( 'Email Source: %s', 'wp-mail-smtp' ),
|
||||
esc_html( $this->get_initiator() )
|
||||
);
|
||||
}
|
||||
|
||||
$result[] = esc_html( $this->get_content() );
|
||||
|
||||
return implode( WP::EOL, $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new or modified event in DB.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @throws \Exception When event init fails.
|
||||
*
|
||||
* @return Event New or updated event class instance.
|
||||
*/
|
||||
public function save() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = DebugEvents::get_table_name();
|
||||
|
||||
if ( (bool) $this->get_id() ) {
|
||||
// Update the existing DB table record.
|
||||
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$table,
|
||||
[
|
||||
'content' => $this->content,
|
||||
'initiator' => $this->initiator,
|
||||
'event_type' => $this->event_type,
|
||||
'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ),
|
||||
],
|
||||
[
|
||||
'id' => $this->get_id(),
|
||||
],
|
||||
[
|
||||
'%s', // content.
|
||||
'%s', // initiator.
|
||||
'%s', // type.
|
||||
'%s', // created_at.
|
||||
],
|
||||
[
|
||||
'%d',
|
||||
]
|
||||
);
|
||||
|
||||
$event_id = $this->get_id();
|
||||
} else {
|
||||
// Create a new DB table record.
|
||||
$wpdb->insert(
|
||||
$table,
|
||||
[
|
||||
'content' => $this->content,
|
||||
'initiator' => $this->initiator,
|
||||
'event_type' => $this->event_type,
|
||||
'created_at' => $this->get_created_at()->format( WP::datetime_mysql_format() ),
|
||||
],
|
||||
[
|
||||
'%s', // content.
|
||||
'%s', // initiator.
|
||||
'%s', // type.
|
||||
'%s', // created_at.
|
||||
]
|
||||
);
|
||||
|
||||
$event_id = $wpdb->insert_id;
|
||||
}
|
||||
|
||||
try {
|
||||
$event = new Event( $event_id );
|
||||
} catch ( \Exception $e ) {
|
||||
$event = new Event();
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the content of this event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string|array $content The event's content.
|
||||
*/
|
||||
public function set_content( $content ) {
|
||||
|
||||
if ( ! is_string( $content ) ) {
|
||||
$this->content = wp_json_encode( $content );
|
||||
} else {
|
||||
$this->content = wp_strip_all_tags( str_replace( '<br>', "\r\n", $content ), false );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initiator by checking the backtrace for the wp_mail function call.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function set_initiator() {
|
||||
|
||||
$initiator = wp_mail_smtp()->get_wp_mail_initiator();
|
||||
|
||||
if ( empty( $initiator->get_file() ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['file'] = $initiator->get_file();
|
||||
|
||||
if ( ! empty( $initiator->get_line() ) ) {
|
||||
$data['line'] = $initiator->get_line();
|
||||
}
|
||||
|
||||
if ( DebugEvents::is_debug_enabled() ) {
|
||||
$data['backtrace'] = $initiator->get_backtrace();
|
||||
}
|
||||
|
||||
$this->initiator = wp_json_encode( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of this event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param int $type The event's type.
|
||||
*/
|
||||
public function set_type( $type ) {
|
||||
|
||||
$this->event_type = (int) $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the event instance is a valid entity to work with.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function is_valid() {
|
||||
|
||||
return ! ( empty( $this->id ) || empty( $this->created_at ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is an error event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_error() {
|
||||
|
||||
return self::TYPE_ERROR === $this->get_type();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is a debug event.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_debug() {
|
||||
|
||||
return self::TYPE_DEBUG === $this->get_type();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
<?php
|
||||
|
||||
namespace WPMailSMTP\Admin\DebugEvents;
|
||||
|
||||
use WPMailSMTP\WP;
|
||||
|
||||
/**
|
||||
* Debug Events Collection.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class EventsCollection implements \Countable, \Iterator {
|
||||
|
||||
/**
|
||||
* Default number of log entries per page.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const PER_PAGE = 10;
|
||||
|
||||
/**
|
||||
* Number of log entries per page.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public static $per_page;
|
||||
|
||||
/**
|
||||
* List of all Event instances.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $list = [];
|
||||
|
||||
/**
|
||||
* List of current collection instance parameters.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* Used for \Iterator when iterating through Queue in loops.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $iterator_position = 0;
|
||||
|
||||
/**
|
||||
* Collection constructor.
|
||||
* $events = new EventsCollection( [ 'type' => 0 ] );
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $params The events collection parameters.
|
||||
*/
|
||||
public function __construct( array $params = [] ) {
|
||||
|
||||
$this->set_per_page();
|
||||
$this->params = $this->process_params( $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the per page attribute to the screen options value.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function set_per_page() {
|
||||
|
||||
$per_page = (int) get_user_meta(
|
||||
get_current_user_id(),
|
||||
'wp_mail_smtp_debug_events_per_page',
|
||||
true
|
||||
);
|
||||
|
||||
if ( $per_page < 1 ) {
|
||||
$per_page = self::PER_PAGE;
|
||||
}
|
||||
|
||||
self::$per_page = $per_page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify, sanitize, and populate with default values
|
||||
* all the passed parameters, which participate in DB queries.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $params The events collection parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function process_params( $params ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
|
||||
|
||||
$params = (array) $params;
|
||||
$processed = [];
|
||||
|
||||
/*
|
||||
* WHERE.
|
||||
*/
|
||||
// Single ID.
|
||||
if ( ! empty( $params['id'] ) ) {
|
||||
$processed['id'] = (int) $params['id'];
|
||||
}
|
||||
|
||||
// Multiple IDs.
|
||||
if (
|
||||
! empty( $params['ids'] ) &&
|
||||
is_array( $params['ids'] )
|
||||
) {
|
||||
$processed['ids'] = array_unique( array_filter( array_map( 'intval', array_values( $params['ids'] ) ) ) );
|
||||
}
|
||||
|
||||
// Type.
|
||||
if (
|
||||
isset( $params['type'] ) &&
|
||||
in_array( $params['type'], array_keys( Event::get_types() ), true )
|
||||
) {
|
||||
$processed['type'] = (int) $params['type'];
|
||||
}
|
||||
|
||||
// Search.
|
||||
if ( ! empty( $params['search'] ) ) {
|
||||
$processed['search'] = sanitize_text_field( $params['search'] );
|
||||
}
|
||||
|
||||
/*
|
||||
* LIMIT.
|
||||
*/
|
||||
if ( ! empty( $params['offset'] ) ) {
|
||||
$processed['offset'] = (int) $params['offset'];
|
||||
}
|
||||
|
||||
if ( ! empty( $params['per_page'] ) ) {
|
||||
$processed['per_page'] = (int) $params['per_page'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Sent date.
|
||||
*/
|
||||
if ( ! empty( $params['date'] ) ) {
|
||||
if ( is_string( $params['date'] ) ) {
|
||||
$params['date'] = array_fill( 0, 2, $params['date'] );
|
||||
} elseif ( is_array( $params['date'] ) && count( $params['date'] ) === 1 ) {
|
||||
$params['date'] = array_fill( 0, 2, $params['date'][0] );
|
||||
}
|
||||
|
||||
// We pass array and treat it as a range from:to.
|
||||
if ( is_array( $params['date'] ) && count( $params['date'] ) === 2 ) {
|
||||
$date_start = WP::get_day_period_date( 'start_of_day', strtotime( $params['date'][0] ), 'Y-m-d H:i:s', true );
|
||||
$date_end = WP::get_day_period_date( 'end_of_day', strtotime( $params['date'][1] ), 'Y-m-d H:i:s', true );
|
||||
|
||||
if ( ! empty( $date_start ) && ! empty( $date_end ) ) {
|
||||
$processed['date'] = [ $date_start, $date_end ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge missing values with defaults.
|
||||
return wp_parse_args(
|
||||
$processed,
|
||||
$this->get_default_params()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of default params for a usual query.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_params() {
|
||||
|
||||
return [
|
||||
'offset' => 0,
|
||||
'per_page' => self::$per_page,
|
||||
'order' => 'DESC',
|
||||
'orderby' => 'id',
|
||||
'search' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL-ready string of WHERE part for a query.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_where() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$where = [ '1=1' ];
|
||||
|
||||
// Shortcut single ID or multiple IDs.
|
||||
if ( ! empty( $this->params['id'] ) || ! empty( $this->params['ids'] ) ) {
|
||||
if ( ! empty( $this->params['id'] ) ) {
|
||||
$where[] = $wpdb->prepare( 'id = %d', $this->params['id'] );
|
||||
} elseif ( ! empty( $this->params['ids'] ) ) {
|
||||
$where[] = 'id IN (' . implode( ',', $this->params['ids'] ) . ')';
|
||||
}
|
||||
|
||||
// When some ID(s) defined - we should ignore all other possible filtering options.
|
||||
return implode( ' AND ', $where );
|
||||
}
|
||||
|
||||
// Type.
|
||||
if ( isset( $this->params['type'] ) ) {
|
||||
$where[] = $wpdb->prepare( 'event_type = %d', $this->params['type'] );
|
||||
}
|
||||
|
||||
// Search.
|
||||
if ( ! empty( $this->params['search'] ) ) {
|
||||
$where[] = '(' .
|
||||
$wpdb->prepare(
|
||||
'content LIKE %s',
|
||||
'%' . $wpdb->esc_like( $this->params['search'] ) . '%'
|
||||
)
|
||||
. ' OR ' .
|
||||
$wpdb->prepare(
|
||||
'initiator LIKE %s',
|
||||
'%' . $wpdb->esc_like( $this->params['search'] ) . '%'
|
||||
)
|
||||
. ')';
|
||||
}
|
||||
|
||||
// Sent date.
|
||||
if (
|
||||
! empty( $this->params['date'] ) &&
|
||||
is_array( $this->params['date'] ) &&
|
||||
count( $this->params['date'] ) === 2
|
||||
) {
|
||||
$where[] = $wpdb->prepare(
|
||||
'( created_at >= %s AND created_at <= %s )',
|
||||
$this->params['date'][0],
|
||||
$this->params['date'][1]
|
||||
);
|
||||
}
|
||||
|
||||
return implode( ' AND ', $where );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL-ready string of ORDER part for a query.
|
||||
* Order is always in the params, as per our defaults.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_order() {
|
||||
|
||||
return 'ORDER BY ' . $this->params['orderby'] . ' ' . $this->params['order'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SQL-ready string of LIMIT part for a query.
|
||||
* Limit is always in the params, as per our defaults.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function build_limit() {
|
||||
|
||||
return 'LIMIT ' . $this->params['offset'] . ', ' . $this->params['per_page'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of DB records according to filters.
|
||||
* Do not retrieve actual records.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_count() {
|
||||
|
||||
$table = DebugEvents::get_table_name();
|
||||
|
||||
$where = $this->build_where();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
return (int) WP::wpdb()->get_var(
|
||||
"SELECT COUNT(id) FROM $table
|
||||
WHERE {$where}"
|
||||
);
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of DB records.
|
||||
* You can either use array returned there OR iterate over the whole object,
|
||||
* as it implements Iterator interface.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return EventsCollection
|
||||
*/
|
||||
public function get() {
|
||||
|
||||
$table = DebugEvents::get_table_name();
|
||||
|
||||
$where = $this->build_where();
|
||||
$limit = $this->build_limit();
|
||||
$order = $this->build_order();
|
||||
|
||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$data = WP::wpdb()->get_results(
|
||||
"SELECT * FROM $table
|
||||
WHERE {$where}
|
||||
{$order}
|
||||
{$limit}"
|
||||
);
|
||||
// phpcs:enable
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
// As we got raw data we need to convert each row to Event.
|
||||
foreach ( $data as $row ) {
|
||||
$this->list[] = new Event( $row );
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*********************************************************************************************
|
||||
* ****************************** \Counter interface method. *********************************
|
||||
*********************************************************************************************/
|
||||
|
||||
/**
|
||||
* Count number of Record in a Queue.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count() {
|
||||
|
||||
return count( $this->list );
|
||||
}
|
||||
|
||||
/*********************************************************************************************
|
||||
* ****************************** \Iterator interface methods. *******************************
|
||||
*********************************************************************************************/
|
||||
|
||||
/**
|
||||
* Rewind the Iterator to the first element.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind() {
|
||||
|
||||
$this->iterator_position = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current element.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return Event|null Return null when no items in collection.
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current() {
|
||||
|
||||
return $this->valid() ? $this->list[ $this->iterator_position ] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key of the current element.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key() {
|
||||
|
||||
return $this->iterator_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move forward to next element.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next() {
|
||||
|
||||
++ $this->iterator_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current position is valid.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid() {
|
||||
|
||||
return isset( $this->list[ $this->iterator_position ] );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace WPMailSMTP\Admin\DebugEvents;
|
||||
|
||||
use WPMailSMTP\MigrationAbstract;
|
||||
|
||||
/**
|
||||
* Debug Events Migration Class
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class Migration extends MigrationAbstract {
|
||||
|
||||
/**
|
||||
* Version of the debug events database table.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
const DB_VERSION = 1;
|
||||
|
||||
/**
|
||||
* Option key where we save the current debug events DB version.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
const OPTION_NAME = 'wp_mail_smtp_debug_events_db_version';
|
||||
|
||||
/**
|
||||
* Option key where we save any errors while creating the debug events DB table.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
const ERROR_OPTION_NAME = 'wp_mail_smtp_debug_events_db_error';
|
||||
|
||||
/**
|
||||
* Create the debug events DB table structure.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected function migrate_to_1() {
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = DebugEvents::get_table_name();
|
||||
$charset_collate = $wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `$table` (
|
||||
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`content` TEXT DEFAULT NULL,
|
||||
`initiator` TEXT DEFAULT NULL,
|
||||
`event_type` TINYINT UNSIGNED NOT NULL DEFAULT '0',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
)
|
||||
ENGINE='InnoDB'
|
||||
{$charset_collate};";
|
||||
|
||||
$result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
|
||||
|
||||
if ( ! empty( $wpdb->last_error ) ) {
|
||||
update_option( self::ERROR_OPTION_NAME, $wpdb->last_error, false );
|
||||
}
|
||||
|
||||
// Save the current version to DB.
|
||||
if ( $result !== false ) {
|
||||
$this->update_db_ver( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,582 @@
|
||||
<?php
|
||||
|
||||
namespace WPMailSMTP\Admin\DebugEvents;
|
||||
|
||||
use WPMailSMTP\Helpers\Helpers;
|
||||
|
||||
if ( ! class_exists( 'WP_List_Table', false ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Class Table that displays the list of debug events.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
class Table extends \WP_List_Table {
|
||||
|
||||
/**
|
||||
* Number of debug events by different types.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $counts;
|
||||
|
||||
/**
|
||||
* Set up a constructor that references the parent constructor.
|
||||
* Using the parent reference to set some default configs.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
// Set parent defaults.
|
||||
parent::__construct(
|
||||
[
|
||||
'singular' => 'event',
|
||||
'plural' => 'events',
|
||||
'ajax' => false,
|
||||
]
|
||||
);
|
||||
|
||||
// Include polyfill if mbstring PHP extension is not enabled.
|
||||
if ( ! function_exists( 'mb_substr' ) || ! function_exists( 'mb_strlen' ) ) {
|
||||
Helpers::include_mbstring_polyfill();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the debug event types for filtering purpose.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array Associative array of debug event types StatusCode=>Name.
|
||||
*/
|
||||
public function get_types() {
|
||||
|
||||
return Event::get_types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the items counts for various types of debug logs.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function get_counts() {
|
||||
|
||||
$this->counts = [];
|
||||
|
||||
// Base params with applied filters.
|
||||
$base_params = $this->get_filters_query_params();
|
||||
|
||||
$total_params = $base_params;
|
||||
unset( $total_params['type'] );
|
||||
$this->counts['total'] = ( new EventsCollection( $total_params ) )->get_count();
|
||||
|
||||
foreach ( $this->get_types() as $type => $name ) {
|
||||
$collection = new EventsCollection( array_merge( $base_params, [ 'type' => $type ] ) );
|
||||
|
||||
$this->counts[ 'type_' . $type ] = $collection->get_count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters items counts by various types of debug events.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $counts {
|
||||
* Items counts by types.
|
||||
*
|
||||
* @type integer $total Total items count.
|
||||
* @type integer $status_{$type_key} Items count by type.
|
||||
* }
|
||||
*/
|
||||
$this->counts = apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_counts', $this->counts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the view types.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function get_views() {
|
||||
|
||||
$base_url = $this->get_filters_base_url();
|
||||
$current_type = $this->get_filtered_types();
|
||||
|
||||
$views = [];
|
||||
|
||||
$views['all'] = sprintf(
|
||||
'<a href="%1$s" %2$s>%3$s <span class="count">(%4$d)</span></a>',
|
||||
esc_url( remove_query_arg( 'type', $base_url ) ),
|
||||
$current_type === false ? 'class="current"' : '',
|
||||
esc_html__( 'All', 'wp-mail-smtp' ),
|
||||
intval( $this->counts['total'] )
|
||||
);
|
||||
|
||||
foreach ( $this->get_types() as $type => $type_label ) {
|
||||
|
||||
$count = intval( $this->counts[ 'type_' . $type ] );
|
||||
|
||||
// Skipping types with no events.
|
||||
if ( $count === 0 && $current_type !== $type ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$views[ $type ] = sprintf(
|
||||
'<a href="%1$s" %2$s>%3$s <span class="count">(%4$d)</span></a>',
|
||||
esc_url( add_query_arg( 'type', $type, $base_url ) ),
|
||||
$current_type === $type ? 'class="current"' : '',
|
||||
esc_html( $type_label ),
|
||||
$count
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters debug event item views.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param array $views {
|
||||
* Debug event items views by types.
|
||||
*
|
||||
* @type string $all Total items view.
|
||||
* @type integer $status_key Items views by type.
|
||||
* }
|
||||
* @param array $counts {
|
||||
* Items counts by types.
|
||||
*
|
||||
* @type integer $total Total items count.
|
||||
* @type integer $status_{$status_key} Items count by types.
|
||||
* }
|
||||
*/
|
||||
return apply_filters( 'wp_mail_smtp_admin_debug_events_table_get_views', $views, $this->counts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the table columns.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array Associative array of slug=>Name columns data.
|
||||
*/
|
||||
public function get_columns() {
|
||||
|
||||
return [
|
||||
'event' => esc_html__( 'Event', 'wp-mail-smtp' ),
|
||||
'type' => esc_html__( 'Type', 'wp-mail-smtp' ),
|
||||
'content' => esc_html__( 'Content', 'wp-mail-smtp' ),
|
||||
'initiator' => esc_html__( 'Source', 'wp-mail-smtp' ),
|
||||
'created_at' => esc_html__( 'Date', 'wp-mail-smtp' ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the main event title with a link to open event details.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param Event $item Event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_event( $item ) {
|
||||
|
||||
return '<strong>' .
|
||||
'<a href="#" data-event-id="' . esc_attr( $item->get_id() ) . '"' .
|
||||
' class="js-wp-mail-smtp-debug-event-preview row-title event-preview" title="' . esc_attr( $item->get_title() ) . '">' .
|
||||
esc_html( $item->get_title() ) .
|
||||
'</a>' .
|
||||
'</strong>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display event's type.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param Event $item Event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_type( $item ) {
|
||||
|
||||
return esc_html( $item->get_type_name() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display event's content.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param Event $item Event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_content( $item ) {
|
||||
|
||||
$content = $item->get_content();
|
||||
|
||||
if ( mb_strlen( $content ) > 100 ) {
|
||||
$content = mb_substr( $content, 0, 100 ) . '...';
|
||||
}
|
||||
|
||||
return wp_kses_post( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display event's wp_mail initiator.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param Event $item Event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_initiator( $item ) {
|
||||
|
||||
return esc_html( $item->get_initiator() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display event's created date.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param Event $item Event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function column_created_at( $item ) {
|
||||
|
||||
return $item->get_created_at_formatted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return type filter value or FALSE.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool|integer
|
||||
*/
|
||||
public function get_filtered_types() {
|
||||
|
||||
if ( ! isset( $_REQUEST['type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return false;
|
||||
}
|
||||
|
||||
return intval( $_REQUEST['type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
}
|
||||
|
||||
/**
|
||||
* Return date filter value or FALSE.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function get_filtered_dates() {
|
||||
|
||||
if ( empty( $_REQUEST['date'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return false;
|
||||
}
|
||||
|
||||
$dates = (array) explode( ' - ', sanitize_text_field( wp_unslash( $_REQUEST['date'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
return array_map( 'sanitize_text_field', $dates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return search filter values or FALSE.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function get_filtered_search() {
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_REQUEST['search'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
return sanitize_text_field( wp_unslash( $_REQUEST['search'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the event log is filtered or not.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_filtered() {
|
||||
|
||||
$is_filtered = false;
|
||||
|
||||
if (
|
||||
$this->get_filtered_search() !== false ||
|
||||
$this->get_filtered_dates() !== false ||
|
||||
$this->get_filtered_types() !== false
|
||||
) {
|
||||
$is_filtered = true;
|
||||
}
|
||||
|
||||
return $is_filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current filters query parameters.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_filters_query_params() {
|
||||
|
||||
$params = [
|
||||
'search' => $this->get_filtered_search(),
|
||||
'type' => $this->get_filtered_types(),
|
||||
'date' => $this->get_filtered_dates(),
|
||||
];
|
||||
|
||||
return array_filter(
|
||||
$params,
|
||||
function ( $v ) {
|
||||
return $v !== false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current filters base url.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_filters_base_url() {
|
||||
|
||||
$base_url = DebugEvents::get_page_url();
|
||||
$filters_params = $this->get_filters_query_params();
|
||||
|
||||
if ( isset( $filters_params['search'] ) ) {
|
||||
$base_url = add_query_arg( 'search', $filters_params['search'], $base_url );
|
||||
}
|
||||
|
||||
if ( isset( $filters_params['type'] ) ) {
|
||||
$base_url = add_query_arg( 'type', $filters_params['type'], $base_url );
|
||||
}
|
||||
|
||||
if ( isset( $filters_params['date'] ) ) {
|
||||
$base_url = add_query_arg( 'date', implode( ' - ', $filters_params['date'] ), $base_url );
|
||||
}
|
||||
|
||||
return $base_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data, prepare pagination, process bulk actions.
|
||||
* Prepare columns for display.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function prepare_items() {
|
||||
|
||||
// Retrieve count.
|
||||
$this->get_counts();
|
||||
|
||||
// Prepare all the params to pass to our Collection. All sanitization is done in that class.
|
||||
$params = $this->get_filters_query_params();
|
||||
|
||||
// Total amount for pagination with WHERE clause - super quick count DB request.
|
||||
$total_items = ( new EventsCollection( $params ) )->get_count();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$params['orderby'] = sanitize_key( $_REQUEST['orderby'] );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_REQUEST['order'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$params['order'] = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC';
|
||||
}
|
||||
|
||||
$params['offset'] = ( $this->get_pagenum() - 1 ) * EventsCollection::$per_page;
|
||||
|
||||
// Get the data from the DB using parameters defined above.
|
||||
$collection = new EventsCollection( $params );
|
||||
$this->items = $collection->get();
|
||||
|
||||
/*
|
||||
* Register our pagination options & calculations.
|
||||
*/
|
||||
$this->set_pagination_args(
|
||||
[
|
||||
'total_items' => $total_items,
|
||||
'per_page' => EventsCollection::$per_page,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the search box.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*
|
||||
* @param string $text The 'submit' button label.
|
||||
* @param string $input_id ID attribute value for the search input field.
|
||||
*/
|
||||
public function search_box( $text, $input_id ) {
|
||||
|
||||
if ( ! $this->is_filtered() && ! $this->has_items() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$search = ! empty( $_REQUEST['search'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['search'] ) ) : '';
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], [ 'event', 'type', 'content', 'initiator', 'created_at' ], true ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$order_by = sanitize_text_field( wp_unslash( $_REQUEST['orderby'] ) );
|
||||
echo '<input type="hidden" name="orderby" value="' . esc_attr( $order_by ) . '" />';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! empty( $_REQUEST['order'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$order = strtoupper( sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) ) === 'DESC' ? 'DESC' : 'ASC';
|
||||
echo '<input type="hidden" name="order" value="' . esc_attr( $order ) . '" />';
|
||||
}
|
||||
?>
|
||||
|
||||
<p class="search-box">
|
||||
<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_html( $text ); ?>:</label>
|
||||
<input type="search" id="<?php echo esc_attr( $input_id ); ?>" name="search" value="<?php echo esc_attr( $search ); ?>" />
|
||||
<?php submit_button( $text, '', '', false, [ 'id' => 'search-submit' ] ); ?>
|
||||
</p>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the table has items to display or not.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_items() {
|
||||
|
||||
return count( $this->items ) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to be displayed when there are no items.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function no_items() {
|
||||
|
||||
if ( $this->is_filtered() ) {
|
||||
esc_html_e( 'No events found.', 'wp-mail-smtp' );
|
||||
} else {
|
||||
esc_html_e( 'No events have been logged for now.', 'wp-mail-smtp' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the table.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public function display() {
|
||||
|
||||
$this->_column_headers = [ $this->get_columns(), [], [] ];
|
||||
|
||||
parent::display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the tablenav if there are no items in the table.
|
||||
* And remove the bulk action nonce and code.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $which Which tablenav: top or bottom.
|
||||
*/
|
||||
protected function display_tablenav( $which ) {
|
||||
|
||||
if ( ! $this->has_items() ) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="tablenav <?php echo esc_attr( $which ); ?>">
|
||||
|
||||
<?php
|
||||
$this->extra_tablenav( $which );
|
||||
$this->pagination( $which );
|
||||
?>
|
||||
|
||||
<br class="clear" />
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra controls to be displayed between bulk actions and pagination.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param string $which Which tablenav: top or bottom.
|
||||
*/
|
||||
protected function extra_tablenav( $which ) {
|
||||
|
||||
if ( $which !== 'top' || ! $this->has_items() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$date = $this->get_filtered_dates() !== false ? implode( ' - ', $this->get_filtered_dates() ) : '';
|
||||
?>
|
||||
<div class="alignleft actions wp-mail-smtp-filter-date">
|
||||
|
||||
<input type="text" name="date" class="regular-text wp-mail-smtp-filter-date-selector wp-mail-smtp-filter-date__control"
|
||||
placeholder="<?php esc_attr_e( 'Select a date range', 'wp-mail-smtp' ); ?>"
|
||||
value="<?php echo esc_attr( $date ); ?>">
|
||||
|
||||
<button type="submit" name="action" value="filter_date" class="button wp-mail-smtp-filter-date__btn">
|
||||
<?php esc_html_e( 'Filter', 'wp-mail-smtp' ); ?>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
if ( current_user_can( 'manage_options' ) ) {
|
||||
wp_nonce_field( 'wp_mail_smtp_debug_events', 'wp-mail-smtp-debug-events-nonce', false );
|
||||
printf(
|
||||
'<button id="wp-mail-smtp-delete-all-debug-events-button" type="button" class="button">%s</button>',
|
||||
esc_html__( 'Delete All Events', 'wp-mail-smtp' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the primary column.
|
||||
* Important for the mobile view.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return string The name of the primary column.
|
||||
*/
|
||||
protected function get_primary_column_name() {
|
||||
|
||||
return 'event';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user