<?php

namespace WPMailSMTP\Pro\Emails\Logs;

use WP_Error;
use WPMailSMTP\Admin\Area;
use WPMailSMTP\ConnectionInterface;
use WPMailSMTP\MailCatcherInterface;
use WPMailSMTP\Options;
use WPMailSMTP\Pro\Emails\Logs\Attachments\Attachments;
use WPMailSMTP\Pro\Emails\Logs\DeliveryVerification\DeliveryVerification;
use WPMailSMTP\Pro\Emails\Logs\Providers\Common;
use WPMailSMTP\Pro\Emails\Logs\Providers\SMTP;
use WPMailSMTP\Pro\Emails\Logs\Webhooks\Webhooks;
use WPMailSMTP\Pro\Tasks\EmailLogCleanupTask;
use WPMailSMTP\Pro\Tasks\Logs\Sendlayer\VerifySentStatusTask as SendlayerVerifySentStatusTask;
use WPMailSMTP\Pro\Tasks\Logs\Mailgun\VerifySentStatusTask as MailgunVerifySentStatusTask;
use WPMailSMTP\Pro\Tasks\Logs\Sendinblue\VerifySentStatusTask as SendinblueVerifySentStatusTask;
use WPMailSMTP\Pro\Tasks\Logs\SMTPcom\VerifySentStatusTask as SMTPcomVerifySentStatusTask;
use WPMailSMTP\Pro\Tasks\Logs\Postmark\VerifySentStatusTask as PostmarkVerifySentStatusTask;
use WPMailSMTP\Pro\Tasks\Logs\SparkPost\VerifySentStatusTask as SparkPostVerifySentStatusTask;
use WPMailSMTP\Providers\MailerAbstract;
use WPMailSMTP\WP;
use WPMailSMTP\Pro\Emails\Logs\Admin\PrintPreview;
use WPMailSMTP\Pro\Emails\Logs\Tracking\Tracking;
use WPMailSMTP\Pro\Emails\Logs\Tracking\Events\Events as TrackingEvents;
use WPMailSMTP\Pro\Emails\Logs\Reports\Admin as ReportsAdmin;

/**
 * Class Logs.
 *
 * @since 1.5.0
 */
class Logs {

	/**
	 * Used for SMTP, because it has several points of failures
	 * and we need to store email and check its status in different places.
	 * API-based mailers are sent and checked at the same place
	 * and don't need this state.
	 *
	 * @var int
	 */
	private $current_email_id = 0;

	/**
	 * Parent email log ID of the current email.
	 * Used for connected email logs storing.
	 *
	 * @since 3.7.0
	 *
	 * @var int
	 */
	private $current_email_parent_id = 0;

	/**
	 * Logs constructor.
	 *
	 * @since 1.5.0
	 */
	public function __construct() {}

