v1.2.0
This commit is contained in:
commit
c98dcb7b50
9 changed files with 5952 additions and 0 deletions
664
includes/class-plugin.php
Normal file
664
includes/class-plugin.php
Normal file
|
|
@ -0,0 +1,664 @@
|
|||
<?php
|
||||
/**
|
||||
* Main plugin bootstrap file.
|
||||
*
|
||||
* @package Robotstxt_SMTP
|
||||
*/
|
||||
|
||||
namespace Robotstxt_SMTP;
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
use Robotstxt_SMTP\Admin\Settings_Page;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Main plugin class.
|
||||
*/
|
||||
class Plugin {
|
||||
/**
|
||||
* Custom post type used to store email logs.
|
||||
*/
|
||||
private const LOG_POST_TYPE = 'robotstxt_smtp_log';
|
||||
|
||||
/**
|
||||
* Cron hook used to trigger the log cleanup.
|
||||
*/
|
||||
private const CRON_HOOK = 'robotstxt_smtp_cleanup_logs';
|
||||
|
||||
/**
|
||||
* Holds the class instance.
|
||||
*
|
||||
* @var Plugin|null
|
||||
*/
|
||||
private static ?Plugin $instance = null;
|
||||
|
||||
/**
|
||||
* Settings page handler.
|
||||
*
|
||||
* @var Settings_Page|null
|
||||
*/
|
||||
private ?Settings_Page $settings_page = null;
|
||||
|
||||
/**
|
||||
* Captures the SMTP debug output for the current email.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private array $current_debug_log = array();
|
||||
|
||||
/**
|
||||
* Retrieves the plugin instance.
|
||||
*
|
||||
* @return Plugin Plugin instance.
|
||||
*/
|
||||
public static function get_instance(): Plugin {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the Amazon SES integration plugin is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_amazon_ses_integration_active(): bool {
|
||||
/**
|
||||
* Filters whether the Amazon SES integration should be considered active.
|
||||
*
|
||||
* @since 1.0.1
|
||||
*
|
||||
* @param bool $is_active True when the Amazon SES add-on is active.
|
||||
*/
|
||||
return (bool) \apply_filters( 'robotstxt_smtp_amazon_ses_active', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void {
|
||||
add_action( 'phpmailer_init', array( $this, 'configure_phpmailer' ) );
|
||||
add_filter( 'wp_mail_from', array( $this, 'filter_mail_from' ) );
|
||||
add_filter( 'wp_mail_from_name', array( $this, 'filter_mail_from_name' ) );
|
||||
|
||||
add_action( 'init', array( $this, 'register_log_post_type' ) );
|
||||
add_action( 'init', array( $this, 'maybe_schedule_cleanup' ) );
|
||||
add_action( 'wp_mail_succeeded', array( $this, 'handle_wp_mail_succeeded' ) );
|
||||
add_action( 'wp_mail_failed', array( $this, 'handle_wp_mail_failed' ) );
|
||||
add_action( self::CRON_HOOK, array( $this, 'cleanup_logs' ) );
|
||||
|
||||
if ( is_admin() ) {
|
||||
$this->register_admin();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the admin functionality.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_admin(): void {
|
||||
if ( null === $this->settings_page ) {
|
||||
$this->settings_page = new Settings_Page();
|
||||
}
|
||||
|
||||
$this->settings_page->register_hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the custom post type used to store email logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_log_post_type(): void {
|
||||
register_post_type(
|
||||
self::LOG_POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => esc_html__( 'SMTP Logs', 'robotstxt-smtp' ),
|
||||
'singular_name' => esc_html__( 'SMTP Log', 'robotstxt-smtp' ),
|
||||
),
|
||||
'public' => false,
|
||||
'show_ui' => false,
|
||||
'show_in_menu' => false,
|
||||
'show_in_nav_menus' => false,
|
||||
'exclude_from_search' => true,
|
||||
'publicly_queryable' => false,
|
||||
'supports' => array( 'title', 'editor' ),
|
||||
'capability_type' => 'post',
|
||||
'map_meta_cap' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules or clears the automatic log cleanup task based on settings.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function maybe_schedule_cleanup(): void {
|
||||
$logging = $this->get_logging_settings();
|
||||
|
||||
if ( ! $logging['enabled'] ) {
|
||||
wp_clear_scheduled_hook( self::CRON_HOOK );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false === wp_next_scheduled( self::CRON_HOOK ) ) {
|
||||
wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', self::CRON_HOOK );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the "From" email address used by WordPress.
|
||||
*
|
||||
* @param string $original_email Original email address provided by WordPress.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_mail_from( string $original_email ): string {
|
||||
$settings = $this->get_mailer_settings();
|
||||
$from_email = isset( $settings['from_email'] ) ? sanitize_email( $settings['from_email'] ) : '';
|
||||
|
||||
if ( empty( $from_email ) ) {
|
||||
return sanitize_email( $original_email );
|
||||
}
|
||||
|
||||
return $from_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the "From" name used by WordPress.
|
||||
*
|
||||
* @param string $original_name Original name provided by WordPress.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_mail_from_name( string $original_name ): string {
|
||||
$settings = $this->get_mailer_settings();
|
||||
$from_name = isset( $settings['from_name'] ) ? sanitize_text_field( $settings['from_name'] ) : '';
|
||||
|
||||
if ( '' === $from_name ) {
|
||||
return sanitize_text_field( $original_name );
|
||||
}
|
||||
|
||||
return $from_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the stored mailer settings merged with defaults.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function get_mailer_settings(): array {
|
||||
$defaults = Settings_Page::get_default_settings();
|
||||
|
||||
if ( Settings_Page::is_network_mode_enabled() ) {
|
||||
$settings = get_site_option( Settings_Page::NETWORK_OPTION_NAME, array() );
|
||||
} else {
|
||||
$settings = get_option( Settings_Page::OPTION_NAME, array() );
|
||||
}
|
||||
|
||||
if ( ! is_array( $settings ) ) {
|
||||
$settings = array();
|
||||
}
|
||||
|
||||
$settings = wp_parse_args( $settings, $defaults );
|
||||
|
||||
/**
|
||||
* Filters the SMTP settings before they are consumed by the mailer.
|
||||
*
|
||||
* @since 1.1.1
|
||||
*
|
||||
* @param array<string, mixed> $settings Sanitized settings merged with defaults.
|
||||
*/
|
||||
return (array) \apply_filters( 'robotstxt_smtp_mailer_settings', $settings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures PHPMailer to use the stored SMTP settings.
|
||||
*
|
||||
* @param PHPMailer $phpmailer Mailer instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configure_phpmailer( PHPMailer $phpmailer ): void {
|
||||
if ( self::is_amazon_ses_integration_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->current_debug_log = array();
|
||||
|
||||
$settings = $this->get_mailer_settings();
|
||||
|
||||
if ( empty( $settings['host'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$phpmailer->isSMTP();
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$phpmailer->Host = $settings['host'];
|
||||
$phpmailer->Port = (int) $settings['port'];
|
||||
$phpmailer->SMTPAuth = ! empty( $settings['username'] ) || ! empty( $settings['password'] );
|
||||
$phpmailer->Username = $phpmailer->SMTPAuth ? $settings['username'] : '';
|
||||
$phpmailer->Password = $phpmailer->SMTPAuth ? $settings['password'] : '';
|
||||
$phpmailer->SMTPAutoTLS = false;
|
||||
$phpmailer->SMTPSecure = '';
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
|
||||
if ( 'ssl' === $settings['security'] ) {
|
||||
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$phpmailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
} elseif ( 'tls' === $settings['security'] ) {
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$phpmailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
$phpmailer->SMTPAutoTLS = true;
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
|
||||
$this->register_debug_logger( $phpmailer );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables SMTP debug logging for the current email.
|
||||
*
|
||||
* @param PHPMailer $phpmailer Mailer instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_debug_logger( PHPMailer $phpmailer ): void {
|
||||
$logging = $this->get_logging_settings();
|
||||
|
||||
if ( ! $logging['enabled'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
$phpmailer->SMTPDebug = SMTP::DEBUG_SERVER;
|
||||
$phpmailer->Debugoutput = function ( string $message, int $level ): void {
|
||||
$this->current_debug_log[] = '[' . $level . '] ' . $message;
|
||||
};
|
||||
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores the email data when WordPress reports a successful delivery.
|
||||
*
|
||||
* @param array<string, mixed> $mail_data Email data provided by wp_mail.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_wp_mail_succeeded( array $mail_data ): void {
|
||||
$logging = $this->get_logging_settings();
|
||||
|
||||
if ( ! $logging['enabled'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->get_mailer_settings();
|
||||
|
||||
$subject = isset( $mail_data['subject'] ) ? sanitize_text_field( (string) $mail_data['subject'] ) : '';
|
||||
$message = isset( $mail_data['message'] ) ? (string) $mail_data['message'] : '';
|
||||
$headers = $this->normalize_headers( $mail_data['headers'] ?? array() );
|
||||
$to = $this->normalize_recipients( $mail_data['to'] ?? array() );
|
||||
$from = $this->determine_from_header( $headers, $settings );
|
||||
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => self::LOG_POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $subject,
|
||||
'post_content' => $message,
|
||||
'post_date' => current_time( 'mysql' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if ( is_wp_error( $post_id ) || 0 === $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_to', implode( ', ', $to ) );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_from', $from );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_headers', implode( "\n", $headers ) );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_status', 'sent' );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_error', '' );
|
||||
|
||||
if ( isset( $mail_data['attachments'] ) ) {
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_attachments', maybe_serialize( $mail_data['attachments'] ) );
|
||||
}
|
||||
|
||||
$debug_log = $this->get_sanitized_debug_log();
|
||||
|
||||
if ( $debug_log ) {
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_debug_log', maybe_serialize( $debug_log ) );
|
||||
}
|
||||
|
||||
if ( 'count' === $logging['mode'] ) {
|
||||
$this->cleanup_logs_by_count( $logging['count'] );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cleanup_logs_by_days( $logging['days'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the email data when WordPress reports a failed delivery.
|
||||
*
|
||||
* @param WP_Error $error Error object reported by wp_mail.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_wp_mail_failed( WP_Error $error ): void {
|
||||
$logging = $this->get_logging_settings();
|
||||
|
||||
if ( ! $logging['enabled'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = $this->get_mailer_settings();
|
||||
$data = $error->get_error_data();
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
$data = array();
|
||||
}
|
||||
|
||||
$subject = isset( $data['subject'] ) ? sanitize_text_field( (string) $data['subject'] ) : '';
|
||||
$message = isset( $data['message'] ) ? (string) $data['message'] : '';
|
||||
$headers = $this->normalize_headers( $data['headers'] ?? array() );
|
||||
$to = $this->normalize_recipients( $data['to'] ?? array() );
|
||||
$from = $this->determine_from_header( $headers, $settings );
|
||||
$error_msg = sanitize_text_field( $error->get_error_message() );
|
||||
|
||||
$post_id = wp_insert_post(
|
||||
array(
|
||||
'post_type' => self::LOG_POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => $subject,
|
||||
'post_content' => $message,
|
||||
'post_date' => current_time( 'mysql' ),
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if ( is_wp_error( $post_id ) || 0 === $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_to', implode( ', ', $to ) );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_from', $from );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_headers', implode( "\n", $headers ) );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_status', 'error' );
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_error', $error_msg );
|
||||
|
||||
if ( isset( $data['attachments'] ) ) {
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_attachments', maybe_serialize( $data['attachments'] ) );
|
||||
}
|
||||
|
||||
$debug_log = $this->get_sanitized_debug_log();
|
||||
|
||||
if ( $debug_log ) {
|
||||
update_post_meta( $post_id, '_robotstxt_smtp_debug_log', maybe_serialize( $debug_log ) );
|
||||
}
|
||||
|
||||
if ( 'count' === $logging['mode'] ) {
|
||||
$this->cleanup_logs_by_count( $logging['count'] );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cleanup_logs_by_days( $logging['days'] );
|
||||
}
|
||||
/**
|
||||
* Retrieves the sanitized SMTP debug output captured for the current email.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function get_sanitized_debug_log(): array {
|
||||
if ( empty( $this->current_debug_log ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$sanitized = array_map(
|
||||
static function ( $entry ): string {
|
||||
return sanitize_textarea_field( (string) $entry );
|
||||
},
|
||||
$this->current_debug_log
|
||||
);
|
||||
|
||||
$sanitized = array_filter(
|
||||
$sanitized,
|
||||
static function ( string $line ): bool {
|
||||
return '' !== trim( $line );
|
||||
}
|
||||
);
|
||||
|
||||
return array_values( $sanitized );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs log cleanup when triggered by cron.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup_logs(): void {
|
||||
$logging = $this->get_logging_settings();
|
||||
|
||||
if ( ! $logging['enabled'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'count' === $logging['mode'] ) {
|
||||
$this->cleanup_logs_by_count( $logging['count'] );
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cleanup_logs_by_days( $logging['days'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all stored logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_all_logs(): void {
|
||||
do {
|
||||
$posts = get_posts(
|
||||
array(
|
||||
'post_type' => self::LOG_POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => 100,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $posts as $post_id ) {
|
||||
wp_delete_post( (int) $post_id, true );
|
||||
}
|
||||
} while ( ! empty( $posts ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up logs by enforcing a maximum number of stored records.
|
||||
*
|
||||
* @param int $limit Maximum number of logs to keep.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup_logs_by_count( int $limit ): void {
|
||||
if ( $limit <= 0 ) {
|
||||
$this->clear_all_logs();
|
||||
return;
|
||||
}
|
||||
|
||||
$logs_to_delete = get_posts(
|
||||
array(
|
||||
'post_type' => self::LOG_POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'posts_per_page' => -1,
|
||||
'offset' => $limit,
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $logs_to_delete as $post_id ) {
|
||||
wp_delete_post( (int) $post_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up logs based on the maximum number of days to retain them.
|
||||
*
|
||||
* @param int $days Number of days to keep.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function cleanup_logs_by_days( int $days ): void {
|
||||
if ( $days <= 0 ) {
|
||||
$this->clear_all_logs();
|
||||
return;
|
||||
}
|
||||
|
||||
$cutoff = time() - ( $days * DAY_IN_SECONDS );
|
||||
|
||||
$logs_to_delete = get_posts(
|
||||
array(
|
||||
'post_type' => self::LOG_POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'fields' => 'ids',
|
||||
'date_query' => array(
|
||||
array(
|
||||
'column' => 'post_date_gmt',
|
||||
'before' => gmdate( 'Y-m-d H:i:s', $cutoff ),
|
||||
),
|
||||
),
|
||||
'posts_per_page' => -1,
|
||||
'no_found_rows' => true,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $logs_to_delete as $post_id ) {
|
||||
wp_delete_post( (int) $post_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the headers array into a list of strings.
|
||||
*
|
||||
* @param mixed $headers Headers provided by wp_mail.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function normalize_headers( $headers ): array {
|
||||
if ( empty( $headers ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( is_string( $headers ) ) {
|
||||
$headers = preg_split( "/\r\n|\r|\n/", $headers );
|
||||
}
|
||||
|
||||
if ( ! is_array( $headers ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$normalized = array();
|
||||
|
||||
foreach ( $headers as $header ) {
|
||||
$line = trim( (string) $header );
|
||||
|
||||
if ( '' !== $line ) {
|
||||
$normalized[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the recipient list into an array of strings.
|
||||
*
|
||||
* @param mixed $recipients Recipient information from wp_mail.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function normalize_recipients( $recipients ): array {
|
||||
if ( empty( $recipients ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( is_string( $recipients ) ) {
|
||||
$recipients = explode( ',', $recipients );
|
||||
}
|
||||
|
||||
if ( ! is_array( $recipients ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$normalized = array();
|
||||
|
||||
foreach ( $recipients as $recipient ) {
|
||||
$normalized[] = sanitize_text_field( trim( (string) $recipient ) );
|
||||
}
|
||||
|
||||
return array_filter( $normalized );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the "From" header for the logged message.
|
||||
*
|
||||
* @param array<int, string> $headers Normalized header list.
|
||||
* @param array<string, mixed> $settings Plugin settings.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function determine_from_header( array $headers, array $settings ): string {
|
||||
foreach ( $headers as $header ) {
|
||||
if ( 0 === stripos( $header, 'from:' ) ) {
|
||||
return sanitize_text_field( trim( substr( $header, 5 ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
$from_email = isset( $settings['from_email'] ) ? sanitize_email( $settings['from_email'] ) : '';
|
||||
$from_name = isset( $settings['from_name'] ) ? sanitize_text_field( $settings['from_name'] ) : '';
|
||||
|
||||
if ( $from_email && $from_name ) {
|
||||
return $from_name . ' <' . $from_email . '>';
|
||||
}
|
||||
|
||||
if ( $from_email ) {
|
||||
return $from_email;
|
||||
}
|
||||
|
||||
return get_bloginfo( 'name', 'display' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the logging-related settings from the option store.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function get_logging_settings(): array {
|
||||
$settings = $this->get_mailer_settings();
|
||||
|
||||
return array(
|
||||
'enabled' => ! empty( $settings['logs_enabled'] ),
|
||||
'mode' => in_array( $settings['logs_retention_mode'], array( 'count', 'days' ), true )
|
||||
? $settings['logs_retention_mode']
|
||||
: 'count',
|
||||
'count' => isset( $settings['logs_retention_count'] ) ? (int) $settings['logs_retention_count'] : 0,
|
||||
'days' => isset( $settings['logs_retention_days'] ) ? (int) $settings['logs_retention_days'] : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
4729
includes/class-settings-page.php
Normal file
4729
includes/class-settings-page.php
Normal file
File diff suppressed because it is too large
Load diff
107
includes/class-smtp-diagnostics-client.php
Normal file
107
includes/class-smtp-diagnostics-client.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/**
|
||||
* SMTP diagnostics client.
|
||||
*
|
||||
* @package Robotstxt_SMTP
|
||||
*/
|
||||
|
||||
namespace Robotstxt_SMTP\Admin;
|
||||
|
||||
use PHPMailer\PHPMailer\SMTP;
|
||||
|
||||
/**
|
||||
* SMTP client wrapper used for diagnostics.
|
||||
*/
|
||||
class SMTP_Diagnostics_Client extends SMTP {
|
||||
/**
|
||||
* Tracks whether the most recent command timed out.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private bool $last_command_timed_out = false;
|
||||
|
||||
/**
|
||||
* Executes an SMTP command and returns the raw reply.
|
||||
*
|
||||
* @param string $name Command name.
|
||||
* @param string $command_string Full command string.
|
||||
* @param array<int,int> $expected_codes Expected success codes.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function execute_command( string $name, string $command_string, array $expected_codes = array( 250 ) ): array {
|
||||
$this->last_command_timed_out = false;
|
||||
$this->reset_error_state();
|
||||
|
||||
$expected = ! empty( $expected_codes ) ? array_map( 'intval', $expected_codes ) : array( 250 );
|
||||
|
||||
$success = parent::sendCommand( $name, $command_string, $expected );
|
||||
|
||||
if ( ! $success ) {
|
||||
$this->last_command_timed_out = $this->error_indicates_timeout( parent::getError() );
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => $success,
|
||||
'reply' => $this->getLastReply(),
|
||||
'timed_out' => $this->last_command_timed_out,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the last command timed out.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function did_last_command_timeout(): bool {
|
||||
return $this->last_command_timed_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates STARTTLS while tracking timeout state.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function startTLS(): bool {
|
||||
$this->last_command_timed_out = false;
|
||||
$this->reset_error_state();
|
||||
|
||||
$result = parent::startTLS();
|
||||
|
||||
if ( ! $result ) {
|
||||
$this->last_command_timed_out = $this->error_indicates_timeout( parent::getError() );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored error state.
|
||||
*/
|
||||
private function reset_error_state(): void {
|
||||
parent::setError( '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an error array represents a timeout.
|
||||
*
|
||||
* @param array<string, mixed> $error Error details.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function error_indicates_timeout( array $error ): bool {
|
||||
foreach ( array( 'error', 'detail', 'smtp_code_ex' ) as $key ) {
|
||||
if ( empty( $error[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = (string) $error[ $key ];
|
||||
|
||||
if ( false !== stripos( $value, 'timed out' ) || false !== stripos( $value, 'timed-out' ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue