787 lines
28 KiB
PHP
787 lines
28 KiB
PHP
<?php
|
|
/**
|
|
* Settings page for admin.
|
|
*
|
|
* @package ROBOTSTXT_SMTP_Newsletter
|
|
*/
|
|
|
|
namespace Robotstxt_SMTP_Newsletter\Admin;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Settings page class.
|
|
*
|
|
* Manages plugin configuration in WordPress admin.
|
|
*/
|
|
class Settings_Page {
|
|
|
|
/**
|
|
* Singleton instance.
|
|
*
|
|
* @var Settings_Page|null
|
|
*/
|
|
private static $instance = null;
|
|
|
|
/**
|
|
* Option name.
|
|
*
|
|
* @var string
|
|
*/
|
|
private const OPTION_NAME = 'robotstxt_smtp_newsletter_options';
|
|
|
|
/**
|
|
* Get singleton instance.
|
|
*
|
|
* @return Settings_Page
|
|
*/
|
|
public static function get_instance() {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
private function __construct() {
|
|
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
|
// Note: We don't add admin_menu here because NewsletterMailerAddon parent handles menu creation.
|
|
// The parent expects admin/index.php to render the settings page.
|
|
}
|
|
|
|
/**
|
|
* Static method to register settings without creating instance.
|
|
* Used when parent addon handles menu creation.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function register_settings_static() {
|
|
// Just ensure the instance is created so register_settings hook is added.
|
|
self::get_instance();
|
|
}
|
|
|
|
/**
|
|
* Register settings.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function register_settings() {
|
|
register_setting(
|
|
'robotstxt_smtp_newsletter',
|
|
self::OPTION_NAME,
|
|
array(
|
|
'sanitize_callback' => array( $this, 'sanitize_options' ),
|
|
)
|
|
);
|
|
|
|
add_settings_section(
|
|
'robotstxt_smtp_newsletter_main',
|
|
__( 'SMTP (by ROBOTSTXT) Integration', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_section' ),
|
|
'robotstxt_smtp_newsletter'
|
|
);
|
|
|
|
add_settings_field(
|
|
'enabled',
|
|
__( 'Enable Integration', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_enabled_field' ),
|
|
'robotstxt_smtp_newsletter',
|
|
'robotstxt_smtp_newsletter_main'
|
|
);
|
|
|
|
add_settings_field(
|
|
'batch_size',
|
|
__( 'Batch Size', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_batch_size_field' ),
|
|
'robotstxt_smtp_newsletter',
|
|
'robotstxt_smtp_newsletter_main'
|
|
);
|
|
|
|
// Queue Status section.
|
|
add_settings_section(
|
|
'robotstxt_smtp_newsletter_queue_status',
|
|
__( 'Queue Status', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_queue_status_section' ),
|
|
'robotstxt_smtp_newsletter'
|
|
);
|
|
|
|
// Recent Campaigns section.
|
|
add_settings_section(
|
|
'robotstxt_smtp_newsletter_recent_campaigns',
|
|
__( 'Recent Campaigns', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_recent_campaigns_section' ),
|
|
'robotstxt_smtp_newsletter'
|
|
);
|
|
|
|
// Worker configuration section (at the end).
|
|
add_settings_section(
|
|
'robotstxt_smtp_newsletter_worker',
|
|
__( 'Queue Worker Configuration', 'robotstxt-smtp-newsletter' ),
|
|
array( $this, 'render_worker_section' ),
|
|
'robotstxt_smtp_newsletter'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Add settings page to admin menu.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function add_settings_page() {
|
|
add_submenu_page(
|
|
'newsletter_main_index',
|
|
__( 'SMTP (by ROBOTSTXT)', 'robotstxt-smtp-newsletter' ),
|
|
__( 'SMTP (by ROBOTSTXT)', 'robotstxt-smtp-newsletter' ),
|
|
'manage_options',
|
|
'robotstxt-smtp-newsletter',
|
|
array( $this, 'render_page' )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Render settings page.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_page() {
|
|
if ( ! current_user_can( 'manage_options' ) ) {
|
|
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-smtp-newsletter' ) );
|
|
}
|
|
|
|
?>
|
|
<div class="wrap">
|
|
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
|
|
|
<form action="options.php" method="post">
|
|
<?php
|
|
settings_fields( 'robotstxt_smtp_newsletter' );
|
|
do_settings_sections( 'robotstxt_smtp_newsletter' );
|
|
submit_button();
|
|
?>
|
|
</form>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render section description.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_section() {
|
|
echo '<p>';
|
|
esc_html_e( 'Configure how Newsletter uses SMTP (by ROBOTSTXT) for email delivery.', 'robotstxt-smtp-newsletter' );
|
|
echo '</p>';
|
|
|
|
// Show current SMTP configuration.
|
|
if ( ! class_exists( '\Robotstxt_SMTP\Plugin' ) ) {
|
|
echo '<div class="notice notice-error inline"><p>';
|
|
esc_html_e( 'ROBOTSTXT SMTP plugin is not active. Please activate it first.', 'robotstxt-smtp-newsletter' );
|
|
echo '</p></div>';
|
|
return;
|
|
}
|
|
|
|
$plugin = \Robotstxt_SMTP_Newsletter\Plugin::get_instance();
|
|
$settings = $plugin->get_smtp_settings();
|
|
|
|
echo '<div class="notice notice-info inline"><p>';
|
|
echo '<strong>' . esc_html__( 'Current SMTP Configuration:', 'robotstxt-smtp-newsletter' ) . '</strong><br>';
|
|
echo '<ul style="margin: 10px 0 0 20px;">';
|
|
echo '<li>' . sprintf(
|
|
esc_html__( 'Host: %s:%d', 'robotstxt-smtp-newsletter' ),
|
|
esc_html( $settings['host'] ?? '' ),
|
|
absint( $settings['port'] ?? 0 )
|
|
) . '</li>';
|
|
|
|
$encryption = $settings['encryption'] ?? '';
|
|
if ( ! empty( $encryption ) ) {
|
|
echo '<li>' . sprintf(
|
|
esc_html__( 'Encryption: %s', 'robotstxt-smtp-newsletter' ),
|
|
esc_html( strtoupper( $encryption ) )
|
|
) . '</li>';
|
|
}
|
|
|
|
if ( \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active() ) {
|
|
echo '<li><strong>' . esc_html__( 'Amazon SES: Active', 'robotstxt-smtp-newsletter' ) . '</strong></li>';
|
|
}
|
|
|
|
echo '</ul>';
|
|
echo '</p></div>';
|
|
|
|
// Show optimal batch size recommendations.
|
|
$this->render_recommendations();
|
|
}
|
|
|
|
/**
|
|
* Render settings recommendations.
|
|
*
|
|
* @return void
|
|
*/
|
|
private function render_recommendations() {
|
|
$optimal = $this->calculate_optimal_settings();
|
|
$current_options = \Robotstxt_SMTP_Newsletter\Plugin::get_instance()->get_settings();
|
|
$current_batch = absint( $current_options['batch_size'] ?? 10 );
|
|
|
|
// Determine batch size status.
|
|
$batch_status = 'optimal';
|
|
if ( $current_batch < $optimal['batch_size'] * 0.5 ) {
|
|
$batch_status = 'too_low';
|
|
} elseif ( $current_batch > $optimal['batch_size'] * 1.5 ) {
|
|
$batch_status = 'too_high';
|
|
} elseif ( abs( $current_batch - $optimal['batch_size'] ) > 5 ) {
|
|
$batch_status = 'suboptimal';
|
|
}
|
|
|
|
// Status styling.
|
|
$notice_class = 'notice-info';
|
|
$status_icon = 'ⓘ'; // Info icon.
|
|
|
|
if ( 'optimal' === $batch_status ) {
|
|
$notice_class = 'notice-success';
|
|
$status_icon = '✔'; // Checkmark.
|
|
} elseif ( in_array( $batch_status, array( 'too_low', 'too_high' ), true ) ) {
|
|
$notice_class = 'notice-warning';
|
|
$status_icon = '⚠'; // Warning icon.
|
|
}
|
|
|
|
echo '<div class="notice ' . esc_attr( $notice_class ) . ' inline" style="margin-top: 15px;"><p>';
|
|
echo '<strong>' . $status_icon . ' ' . esc_html__( 'Settings Recommendations', 'robotstxt-smtp-newsletter' ) . '</strong><br>';
|
|
echo '<ul style="margin: 10px 0 0 20px;">';
|
|
|
|
// Show delivery method.
|
|
$delivery_method = $optimal['is_ses']
|
|
? __( 'Amazon SES API', 'robotstxt-smtp-newsletter' )
|
|
: __( 'SMTP', 'robotstxt-smtp-newsletter' );
|
|
echo '<li>' . sprintf(
|
|
/* translators: %s: delivery method */
|
|
esc_html__( 'Delivery method: %s', 'robotstxt-smtp-newsletter' ),
|
|
esc_html( $delivery_method )
|
|
) . '</li>';
|
|
|
|
// Show calculation reasoning.
|
|
if ( ! empty( $optimal['reasoning'] ) ) {
|
|
foreach ( $optimal['reasoning'] as $reason ) {
|
|
echo '<li>' . esc_html( $reason ) . '</li>';
|
|
}
|
|
}
|
|
|
|
echo '</ul>';
|
|
|
|
// Show recommendation.
|
|
echo '<p style="margin-top: 10px;"><strong>';
|
|
|
|
if ( 'optimal' === $batch_status ) {
|
|
echo '✔ ' . sprintf(
|
|
/* translators: %d: batch size */
|
|
esc_html__( 'Batch size: Your current setting (%d) is optimal!', 'robotstxt-smtp-newsletter' ),
|
|
$current_batch
|
|
);
|
|
} else {
|
|
echo '► ' . sprintf(
|
|
/* translators: %d: recommended batch size */
|
|
esc_html__( 'Recommended batch size: %d emails/batch', 'robotstxt-smtp-newsletter' ),
|
|
$optimal['batch_size']
|
|
);
|
|
|
|
if ( 'too_low' === $batch_status ) {
|
|
echo '<br> ' . sprintf(
|
|
/* translators: %d: current batch size */
|
|
esc_html__( 'Current (%d) is significantly lower than optimal. Increase it to enqueue faster.', 'robotstxt-smtp-newsletter' ),
|
|
$current_batch
|
|
);
|
|
} elseif ( 'too_high' === $batch_status ) {
|
|
echo '<br> ' . sprintf(
|
|
/* translators: %d: current batch size */
|
|
esc_html__( 'Current (%d) is too high and may trigger rate limits. Reduce it to avoid errors.', 'robotstxt-smtp-newsletter' ),
|
|
$current_batch
|
|
);
|
|
} elseif ( 'suboptimal' === $batch_status ) {
|
|
echo '<br> ' . sprintf(
|
|
/* translators: 1: current batch size, 2: recommended batch size */
|
|
esc_html__( 'Current (%1$d) could be optimized. Consider changing to %2$d.', 'robotstxt-smtp-newsletter' ),
|
|
$current_batch,
|
|
$optimal['batch_size']
|
|
);
|
|
}
|
|
}
|
|
|
|
echo '</strong></p>';
|
|
echo '</p></div>';
|
|
}
|
|
|
|
/**
|
|
* Render enabled field.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_enabled_field() {
|
|
$options = get_option( self::OPTION_NAME, array() );
|
|
$enabled = ! empty( $options['enabled'] );
|
|
?>
|
|
<label>
|
|
<input type="checkbox" name="<?php echo esc_attr( self::OPTION_NAME ); ?>[enabled]" value="1" <?php checked( $enabled ); ?>>
|
|
<?php esc_html_e( 'Use SMTP (by ROBOTSTXT) for Newsletter delivery', 'robotstxt-smtp-newsletter' ); ?>
|
|
</label>
|
|
<p class="description">
|
|
<?php esc_html_e( 'When enabled, Newsletter will use SMTP (by ROBOTSTXT) configuration with SMTPKeepAlive for efficient bulk sending.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render batch size field.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_batch_size_field() {
|
|
$options = get_option( self::OPTION_NAME, array() );
|
|
$batch_size = absint( $options['batch_size'] ?? 10 );
|
|
?>
|
|
<input type="number" name="<?php echo esc_attr( self::OPTION_NAME ); ?>[batch_size]" value="<?php echo esc_attr( $batch_size ); ?>" min="1" max="10000" class="small-text">
|
|
<p class="description">
|
|
<?php esc_html_e( 'Number of emails each worker picks up and sends per cycle. Workers read this value from the database automatically — no need to specify it in the command line.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
<table style="margin-top:10px; border-collapse:collapse; font-size:13px;">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left; padding:4px 12px 4px 0; color:#666; font-weight:normal; border-bottom:1px solid #ddd;">
|
|
<?php esc_html_e( 'Workers', 'robotstxt-smtp-newsletter' ); ?>
|
|
</th>
|
|
<th style="text-align:left; padding:4px 12px 4px 0; color:#666; font-weight:normal; border-bottom:1px solid #ddd;">
|
|
<?php esc_html_e( 'Batch Size', 'robotstxt-smtp-newsletter' ); ?>
|
|
</th>
|
|
<th style="text-align:left; padding:4px 0; color:#666; font-weight:normal; border-bottom:1px solid #ddd;">
|
|
<?php esc_html_e( 'Simultaneous emails', 'robotstxt-smtp-newsletter' ); ?>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$examples = array( 1, 5, 10, 20, 50 );
|
|
foreach ( $examples as $example ) :
|
|
$simultaneous = 10 * $example;
|
|
$is_current = ( $example === $batch_size );
|
|
$row_style = $is_current ? 'background:#e8f4fd; font-weight:bold;' : '';
|
|
?>
|
|
<tr style="<?php echo esc_attr( $row_style ); ?>">
|
|
<td style="padding:3px 12px 3px 0;">10</td>
|
|
<td style="padding:3px 12px 3px 0;">
|
|
<?php echo absint( $example ); ?>
|
|
<?php if ( $is_current ) : ?>
|
|
← <?php esc_html_e( 'current', 'robotstxt-smtp-newsletter' ); ?>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="padding:3px 0;">
|
|
<strong><?php echo number_format_i18n( $simultaneous ); ?></strong>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php if ( ! in_array( $batch_size, $examples, true ) && $batch_size > 0 ) : ?>
|
|
<tr style="background:#e8f4fd; font-weight:bold;">
|
|
<td style="padding:3px 12px 3px 0;">10</td>
|
|
<td style="padding:3px 12px 3px 0;">
|
|
<?php echo absint( $batch_size ); ?>
|
|
← <?php esc_html_e( 'current', 'robotstxt-smtp-newsletter' ); ?>
|
|
</td>
|
|
<td style="padding:3px 0;">
|
|
<strong><?php echo number_format_i18n( 10 * $batch_size ); ?></strong>
|
|
</td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
<p class="description" style="margin-top:8px;">
|
|
<?php esc_html_e( 'Table assumes the recommended setup of 10 parallel workers. Adjust your worker command to run more or fewer workers.', 'robotstxt-smtp-newsletter' ); ?><br>
|
|
<?php esc_html_e( 'See calculated recommendations below based on your rate limits.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render recent campaigns section.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_recent_campaigns_section() {
|
|
$stats = \Robotstxt_SMTP_Newsletter\Statistics::get_instance();
|
|
|
|
// Sync statistics from queue.
|
|
$stats->sync_from_queue();
|
|
|
|
// Get recent campaigns (last 5).
|
|
$campaigns = $stats->get_recent_campaigns( 5 );
|
|
?>
|
|
<p class="description" style="margin-bottom:15px;">
|
|
<?php esc_html_e( 'Statistics from the most recent email campaigns.', 'robotstxt-smtp-newsletter' ); ?>
|
|
<a href="<?php echo esc_url( admin_url( 'admin.php?page=robotstxt-smtp-newsletter-stats' ) ); ?>" style="margin-left:10px;">
|
|
<?php esc_html_e( 'View detailed statistics →', 'robotstxt-smtp-newsletter' ); ?>
|
|
</a>
|
|
</p>
|
|
|
|
<?php if ( empty( $campaigns ) ) : ?>
|
|
<div class="notice notice-info inline" style="max-width:700px;">
|
|
<p>
|
|
<?php esc_html_e( 'No campaigns found yet. Send your first Newsletter campaign to see statistics here.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
</div>
|
|
<?php else : ?>
|
|
<table class="widefat" style="max-width:1000px; margin-top:10px;">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e( 'Campaign', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:center;"><?php esc_html_e( 'Started', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:center;"><?php esc_html_e( 'Finished', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Total', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Sent', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Failed', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Duration', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Speed', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ( $campaigns as $campaign ) : ?>
|
|
<?php
|
|
// Calculate success rate.
|
|
$success_rate = $campaign->total_emails > 0
|
|
? ( $campaign->sent_emails / $campaign->total_emails ) * 100
|
|
: 0;
|
|
$row_style = '';
|
|
if ( $success_rate < 95 ) {
|
|
$row_style = 'background-color: #fff3cd;'; // Warning yellow for low success rate.
|
|
}
|
|
?>
|
|
<tr style="<?php echo esc_attr( $row_style ); ?>">
|
|
<td>
|
|
<strong><?php echo esc_html( $campaign->campaign_name ); ?></strong>
|
|
<?php if ( $success_rate < 95 && $campaign->finished_at ) : ?>
|
|
<span style="color:#856404; margin-left:5px;" title="<?php esc_attr_e( 'Low success rate', 'robotstxt-smtp-newsletter' ); ?>">⚠</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="text-align:center; font-size:12px;">
|
|
<?php
|
|
if ( $campaign->started_at ) {
|
|
echo esc_html( mysql2date( 'Y-m-d H:i', $campaign->started_at ) );
|
|
} else {
|
|
echo '-';
|
|
}
|
|
?>
|
|
</td>
|
|
<td style="text-align:center; font-size:12px;">
|
|
<?php
|
|
if ( $campaign->finished_at ) {
|
|
echo esc_html( mysql2date( 'Y-m-d H:i', $campaign->finished_at ) );
|
|
} else {
|
|
echo '<span style="color:#f0ad4e;">' . esc_html__( 'In progress', 'robotstxt-smtp-newsletter' ) . '</span>';
|
|
}
|
|
?>
|
|
</td>
|
|
<td style="text-align:right;">
|
|
<strong><?php echo number_format_i18n( $campaign->total_emails ); ?></strong>
|
|
</td>
|
|
<td style="text-align:right; color:#5cb85c;">
|
|
<strong><?php echo number_format_i18n( $campaign->sent_emails ); ?></strong>
|
|
<small style="color:#666;">(<?php echo number_format_i18n( $success_rate, 1 ); ?>%)</small>
|
|
</td>
|
|
<td style="text-align:right;">
|
|
<?php if ( $campaign->failed_emails > 0 ) : ?>
|
|
<strong style="color:#d9534f;"><?php echo number_format_i18n( $campaign->failed_emails ); ?></strong>
|
|
<?php else : ?>
|
|
<span style="color:#999;">0</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="text-align:right; font-size:12px;">
|
|
<?php
|
|
if ( $campaign->duration_seconds ) {
|
|
$hours = floor( $campaign->duration_seconds / 3600 );
|
|
$minutes = floor( ( $campaign->duration_seconds % 3600 ) / 60 );
|
|
$seconds = $campaign->duration_seconds % 60;
|
|
|
|
if ( $hours > 0 ) {
|
|
echo sprintf( '%dh %dm', $hours, $minutes );
|
|
} elseif ( $minutes > 0 ) {
|
|
echo sprintf( '%dm %ds', $minutes, $seconds );
|
|
} else {
|
|
echo sprintf( '%ds', $seconds );
|
|
}
|
|
} else {
|
|
echo '-';
|
|
}
|
|
?>
|
|
</td>
|
|
<td style="text-align:right; font-size:12px;">
|
|
<?php
|
|
if ( $campaign->emails_per_hour ) {
|
|
echo '<strong>' . number_format_i18n( $campaign->emails_per_hour, 0 ) . '</strong> /h';
|
|
if ( $campaign->emails_per_minute ) {
|
|
echo '<br><small style="color:#666;">' . number_format_i18n( $campaign->emails_per_minute, 1 ) . ' /min</small>';
|
|
}
|
|
} else {
|
|
echo '-';
|
|
}
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render worker configuration section.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_worker_section() {
|
|
$worker_path = ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'worker.php';
|
|
$is_multisite = is_multisite();
|
|
$site_url = get_site_url();
|
|
|
|
$options = get_option( 'robotstxt_smtp_newsletter_options', array() );
|
|
$batch_size = max( 1, absint( $options['batch_size'] ?? 10 ) );
|
|
?>
|
|
<div class="notice notice-info inline">
|
|
<p><strong><?php esc_html_e( 'Important: Queue Processing', 'robotstxt-smtp-newsletter' ); ?></strong></p>
|
|
<p>
|
|
<?php esc_html_e( 'Newsletter emails are queued to the database and processed by external worker processes.', 'robotstxt-smtp-newsletter' ); ?>
|
|
<?php esc_html_e( 'Workers read the Batch Size from the database automatically — no need to pass it as a command-line argument.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
|
|
<table style="margin:10px 0 15px 0; border-collapse:collapse; font-size:13px;">
|
|
<tr>
|
|
<td style="padding:3px 15px 3px 0; color:#666;"><?php esc_html_e( 'Parallel workers:', 'robotstxt-smtp-newsletter' ); ?></td>
|
|
<td style="padding:3px 0;"><strong>10</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding:3px 15px 3px 0; color:#666;"><?php esc_html_e( 'Batch size (current setting):', 'robotstxt-smtp-newsletter' ); ?></td>
|
|
<td style="padding:3px 0;"><strong><?php echo absint( $batch_size ); ?></strong></td>
|
|
</tr>
|
|
<tr style="border-top:1px solid #ddd;">
|
|
<td style="padding:6px 15px 3px 0; color:#666;"><?php esc_html_e( 'Simultaneous emails:', 'robotstxt-smtp-newsletter' ); ?></td>
|
|
<td style="padding:6px 0;"><strong style="font-size:15px;"><?php echo number_format_i18n( 10 * $batch_size ); ?></strong>
|
|
<small style="color:#666; margin-left:5px;">(10 × <?php echo absint( $batch_size ); ?>)</small>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<?php if ( $is_multisite ) : ?>
|
|
<div style="background:#fff3cd; border-left:4px solid #ffc107; padding:10px; margin:15px 0;">
|
|
<p style="margin:0;">
|
|
<strong><?php esc_html_e( 'MultiSite Detected:', 'robotstxt-smtp-newsletter' ); ?></strong>
|
|
<?php esc_html_e( 'Worker commands must include the --url parameter to specify which site to process.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
</div>
|
|
|
|
<h4><?php esc_html_e( 'Single Worker Command (MultiSite)', 'robotstxt-smtp-newsletter' ); ?></h4>
|
|
<code style="display:block; padding:10px; background:#f5f5f5; overflow-x:auto; margin:10px 0; font-family:monospace;">
|
|
php <?php echo esc_html( $worker_path ); ?> "w1" --url="<?php echo esc_html( $site_url ); ?>"
|
|
</code>
|
|
|
|
<h4><?php esc_html_e( 'Parallel Processing (10 workers) - MultiSite', 'robotstxt-smtp-newsletter' ); ?></h4>
|
|
<code style="display:block; padding:10px; background:#f5f5f5; overflow-x:auto; margin:10px 0; font-family:monospace;">
|
|
seq 1 10 | xargs -P10 -I{} php <?php echo esc_html( $worker_path ); ?> "w{}" --url="<?php echo esc_html( $site_url ); ?>"
|
|
</code>
|
|
|
|
<p style="margin-top:10px; color:#856404;">
|
|
<strong><?php esc_html_e( 'Note:', 'robotstxt-smtp-newsletter' ); ?></strong>
|
|
<?php
|
|
printf(
|
|
/* translators: %s: site URL */
|
|
esc_html__( 'The commands above are configured for the current site: %s', 'robotstxt-smtp-newsletter' ),
|
|
'<code>' . esc_html( $site_url ) . '</code>'
|
|
);
|
|
?>
|
|
</p>
|
|
<?php else : ?>
|
|
<h4><?php esc_html_e( 'Single Worker Command', 'robotstxt-smtp-newsletter' ); ?></h4>
|
|
<code style="display:block; padding:10px; background:#f5f5f5; overflow-x:auto; margin:10px 0; font-family:monospace;">
|
|
php <?php echo esc_html( $worker_path ); ?> "w1"
|
|
</code>
|
|
|
|
<h4><?php esc_html_e( 'Parallel Processing (10 workers)', 'robotstxt-smtp-newsletter' ); ?></h4>
|
|
<code style="display:block; padding:10px; background:#f5f5f5; overflow-x:auto; margin:10px 0; font-family:monospace;">
|
|
seq 1 10 | xargs -P10 -I{} php <?php echo esc_html( $worker_path ); ?> "w{}"
|
|
</code>
|
|
<?php endif; ?>
|
|
|
|
<p style="margin-top:15px;">
|
|
<strong><?php esc_html_e( 'Recommended Setup:', 'robotstxt-smtp-newsletter' ); ?></strong>
|
|
<?php esc_html_e( 'Add to system cron (crontab) to run every minute, or use a process supervisor like systemd or supervisord.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render queue status section.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function render_queue_status_section() {
|
|
$queue = \Robotstxt_SMTP_Newsletter\Queue::get_instance();
|
|
$stats = $queue->get_queue_stats();
|
|
?>
|
|
<p class="description" style="margin-bottom:15px;">
|
|
<?php esc_html_e( 'Real-time statistics from the email queue. Refresh the page to update.', 'robotstxt-smtp-newsletter' ); ?>
|
|
</p>
|
|
<table class="widefat" style="max-width:700px; margin-top:10px;">
|
|
<thead>
|
|
<tr>
|
|
<th><?php esc_html_e( 'Status', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:right;"><?php esc_html_e( 'Count', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
<th style="text-align:left;"><?php esc_html_e( 'Description', 'robotstxt-smtp-newsletter' ); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong><?php esc_html_e( 'Pending', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
|
<td style="text-align:right;"><strong style="font-size:18px;"><?php echo number_format_i18n( $stats['pending'] ); ?></strong></td>
|
|
<td style="color:#666;">
|
|
<?php esc_html_e( 'Emails waiting to be processed by workers', 'robotstxt-smtp-newsletter' ); ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php esc_html_e( 'Processing', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
|
<td style="text-align:right;"><strong style="font-size:18px; color:#f0ad4e;"><?php echo number_format_i18n( $stats['locked'] ); ?></strong></td>
|
|
<td style="color:#666;">
|
|
<?php esc_html_e( 'Emails currently being sent by active workers', 'robotstxt-smtp-newsletter' ); ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php esc_html_e( 'Sent (24h)', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
|
<td style="text-align:right;"><strong style="font-size:18px; color:#5cb85c;"><?php echo number_format_i18n( $stats['sent_24h'] ); ?></strong></td>
|
|
<td style="color:#666;">
|
|
<?php esc_html_e( 'Emails successfully sent in the last 24 hours', 'robotstxt-smtp-newsletter' ); ?>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong><?php esc_html_e( 'Failed (24h)', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
|
<td style="text-align:right;"><strong style="font-size:18px; color:#d9534f;"><?php echo number_format_i18n( $stats['failed_24h'] ); ?></strong></td>
|
|
<td style="color:#666;">
|
|
<?php esc_html_e( 'Emails that failed after 3 retry attempts', 'robotstxt-smtp-newsletter' ); ?>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Calculate optimal batch size based on rate limits.
|
|
*
|
|
* @return array<string, mixed> Array with 'batch_size', 'reasoning', and 'is_ses'.
|
|
*/
|
|
private function calculate_optimal_settings() {
|
|
$plugin = \Robotstxt_SMTP_Newsletter\Plugin::get_instance();
|
|
$settings = $plugin->get_smtp_settings();
|
|
|
|
// Get rate limits.
|
|
$rate_per_second = absint( $settings['rate_limit_per_second'] ?? 0 );
|
|
$rate_per_hour = absint( $settings['rate_limit_per_hour'] ?? 0 );
|
|
$rate_per_day = absint( $settings['rate_limit_per_day'] ?? 0 );
|
|
|
|
// Check if Amazon SES is active.
|
|
$is_ses = \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active();
|
|
|
|
$optimal_batch = 10; // Default fallback.
|
|
$reasoning = array();
|
|
$limiting_factor = '';
|
|
|
|
// If no rate limits are set.
|
|
if ( 0 === $rate_per_second && 0 === $rate_per_hour && 0 === $rate_per_day ) {
|
|
$optimal_batch = $is_ses ? 50 : 20;
|
|
$reasoning[] = __( 'No rate limits configured.', 'robotstxt-smtp-newsletter' );
|
|
$reasoning[] = $is_ses
|
|
? __( 'Using default for Amazon SES (50 emails/batch).', 'robotstxt-smtp-newsletter' )
|
|
: __( 'Using default for SMTP (20 emails/batch).', 'robotstxt-smtp-newsletter' );
|
|
$limiting_factor = 'default';
|
|
} else {
|
|
// Calculate based on rate limits - use conservative values for queuing.
|
|
$candidates = array();
|
|
|
|
if ( $rate_per_second > 0 ) {
|
|
// For per-second: batch should be reasonable for burst processing.
|
|
$max_emails = min( 100, $rate_per_second * 30 ); // 30 seconds worth.
|
|
$candidates['per_second'] = $max_emails;
|
|
$reasoning[] = sprintf(
|
|
/* translators: 1: emails per second, 2: calculated max */
|
|
__( 'Per-second limit: %1$d emails/sec → batch of %2$d', 'robotstxt-smtp-newsletter' ),
|
|
$rate_per_second,
|
|
$max_emails
|
|
);
|
|
}
|
|
|
|
if ( $rate_per_hour > 0 ) {
|
|
// For per-hour: assume worker processes every 5 minutes.
|
|
$batches_per_hour = 12;
|
|
$max_emails = floor( $rate_per_hour / $batches_per_hour );
|
|
$candidates['per_hour'] = $max_emails;
|
|
$reasoning[] = sprintf(
|
|
/* translators: 1: emails per hour, 2: calculated max */
|
|
__( 'Per-hour limit: %1$d emails/hour → batch of %2$d', 'robotstxt-smtp-newsletter' ),
|
|
$rate_per_hour,
|
|
$max_emails
|
|
);
|
|
}
|
|
|
|
if ( $rate_per_day > 0 ) {
|
|
// For per-day: assume 288 worker runs per day (every 5 minutes).
|
|
$batches_per_day = 288;
|
|
$max_emails = floor( $rate_per_day / $batches_per_day );
|
|
$candidates['per_day'] = $max_emails;
|
|
$reasoning[] = sprintf(
|
|
/* translators: 1: emails per day, 2: calculated max */
|
|
__( 'Per-day limit: %1$d emails/day → batch of %2$d', 'robotstxt-smtp-newsletter' ),
|
|
$rate_per_day,
|
|
$max_emails
|
|
);
|
|
}
|
|
|
|
// The optimal batch size is the minimum of all candidates.
|
|
if ( ! empty( $candidates ) ) {
|
|
$optimal_batch = min( $candidates );
|
|
$limiting_factor = array_search( $optimal_batch, $candidates, true );
|
|
|
|
// Apply safety margin (90%).
|
|
$optimal_batch = max( 1, floor( $optimal_batch * 0.9 ) );
|
|
|
|
$reasoning[] = sprintf(
|
|
/* translators: 1: limiting factor, 2: final batch size */
|
|
__( 'Most restrictive: %1$s. Recommended batch (with 10%% margin): %2$d', 'robotstxt-smtp-newsletter' ),
|
|
$limiting_factor,
|
|
$optimal_batch
|
|
);
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'batch_size' => $optimal_batch,
|
|
'reasoning' => $reasoning,
|
|
'limiting_factor' => $limiting_factor,
|
|
'is_ses' => $is_ses,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Sanitize options.
|
|
*
|
|
* @param array<string, mixed> $options Raw options.
|
|
*
|
|
* @return array<string, mixed> Sanitized options.
|
|
*/
|
|
public function sanitize_options( $options ) {
|
|
$clean = array();
|
|
|
|
$clean['enabled'] = ! empty( $options['enabled'] );
|
|
|
|
// Batch size: Allow up to 10,000 for high-throughput scenarios (SES, high rate limits).
|
|
$clean['batch_size'] = max( 1, min( 10000, (int) ( $options['batch_size'] ?? 10 ) ) );
|
|
|
|
return $clean;
|
|
}
|
|
}
|