	/**
	 * Initialize the Logs functionality.
	 *
	 * @since 1.5.0
	 */
	public function init() {

		// Redefine default Lite CSS file.
		add_filter( 'wp_mail_smtp_admin_enqueue_assets_logs_css', function () {

			return wp_mail_smtp()->pro->assets_url . '/css/smtp-pro-logs.min.css';
		} );

		// Redefine default Lite JS file.
		add_filter( 'wp_mail_smtp_admin_enqueue_assets_logs_js', function () {

			return wp_mail_smtp()->pro->assets_url . '/js/smtp-pro-logs' . WP::asset_min() . '.js';
		} );

		add_action( 'wp_mail_smtp_admin_area_enqueue_assets', array( $this, 'enqueue_assets' ) );

		// Redefine the Logs page display class.
		add_filter( 'wp_mail_smtp_admin_display_get_logs_fqcn', function () {

			if ( wp_mail_smtp()->pro->get_logs()->is_archive() ) {
				return \WPMailSMTP\Pro\Emails\Logs\Admin\ArchivePage::class;
			} else {
				return \WPMailSMTP\Pro\Emails\Logs\Admin\SinglePage::class;
			}
		} );

		// Add a new Email Log tab under General.
		add_filter( 'wp_mail_smtp_admin_get_pages', function ( $pages ) {

			$misc = $pages['misc'];
			unset( $pages['misc'] );

			$pages['logs'] = new Admin\SettingsTab();
			$pages['misc'] = $misc;

			return $pages;
		}, 0 );

		// Filter admin area options save process.
		add_filter( 'wp_mail_smtp_options_set', [ $this, 'filter_options_set' ] );
		add_filter( 'wp_mail_smtp_options_get_const_value', [ $this, 'filter_options_get_const_value' ], 10, 4 );
		add_filter( 'wp_mail_smtp_options_is_const_defined', [ $this, 'filter_options_is_const_defined' ], 10, 3 );

		// Track single email Preview and Deletion.
		add_action( 'admin_init', [ $this, 'process_email_preview' ] );
		add_action( 'admin_init', [ $this, 'process_email_delete' ] );

		// Display notices.
		add_action( 'admin_init', [ $this, 'display_notices' ] );

		if ( $this->is_enabled() ) {
			// Actually log emails - SMTP.
			add_action( 'wp_mail_smtp_mailcatcher_smtp_pre_send_before', [ $this, 'process_smtp_pre_send_before' ] );
			add_action( 'wp_mail_smtp_mailcatcher_smtp_send_before', [ $this, 'process_smtp_send_before' ] );
			add_action( 'wp_mail_smtp_mailcatcher_smtp_send_after', [ $this, 'process_smtp_send_after' ], 10, 7 );

			// Actually log emails - Catch All.
			add_action( 'wp_mail_smtp_mailcatcher_pre_send_before', [ $this, 'process_pre_send_before' ] );
			add_action( 'wp_mail_smtp_mailcatcher_send_after', [ $this, 'process_log_save' ], 10, 2 );

			// Log email errors.
			add_action( 'wp_mail_smtp_mailcatcher_send_failed', [ $this, 'process_smtp_fails' ] );

			// Process AJAX request for deleting all logs.
			add_action( 'wp_ajax_wp_mail_smtp_delete_all_log_entries', [ $this, 'process_ajax_delete_all_log_entries' ] );

			// Set parent email log ID for backup connection email.
			add_action(
				'wp_mail_smtp_pro_backup_connections_send_email_before',
				function () {
					$this->set_current_email_parent_id( $this->get_current_email_id() );
				}
			);
			add_action( 'wp_mail_smtp_pro_backup_connections_send_email_after', [ $this, 'reset_current_email_parent_id' ] );

			add_action( 'wp_mail_smtp_mail_catcher_send_blocked', [ $this, 'maybe_log_blocked_email' ] );

			// Initialize email print preview.
			( new PrintPreview() )->hooks();

			// Initialize emails resend.
			( new Resend() )->hooks();

			// Initialize email recheck delivery status.
			( new RecheckDeliveryStatus() )->hooks();
		}

		// Initialize webhooks.
		$this->get_webhooks();

		// Initialize email tracking.
		( new Tracking( $this->is_enabled_tracking() ) )->hooks();

		// Initialize screen options for the logs admin archive page.
		add_action( 'load-wp-mail-smtp_page_wp-mail-smtp-logs', [ $this, 'archive_screen_options' ] );
		add_filter( 'set-screen-option', [ $this, 'set_archive_screen_options' ], 10, 3 );
		add_filter( 'set_screen_option_wp_mail_smtp_log_entries_per_page', [ $this, 'set_archive_screen_options' ], 10, 3 );

		// Set default hidden columns for logs table.
		add_filter( 'default_hidden_columns', [ $this, 'archive_table_default_hidden_columns' ], 10, 2 );

		// Register the sent status verification tasks.
		add_action( 'wp_mail_smtp_providers_mailer_verify_sent_status', [ $this, 'run_sent_status_verification' ], 10, 2 );

		// Detect log retention period constant change.
		if ( Options::init()->is_const_defined( 'logs', 'log_retention_period' ) ) {
			add_action( 'admin_init', [ $this, 'detect_log_retention_period_constant_change' ] );
		}

		// Init logs table.
		if ( $this->is_archive() ) {
			add_action(
				'admin_init',
				function () {
					wp_mail_smtp()->get_admin()->generate_display_logs_object()->get_table();
				}
			);
		}

		// Init reports admin page.
		add_filter(
			'wp_mail_smtp_admin_page_reports_tabs',
			function ( $tabs ) {
				$tabs['reports'] = ReportsAdmin::class;
				return $tabs;
			}
		);

		// Disable summary report email if email log is disabled.
		if ( ! $this->is_enabled() ) {
			add_filter( 'wp_mail_smtp_reports_emails_summary_is_disabled', '__return_true' );
		}
	}

	/**
	 * Register the screen options for the email logs archive page.
	 *
	 * @since 1.9.0
	 */
	public function archive_screen_options() {

		$screen = get_current_screen();

		if (
			! is_object( $screen ) ||
			strpos( $screen->id, 'wp-mail-smtp_page_wp-mail-smtp-logs' ) === false ||
			isset( $_REQUEST['mode'] ) //phpcs:ignore
		) {
			return;
		}

		add_screen_option(
			'per_page',
			array(
				'label'   => esc_html__( 'Number of log entries per page:', 'wp-mail-smtp-pro' ),
				'option'  => 'wp_mail_smtp_log_entries_per_page',
				'default' => EmailsCollection::PER_PAGE,
			)
		);
	}

	/**
	 * Set the screen options for the archive logs page.
	 *
	 * @since 1.9.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_archive_screen_options( $keep, $option, $value ) {

		if ( 'wp_mail_smtp_log_entries_per_page' === $option ) {
			return (int) $value;
		}

		return $keep;
	}

	/**
	 * Set default hidden columns for logs table.
	 * This method is only working for new users,
	 * for existing users a migration is hiding these columns.
	 *
	 * @since 3.1.0
	 *
	 * @param string[]   $hidden Array of IDs of columns hidden by default.
	 * @param \WP_Screen $screen WP_Screen object of the current screen.
	 *
	 * @return array
	 */
	public function archive_table_default_hidden_columns( $hidden, $screen ) {

		if ( $screen->id === 'wp-mail-smtp_page_wp-mail-smtp-logs' ) {
			$hidden[] = 'cc';
			$hidden[] = 'bcc';
		}

		return $hidden;
	}

	/**
	 * Sanitize admin area options.
	 *
	 * @since 1.5.0
	 *
	 * @param array $options Currently processed options passed to a filter hook.
	 *
	 * @return array
	 */
	public function filter_options_set( $options ) {

		if ( ! isset( $options['logs'] ) ) {
			// All options are off by default.
			$options['logs'] = [
				'enabled'              => false,
				'log_blocked_emails'   => false,
				'log_email_content'    => false,
				'log_retention_period' => 0,
				'save_attachments'     => false,
				'open_email_tracking'  => false,
				'click_link_tracking'  => false,
			];

			return $options;
		}

		foreach ( $options['logs'] as $key => $value ) {
			if ( $key === 'log_retention_period' ) {
				$options['logs'][ $key ] = intval( $value );

				// If this option has changed, cancel the recurring cleanup task.
				if ( Options::init()->is_option_changed( $options['logs'][ $key ], 'logs', $key ) ) {
					( new EmailLogCleanupTask() )->cancel();
				}
			} else {
				$options['logs'][ $key ] = (bool) $value;
			}
		}

		return $options;
	}

	/**
	 * Process the options values through the constants check.
	 *
	 * @since 2.8.0
	 *
	 * @param mixed  $return Constant value.
	 * @param string $group  The option group.
	 * @param string $key    The option key.
	 * @param mixed  $value  DB option value.
	 *
	 * @return mixed
	 */
	public function filter_options_get_const_value( $return, $group, $key, $value ) {

		$options = Options::init();

		if ( $group === 'logs' ) {
			switch ( $key ) {
				case 'enabled':
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOGS_ENABLED ) :
						$value;
					break;
				case 'log_blocked_emails': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOG_BLOCKED_EMAILS ) :
						$value;
					break;
				case 'log_email_content':
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOGS_LOG_EMAIL_CONTENT ) :
						$value;
					break;
				case 'log_retention_period':
					$return = $options->is_const_defined( $group, $key ) ?
						intval( WPMS_LOGS_LOG_RETENTION_PERIOD ) :
						$value;
					break;
				case 'save_attachments':
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOGS_SAVE_ATTACHMENTS ) :
						$value;
					break;
				case 'open_email_tracking':
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOGS_OPEN_EMAIL_TRACKING ) :
						$value;
					break;
				case 'click_link_tracking':
					$return = $options->is_const_defined( $group, $key ) ?
						$options->parse_boolean( WPMS_LOGS_CLICK_LINK_TRACKING ) :
						$value;
					break;
			}
		}

		return $return;
	}

	/**
	 * Check is constant defined.
	 *
	 * @since 2.8.0
	 *
	 * @param mixed  $return Constant value.
	 * @param string $group  The option group.
	 * @param string $key    The option key.
	 *
	 * @return bool
	 */
	public function filter_options_is_const_defined( $return, $group, $key ) {

		if ( $group === 'logs' ) {
			switch ( $key ) {
				case 'enabled':
					$return = defined( 'WPMS_LOGS_ENABLED' );
					break;
				case 'log_email_content':
					$return = defined( 'WPMS_LOGS_LOG_EMAIL_CONTENT' );
					break;
				case 'log_retention_period':
					$return = defined( 'WPMS_LOGS_LOG_RETENTION_PERIOD' );
					break;
				case 'save_attachments':
					$return = defined( 'WPMS_LOGS_SAVE_ATTACHMENTS' );
					break;
				case 'open_email_tracking':
					$return = defined( 'WPMS_LOGS_OPEN_EMAIL_TRACKING' );
					break;
				case 'click_link_tracking':
					$return = defined( 'WPMS_LOGS_CLICK_LINK_TRACKING' );
					break;
			}
		}

		return $return;
	}

	/**
	 * Detect log retention period constant change.
	 *
	 * @since 2.8.0
	 */
	public function detect_log_retention_period_constant_change() {

		if ( ! WP::in_wp_admin() ) {
			return;
		}

		if ( Options::init()->is_const_changed( 'logs', 'log_retention_period' ) ) {
			( new EmailLogCleanupTask() )->cancel();
		}
	}

	/**
	 * Get admin area page URL for Logs, regardless of page mode, default is Archive page.
	 *
	 * @since 1.5.0
	 *
	 * @return string
	 */
	public function get_admin_page_url() {

		return wp_mail_smtp()->get_admin()->get_admin_page_url( Area::SLUG . '-logs' );
	}

	/**
	 * Get plugin settings page URL for Logs.
	 *
	 * @since 2.7.0
	 *
	 * @return string
	 */
	public function get_settings_url() {

		return add_query_arg( 'tab', 'logs', wp_mail_smtp()->get_admin()->get_admin_page_url() );
	}

	/**
	 * Get email logs page manage capability.
	 *
	 * @since 2.8.0
	 *
	 * @return string
	 */
	public function get_manage_capability() {

		/**
		 * Filter email logs page manage capability.
		 *
		 * @since 2.8.0
		 *
		 * @param string  $capability Email logs page manage capability.
		 */
		return apply_filters(
			'wp_mail_smtp_pro_emails_logs_logs_get_manage_capability',
			wp_mail_smtp()->get_capability_manage_options()
		);
	}

	/**
	 * Enqueue required JS and CSS.
	 *
	 * @since 1.5.0
	 */
	public function enqueue_assets() {

		if ( ! wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) ) {
			return;
		}

		$settings = [
			'plugin_url'                                      => wp_mail_smtp()->plugin_url,
			'text_email_delete_sure'                          => esc_html__( 'Are you sure that you want to delete this email log? This action cannot be undone.', 'wp-mail-smtp-pro' ),
			'ok'                                              => esc_html__( 'OK', 'wp-mail-smtp-pro' ),
			'icon'                                            => esc_html__( 'Icon', 'wp-mail-smtp-pro' ),
			'delete_all_email_logs_confirmation_text'         => esc_html__( 'Are you sure you want to permanently delete all email logs?', 'wp-mail-smtp-pro' ),
			'recheck_all_email_logs_status_confirmation_text' => esc_html__( 'Are you sure you want to re-check the statuses of all pending email logs?', 'wp-mail-smtp-pro' ),
			'heads_up_title'                                  => esc_html__( 'Heads up!', 'wp-mail-smtp-pro' ),
			'yes_text'                                        => esc_html__( 'Yes', 'wp-mail-smtp-pro' ),
			'cancel_text'                                     => esc_html__( 'Cancel', 'wp-mail-smtp-pro' ),
			'error_occurred'                                  => esc_html__( 'An error occurred!', 'wp-mail-smtp-pro' ),
		];

		if ( $this->is_archive() ) {
			wp_enqueue_style(
				'wp-mail-smtp-admin-flatpickr',
				wp_mail_smtp()->assets_url . '/css/vendor/flatpickr.min.css',
				[],
				'4.6.9'
			);
			wp_enqueue_script(
				'wp-mail-smtp-admin-flatpickr',
				wp_mail_smtp()->assets_url . '/js/vendor/flatpickr.min.js',
				[],
				'4.6.9',
				false
			);

			$settings['lang_code']                           = sanitize_key( WP::get_language_code() );
			$settings['bulk_resend_email_confirmation_text'] = Resend::prepare_resend_confirmation_content( false, true );
			$settings['bulk_resend_email_processing_text']   = esc_html__( 'Queuing emails...', 'wp-mail-smtp-pro' );
		} else {
			$email = new Email( intval( $_GET['email_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated

			if ( $email->is_valid() ) {
				$settings['email_id']                                  = $email->get_id();
				$settings['resend_email_confirmation_text']            = Resend::prepare_resend_confirmation_content( $email );
				$settings['resend_email_processing_text']              = esc_html__( 'Sending email...', 'wp-mail-smtp-pro' );
				$settings['resend_email_invalid_recipients_addresses'] = esc_html__( 'Invalid recipients email addresses.', 'wp-mail-smtp-pro' );
			}
		}

		wp_localize_script(
			'wp-mail-smtp-admin-logs',
			'wp_mail_smtp_logs',
			$settings
		);
	}

	/**
	 * Whether the logging to DB functionality is enabled or not.
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function is_enabled() {

		return (bool) Options::init()->get( 'logs', 'enabled' );
	}

	/**
	 * Whether the DB table exists.
	 *
	 * @since 1.7.0
	 *
	 * @return bool
	 */
	public 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();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
		$is_valid = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s;', $table ) );

		return $is_valid;
	}

	/**
	 * Whether the email content logging is enabled or not.
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function is_enabled_content() {

		return (bool) Options::init()->get( 'logs', 'log_email_content' );
	}

	/**
	 * Whether the attachment storing/saving is enabled or not.
	 *
	 * @since 2.9.0
	 *
	 * @return bool
	 */
	public function is_enabled_save_attachments() {

		return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'save_attachments' );
	}

	/**
	 * Whether the open email tracking is enabled or not.
	 *
	 * @since 2.9.0
	 *
	 * @return bool
	 */
	public function is_enabled_open_email_tracking() {

		return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'open_email_tracking' );
	}

	/**
	 * Whether the click link tracking is enabled or not.
	 *
	 * @since 2.9.0
	 *
	 * @return bool
	 */
	public function is_enabled_click_link_tracking() {

		return $this->is_enabled() && (bool) Options::init()->get( 'logs', 'click_link_tracking' );
	}

	/**
	 * Whether the email tracking is enabled or not.
	 *
	 * @since 2.9.0
	 *
	 * @return bool
	 */
	public function is_enabled_tracking() {

		return $this->is_enabled_open_email_tracking() || $this->is_enabled_click_link_tracking();
	}

	/**
	 * Get the email connection.
	 *
	 * @since 3.7.0
	 *
	 * @param Email $email Email object.
	 *
	 * @return ConnectionInterface
	 */
	public function get_email_connection( $email ) {

		$connection_id       = $email->get_connection_id();
		$connections_manager = wp_mail_smtp()->get_connections_manager();

		if ( ! empty( $connection_id ) ) {
			$connection = $connections_manager->get_connection( $email->get_connection_id(), false );
		} else {
			$connection = $connections_manager->get_primary_connection();
		}

		return $connection;
	}

	/**
	 * Whether we are on a Logs page (archive, list of all emails).
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function is_archive() {

		return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) && ! isset( $_GET['mode'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
	}

	/**
	 * Whether we are previewing the single email HTML.
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function is_preview() {

		// Nonce verification.
		if (
			! isset( $_GET['_wpnonce'] ) ||
			! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wp_mail_smtp_pro_logs_log_preview' )
		) {
			return false;
		}

		return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) &&
			isset( $_GET['mode'] ) &&
			$_GET['mode'] === 'preview' &&
			! empty( $_GET['email_id'] );
	}

	/**
	 * Whether we are deleting email(s) now.
	 *
	 * @since 1.5.0
	 *
	 * @return bool
	 */
	public function is_deleting() {

		$is_nonce_good = false;

		// Nonce verification.
		if (
			isset( $_REQUEST['_wpnonce'] ) &&
			(
				wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'wp_mail_smtp_pro_logs_log_delete' ) ||
				wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-emails' )
			)
		) {
			$is_nonce_good = true;
		}

		if ( ! $is_nonce_good ) {
			return false;
		}

		return wp_mail_smtp()->get_admin()->is_admin_page( 'logs' ) &&
			! empty( $_REQUEST['email_id'] ) &&
			(
				( // Single email deletion.
					isset( $_REQUEST['mode'] ) && $_REQUEST['mode'] === 'delete'
				) ||
				( // Bulk email deletion.
					isset( $_REQUEST['action'] ) && $_REQUEST['action'] === 'delete' ||
					isset( $_REQUEST['action2'] ) && $_REQUEST['action2'] === 'delete'
				)
			);
	}

	/**
	 * Generate an email preview and display it for users.
	 *
	 * @since 1.5.0
	 */
	public function process_email_preview() {

		if ( ! $this->is_preview() ) {
			return;
		}

		$email = new Email( intval( $_GET['email_id'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotValidated

		// It's a raw HTML (with html/body tags), so print as is.
		echo $email->is_html() ?
			$this->prepare_email_preview_html( $email->get_content() ) : // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			nl2br( esc_html( $email->get_content() ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

		exit;
	}

	/**
	 * Prepare email preview html.
	 *
	 * @since 2.8.0
	 *
	 * @param string $html Email content html.
	 *
	 * @return string
	 */
	public function prepare_email_preview_html( $html ) {

		ob_start();
		?>
		<style type="text/css">
		@media print {
			/* Fix background colors issue when print. */
			body {
				-webkit-print-color-adjust: exact;
			}
		}
		</style>
		<?php
		$print_css = ob_get_clean();

		// Append css in head before closing tag.
		$html = preg_replace( '/<\s*\/\s*head\s*>/', $print_css . '</head>', $html, 1 );

		return $html;
	}

	/**
	 * Delete the email log entry.
	 *
	 * @since 1.5.0
	 */
	public function process_email_delete() {

		if ( ! $this->is_deleting() ) {
			return;
		}

		if ( ! current_user_can( $this->get_manage_capability() ) ) {
			wp_die( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) );
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! isset( $_REQUEST['email_id'] ) ) {
			wp_die( esc_html__( 'Required parameters are missing.', 'wp-mail-smtp-pro' ) );
		}

		$emails = null;

		// phpcs:disable WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( is_array( $_REQUEST['email_id'] ) ) {
			$emails = new EmailsCollection( [ 'ids' => array_map( 'intval', array_values( $_REQUEST['email_id'] ) ) ] );
		} elseif ( is_numeric( $_REQUEST['email_id'] ) ) {
			$emails = new EmailsCollection( [ 'id' => intval( $_REQUEST['email_id'] ) ] );
		}
		// phpcs:enable

		$deleted = 0;

		if ( $emails !== null ) {
			$deleted = $emails->delete();
		}

		if ( $deleted === 1 ) {
			$url = add_query_arg( 'message', 'deleted_one', $this->get_admin_page_url() );
		} elseif ( $deleted > 1 ) {
			$url = add_query_arg( 'message', 'deleted_some', $this->get_admin_page_url() );
		} else {
			$url = add_query_arg( 'message', 'deleted_none', $this->get_admin_page_url() );
		}

		wp_safe_redirect( $url );
		exit;
	}

	/**
	 * Display notices on Logs page when needed.
	 *
	 * @since 1.5.0
	 */
	public function display_notices() {

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$message = isset( $_GET['message'] ) ? sanitize_key( $_GET['message'] ) : '';

		if (
			empty( $message ) ||
			! current_user_can( $this->get_manage_capability() ) ||
			! wp_mail_smtp()->get_admin()->is_admin_page( 'logs' )
		) {
			return;
		}

		switch ( $message ) {
			case 'deleted_one':
				WP::add_admin_notice(
					esc_html__( 'Email Log entry was successfully deleted.', 'wp-mail-smtp-pro' ),
					WP::ADMIN_NOTICE_SUCCESS
				);
				break;

			case 'deleted_some':
				WP::add_admin_notice(
					esc_html__( 'Email Log entries were successfully deleted.', 'wp-mail-smtp-pro' ),
					WP::ADMIN_NOTICE_SUCCESS
				);
				break;

			case 'deleted_none':
				WP::add_admin_notice(
					esc_html__( 'There was an error while processing your request, and no email log entries were deleted. Please try again.', 'wp-mail-smtp-pro' ),
					WP::ADMIN_NOTICE_WARNING
				);
				break;
		}
	}

	/**
	 * Save the email that is going to be sent right now.
	 *
	 * @since 2.1.2
	 *
	 * @param MailCatcherInterface $mailcatcher The MailCatcher object.
	 */
	public function process_smtp_pre_send_before( $mailcatcher ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		$this->set_current_email_id(
			( new SMTP() )->set_source( $mailcatcher )->save_before( $this->get_current_email_parent_id() )
		);
	}

	/**
	 * Update the email that is going to be sent right now.
	 * It was originally saved before in `process_smtp_pre_send_before`.
	 *
	 * @since 1.5.0
	 * @since 2.1.2 Update the Email before it will be sent.
	 *              The saving was already done in `process_smtp_pre_send_before`.
	 *
     * @param MailCatcherInterface $mailcatcher The MailCatcher object.
	 */
	public function process_smtp_send_before( $mailcatcher ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		( new SMTP() )->set_source( $mailcatcher )->update_before( $this->get_current_email_id() );
	}

	/**
	 * Save to DB emails sent through both SMTP and mail().
	 *
	 * @since 1.5.0
	 *
	 * @param bool $is_sent
	 * @param array $to
	 * @param array $cc
	 * @param array $bcc
	 * @param string $subject
	 * @param string $body
	 * @param string $from
	 *
	 * @throws \Exception When email saving failed.
	 */
	public function process_smtp_send_after( $is_sent, $to, $cc, $bcc, $subject, $body, $from ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		( new SMTP() )->save_after( $this->get_current_email_id(), $is_sent );
	}

	/**
	 * Save the actual email data before email send.
	 *
	 * @since 2.9.0
	 *
	 * @param MailCatcherInterface $mailcatcher The MailCatcher object.
	 */
	public function process_pre_send_before( $mailcatcher ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		$this->set_current_email_id(
			( new Common() )->set_source( $mailcatcher )->save_before( $this->get_current_email_parent_id() )
		);
	}

	/**
	 * Save all emails as sent regardless of the actual status. Will be improved in the future.
	 * Supports every mailer, except SMTP, which is handled separately.
	 *
	 * @since 1.5.0
	 *
	 * @param \WPMailSMTP\Providers\MailerAbstract $mailer      The Mailer object.
	 * @param MailCatcherInterface                 $mailcatcher The MailCatcher object.
	 */
	public function process_log_save( MailerAbstract $mailer, MailCatcherInterface $mailcatcher ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		$email_id = ( new Common( $mailer ) )->set_source( $mailcatcher )->save( $this->get_current_email_id() );

		$mailer->verify_sent_status( $email_id );
	}

	/**
	 * Process the failed email sending.
	 *
	 * @since 2.1.0
	 * @since 4.0.0 Added API based mailers error processing.
	 *
	 * @param WP_Error|string $error The WP Error or error message.
	 */
	public function process_smtp_fails( $error ) {

		if ( ! $this->is_valid_db() ) {
			return;
		}

		$mailer_slug = wp_mail_smtp()->get_connections_manager()->get_mail_connection()->get_mailer_slug();

		if ( in_array( $mailer_slug, [ 'smtp', 'pepipost', 'mail' ], true ) ) {
			( new SMTP() )->failed( $this->get_current_email_id(), $error );
		} else {
			( new Common() )->failed( $this->get_current_email_id(), $error );
		}
	}

	/**
	 * Get the current email ID.
	 *
	 * @since 1.5.0
	 *
	 * @return int
	 */
	public function get_current_email_id() {

		return (int) $this->current_email_id;
	}

	/**
	 * Set the email ID that is currently processing.
	 *
	 * @since 1.5.0
	 *
	 * @param int $email_id
	 */
	public function set_current_email_id( $email_id ) {

		$this->current_email_id = (int) $email_id;
	}

	/**
	 * Get the current email parent email log ID.
	 *
	 * @since 3.7.0
	 *
	 * @return int
	 */
	private function get_current_email_parent_id() {

		return $this->current_email_parent_id;
	}

	/**
	 * Set the current email parent email log ID.
	 *
	 * @since 3.7.0
	 *
	 * @param int $email_id The email ID.
	 */
	public function set_current_email_parent_id( $email_id ) {

		$this->current_email_parent_id = (int) $email_id;
	}

	/**
	 * Reset parent email log ID.
	 *
	 * @since 3.7.0
	 */
	public function reset_current_email_parent_id() {

		$this->current_email_parent_id = 0;
	}

	/**
	 * Get the table name.
	 *
	 * @since 1.5.0
	 *
	 * @return string Table name, prefixed.
	 */
	public static function get_table_name() {

		global $wpdb;

		return $wpdb->prefix . 'wpmailsmtp_emails_log';
	}

	/**
	 * Process AJAX request for deleting all email log entries.
	 *
	 * @since 2.5.0
	 */
	public function process_ajax_delete_all_log_entries() {

		if (
			empty( $_POST['nonce'] ) ||
			! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'wp_mail_smtp_pro_delete_log_entries' )
		) {
			wp_send_json_error( esc_html__( 'Access rejected.', 'wp-mail-smtp-pro' ) );
		}

		if ( ! current_user_can( $this->get_manage_capability() ) ) {
			wp_send_json_error( esc_html__( 'You don\'t have the capability to perform this action.', 'wp-mail-smtp-pro' ) );
		}

		global $wpdb;

		$table = self::get_table_name();

		$sql = "TRUNCATE TABLE `$table`;";

		$result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching

		if ( $result !== false ) {

			// Delete all attachments if any.
			( new Attachments() )->delete_all_attachments();

			// Delete all tracking events if any.
			( new TrackingEvents() )->delete_all_events();

			wp_send_json_success( esc_html__( 'All email log entries were deleted successfully.', 'wp-mail-smtp-pro' ) );
		}

		wp_send_json_error(
			sprintf( /* translators: %s - WPDB error message. */
				esc_html__( 'There was an issue while trying to delete all email log entries. Error message: %s', 'wp-mail-smtp-pro' ),
				$wpdb->last_error
			)
		);
	}

	/**
	 * Initialize the background tasks for sent status verification, based on the mailer and the email log ID.
	 *
	 * @since 2.5.0
	 *
	 * @param int            $email_log_id The Email log ID.
	 * @param MailerAbstract $mailer       The mailer in use.
	 */
	public function run_sent_status_verification( $email_log_id, MailerAbstract $mailer ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$mailer_name = $mailer->get_mailer_name();

		if ( Webhooks::is_allowed() && $mailer->get_connection()->is_primary() ) {
			$webhooks_provider = $this->get_webhooks()->get_provider( $mailer_name );

			// Skip AS task verification if webhooks are set up.
			if ( $webhooks_provider !== false && $webhooks_provider->get_setup_status() === Webhooks::SUCCESS_SETUP ) {
				return;
			}
		}

		( new DeliveryVerification() )->schedule_verification( $email_log_id );
	}

	/**
	 * Initialize the Webhooks functionality.
	 *
	 * @since 3.3.0
	 *
	 * @return Webhooks
	 */
	public function get_webhooks() {

		static $webhooks;

		if ( ! isset( $webhooks ) ) {
			$webhooks = new Webhooks();
			$webhooks->hooks();
		}

		return $webhooks;
	}

	/**
	 * Log blocked emails when "Do Not Send" and "Log Blocked Emails" options are enabled.
	 *
	 * @since 4.5.0
	 *
	 * @param MailCatcherInterface $mailcatcher The MailCatcher object.
	 *
	 * @return void
	 */
	public function maybe_log_blocked_email( MailCatcherInterface $mailcatcher ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		// Check if "Log Blocked Emails" option is enabled.
		$options            = Options::init();
		$log_blocked_emails = $options->get( 'logs', 'log_blocked_emails' );

		// Only proceed if the option is enabled and the Logs class exists.
		if ( ! $log_blocked_emails ) {
			return;
		}

		$connection  = wp_mail_smtp()->get_connections_manager()->get_mail_connection();
		$mailer_slug = $connection->get_mailer_slug();

		// Define a custom header, that will be used to identify the plugin and the mailer.
		// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
		$mailcatcher->XMailer = 'WPMailSMTP/Mailer/' . $mailer_slug . ' ' . WPMS_PLUGIN_VER;

		// Only proceed if email logging is enabled.
		if ( ! $this->is_enabled() || ! $this->is_valid_db() ) {
			return;
		}

		// Create a log entry for this blocked email.
		if ( $mailer_slug === 'mail' || $mailer_slug === 'smtp' || $mailer_slug === 'pepipost' ) {
			$email_id = ( new SMTP() )->set_source( $mailcatcher )->save_before();
		} else {
			$mailer   = $connection->get_mailer();
			$email_id = ( new Common( $mailer ) )->set_source( $mailcatcher )->save_before();
		}

		// Add custom notes about this being blocked.
		if ( $email_id > 0 ) {
			$email = new Email( $email_id );

			// Save attachments to the email log.
			( new Attachments() )->process_attachments( $email_id, $mailcatcher->getAttachments() );

			$email->set_error_text( esc_html__( 'Email was blocked by the "Do Not Send" option.', 'wp-mail-smtp-pro' ) );
			$email->set_status( Email::STATUS_BLOCKED );
			$email->save();
		}
	}
}
