v2.2.0
This commit is contained in:
parent
0a42e00299
commit
0ece5f3a6d
16 changed files with 2342 additions and 785 deletions
172
admin/class-emails-list-page.php
Normal file
172
admin/class-emails-list-page.php
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
/**
|
||||
* Email Queue Admin Page
|
||||
*
|
||||
* Displays queued and sent emails with status information.
|
||||
*
|
||||
* @package ROBOTSTXT_SMTP_Newsletter
|
||||
* @since 2.2.0
|
||||
*/
|
||||
|
||||
namespace Robotstxt_SMTP_Newsletter\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emails List Page class.
|
||||
*
|
||||
* Provides admin UI for viewing email queue.
|
||||
*/
|
||||
class Emails_List_Page {
|
||||
|
||||
/**
|
||||
* Register admin page hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
add_submenu_page(
|
||||
'newsletter_main_main',
|
||||
__( 'Email Queue', 'robotstxt-smtp-newsletter' ),
|
||||
__( 'Email Queue', 'robotstxt-smtp-newsletter' ),
|
||||
'manage_options',
|
||||
'robotstxt-smtp-newsletter-emails',
|
||||
array( __CLASS__, 'render_page' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions', 'robotstxt-smtp-newsletter' ) );
|
||||
}
|
||||
|
||||
$queue = \Robotstxt_SMTP_Newsletter\Queue::get_instance();
|
||||
$page = isset( $_GET['paged'] ) ? max( 1, intval( $_GET['paged'] ) ) : 1;
|
||||
$per_page = 50;
|
||||
$offset = ( $page - 1 ) * $per_page;
|
||||
|
||||
$emails = $queue->get_recent_emails( $per_page, $offset );
|
||||
$total_emails = $queue->count_total_emails();
|
||||
$total_pages = ceil( $total_emails / $per_page );
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Email Queue', 'robotstxt-smtp-newsletter' ); ?></h1>
|
||||
|
||||
<p class="description">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: total emails in queue */
|
||||
esc_html__( 'Total emails in database: %d', 'robotstxt-smtp-newsletter' ),
|
||||
number_format_i18n( $total_emails )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:50px;"><?php esc_html_e( 'ID', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'To', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Subject', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Campaign', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Status', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Created', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Sent', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ( empty( $emails ) ) : ?>
|
||||
<tr>
|
||||
<td colspan="7" style="text-align:center; padding:40px;">
|
||||
<p><?php esc_html_e( 'No emails in queue', 'robotstxt-smtp-newsletter' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ( $emails as $email ) : ?>
|
||||
<tr>
|
||||
<td><?php echo absint( $email->id ); ?></td>
|
||||
<td><?php echo esc_html( $email->to_email ); ?></td>
|
||||
<td>
|
||||
<strong><?php echo esc_html( $email->subject ); ?></strong>
|
||||
<?php if ( ! empty( $email->last_error ) ) : ?>
|
||||
<br><small style="color:#d9534f;">
|
||||
<?php echo esc_html( $email->last_error ); ?>
|
||||
</small>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html( $email->campaign_name ?? '-' ); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
if ( $email->sent_at ) {
|
||||
echo '<span style="color:#5cb85c;">✓ ' . esc_html__( 'Sent', 'robotstxt-smtp-newsletter' ) . '</span>';
|
||||
} elseif ( $email->locked_at ) {
|
||||
echo '<span style="color:#f0ad4e;">⏳ ' . esc_html__( 'Processing', 'robotstxt-smtp-newsletter' ) . '</span>';
|
||||
} elseif ( $email->attempts >= 3 ) {
|
||||
echo '<span style="color:#d9534f;">✗ ' . esc_html__( 'Failed', 'robotstxt-smtp-newsletter' ) . '</span>';
|
||||
} else {
|
||||
echo '<span style="color:#999;">⏸ ' . esc_html__( 'Pending', 'robotstxt-smtp-newsletter' ) . '</span>';
|
||||
if ( $email->attempts > 0 ) {
|
||||
echo sprintf(
|
||||
' <small>(%s)</small>',
|
||||
sprintf(
|
||||
/* translators: %d: number of retry attempts */
|
||||
esc_html__( 'Retry %d', 'robotstxt-smtp-newsletter' ),
|
||||
absint( $email->attempts )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$created_time = mysql2date( 'Y-m-d H:i:s', $email->created_at );
|
||||
echo esc_html( $created_time );
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
if ( $email->sent_at ) {
|
||||
$sent_time = mysql2date( 'Y-m-d H:i:s', $email->sent_at );
|
||||
echo esc_html( $sent_time );
|
||||
} else {
|
||||
echo '-';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ( $total_pages > 1 ) : ?>
|
||||
<div class="tablenav bottom">
|
||||
<div class="tablenav-pages">
|
||||
<?php
|
||||
echo paginate_links(
|
||||
array(
|
||||
'base' => add_query_arg( 'paged', '%#%' ),
|
||||
'format' => '',
|
||||
'prev_text' => __( '«' ),
|
||||
'next_text' => __( '»' ),
|
||||
'total' => $total_pages,
|
||||
'current' => $page,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
|
@ -101,12 +101,28 @@ class Settings_Page {
|
|||
'robotstxt_smtp_newsletter_main'
|
||||
);
|
||||
|
||||
add_settings_field(
|
||||
'max_per_connection',
|
||||
__( 'Max Emails Per Connection', 'robotstxt-smtp-newsletter' ),
|
||||
array( $this, 'render_max_per_connection_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'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -206,10 +222,9 @@ class Settings_Page {
|
|||
* @return void
|
||||
*/
|
||||
private function render_recommendations() {
|
||||
$optimal = $this->calculate_optimal_settings();
|
||||
$optimal = $this->calculate_optimal_settings();
|
||||
$current_options = \Robotstxt_SMTP_Newsletter\Plugin::get_instance()->get_settings();
|
||||
$current_batch = absint( $current_options['batch_size'] ?? 10 );
|
||||
$current_max_conn = absint( $current_options['max_per_connection'] ?? 50 );
|
||||
$current_batch = absint( $current_options['batch_size'] ?? 10 );
|
||||
|
||||
// Determine batch size status.
|
||||
$batch_status = 'optimal';
|
||||
|
|
@ -221,53 +236,26 @@ class Settings_Page {
|
|||
$batch_status = 'suboptimal';
|
||||
}
|
||||
|
||||
// Determine max_per_connection status (only for SMTP).
|
||||
$max_conn_status = 'optimal';
|
||||
if ( ! $optimal['is_ses'] && $optimal['max_per_connection'] > 0 ) {
|
||||
if ( $current_max_conn < $optimal['max_per_connection'] * 0.5 ) {
|
||||
$max_conn_status = 'too_low';
|
||||
} elseif ( $current_max_conn > $optimal['max_per_connection'] * 1.5 ) {
|
||||
$max_conn_status = 'too_high';
|
||||
} elseif ( abs( $current_max_conn - $optimal['max_per_connection'] ) > 20 ) {
|
||||
$max_conn_status = 'suboptimal';
|
||||
}
|
||||
}
|
||||
|
||||
// Overall status.
|
||||
$overall_status = 'optimal';
|
||||
if ( 'too_low' === $batch_status || 'too_high' === $batch_status || 'too_low' === $max_conn_status || 'too_high' === $max_conn_status ) {
|
||||
$overall_status = 'warning';
|
||||
} elseif ( 'suboptimal' === $batch_status || 'suboptimal' === $max_conn_status ) {
|
||||
$overall_status = 'info';
|
||||
}
|
||||
|
||||
// Status styling.
|
||||
$notice_class = 'notice-info';
|
||||
$status_icon = 'ⓘ'; // Info icon.
|
||||
$status_icon = 'ⓘ'; // Info icon.
|
||||
|
||||
if ( 'optimal' === $overall_status ) {
|
||||
if ( 'optimal' === $batch_status ) {
|
||||
$notice_class = 'notice-success';
|
||||
$status_icon = '✔'; // Checkmark.
|
||||
} elseif ( 'warning' === $overall_status ) {
|
||||
$status_icon = '✔'; // Checkmark.
|
||||
} elseif ( in_array( $batch_status, array( 'too_low', 'too_high' ), true ) ) {
|
||||
$notice_class = 'notice-warning';
|
||||
$status_icon = '⚠'; // Warning icon.
|
||||
$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 cron interval.
|
||||
echo '<li>' . sprintf(
|
||||
/* translators: %d: seconds */
|
||||
esc_html__( 'Newsletter cron runs every: %d seconds', 'robotstxt-smtp-newsletter' ),
|
||||
$optimal['cron_interval']
|
||||
) . '</li>';
|
||||
|
||||
// Show delivery method.
|
||||
$delivery_method = $optimal['is_ses']
|
||||
? __( 'Amazon SES API (REST)', 'robotstxt-smtp-newsletter' )
|
||||
: __( 'SMTP with SMTPKeepAlive', 'robotstxt-smtp-newsletter' );
|
||||
? __( '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' ),
|
||||
|
|
@ -283,10 +271,9 @@ class Settings_Page {
|
|||
|
||||
echo '</ul>';
|
||||
|
||||
// Show recommendations.
|
||||
// Show recommendation.
|
||||
echo '<p style="margin-top: 10px;"><strong>';
|
||||
|
||||
// Batch size recommendation.
|
||||
if ( 'optimal' === $batch_status ) {
|
||||
echo '✔ ' . sprintf(
|
||||
/* translators: %d: batch size */
|
||||
|
|
@ -303,7 +290,7 @@ class Settings_Page {
|
|||
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 send faster.', 'robotstxt-smtp-newsletter' ),
|
||||
esc_html__( 'Current (%d) is significantly lower than optimal. Increase it to enqueue faster.', 'robotstxt-smtp-newsletter' ),
|
||||
$current_batch
|
||||
);
|
||||
} elseif ( 'too_high' === $batch_status ) {
|
||||
|
|
@ -322,47 +309,7 @@ class Settings_Page {
|
|||
}
|
||||
}
|
||||
|
||||
// Max per connection recommendation (only for SMTP).
|
||||
if ( ! $optimal['is_ses'] && $optimal['max_per_connection'] > 0 ) {
|
||||
echo '<br>';
|
||||
if ( 'optimal' === $max_conn_status ) {
|
||||
echo '✔ ' . sprintf(
|
||||
/* translators: %d: max per connection */
|
||||
esc_html__( 'Max per connection: Your current setting (%d) is optimal!', 'robotstxt-smtp-newsletter' ),
|
||||
$current_max_conn
|
||||
);
|
||||
} else {
|
||||
echo '► ' . sprintf(
|
||||
/* translators: %d: recommended max per connection */
|
||||
esc_html__( 'Recommended max per connection: %d emails', 'robotstxt-smtp-newsletter' ),
|
||||
$optimal['max_per_connection']
|
||||
);
|
||||
|
||||
if ( 'too_low' === $max_conn_status ) {
|
||||
echo '<br> ' . sprintf(
|
||||
/* translators: %d: current max per connection */
|
||||
esc_html__( 'Current (%d) is too low. Increase to reduce connection overhead.', 'robotstxt-smtp-newsletter' ),
|
||||
$current_max_conn
|
||||
);
|
||||
} elseif ( 'too_high' === $max_conn_status ) {
|
||||
echo '<br> ' . sprintf(
|
||||
/* translators: %d: current max per connection */
|
||||
esc_html__( 'Current (%d) is too high and may cause SMTP timeouts. Reduce it.', 'robotstxt-smtp-newsletter' ),
|
||||
$current_max_conn
|
||||
);
|
||||
} elseif ( 'suboptimal' === $max_conn_status ) {
|
||||
echo '<br> ' . sprintf(
|
||||
/* translators: 1: current max per connection, 2: recommended max per connection */
|
||||
esc_html__( 'Current (%1$d) could be optimized. Consider changing to %2$d.', 'robotstxt-smtp-newsletter' ),
|
||||
$current_max_conn,
|
||||
$optimal['max_per_connection']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo '</strong></p>';
|
||||
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
|
|
@ -403,45 +350,229 @@ class Settings_Page {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render max per connection field.
|
||||
* Render recent campaigns section.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_max_per_connection_field() {
|
||||
$options = get_option( self::OPTION_NAME, array() );
|
||||
$max_per_connection = $options['max_per_connection'] ?? 50;
|
||||
public function render_recent_campaigns_section() {
|
||||
$stats = \Robotstxt_SMTP_Newsletter\Statistics::get_instance();
|
||||
|
||||
// Hide for Amazon SES.
|
||||
if ( class_exists( '\Robotstxt_SMTP\Plugin' ) && \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active() ) {
|
||||
?>
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Not applicable for Amazon SES (uses API instead of SMTP connections).', 'robotstxt-smtp-newsletter' ); ?>
|
||||
</p>
|
||||
<input type="hidden" name="<?php echo esc_attr( self::OPTION_NAME ); ?>[max_per_connection]" value="<?php echo esc_attr( $max_per_connection ); ?>">
|
||||
<?php
|
||||
return;
|
||||
}
|
||||
// Sync statistics from queue.
|
||||
$stats->sync_from_queue();
|
||||
|
||||
// Get recent campaigns (last 5).
|
||||
$campaigns = $stats->get_recent_campaigns( 5 );
|
||||
?>
|
||||
<input type="number" name="<?php echo esc_attr( self::OPTION_NAME ); ?>[max_per_connection]" value="<?php echo esc_attr( $max_per_connection ); ?>" min="1" max="1000" class="small-text">
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'Maximum emails to send before reconnecting to SMTP server. Prevents timeouts.', 'robotstxt-smtp-newsletter' ); ?><br>
|
||||
<?php esc_html_e( 'See calculated recommendations below based on your rate limits.', 'robotstxt-smtp-newsletter' ); ?>
|
||||
<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
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate optimal batch size and max per connection based on rate limits and cron interval.
|
||||
* Render worker configuration section.
|
||||
*
|
||||
* @return array<string, mixed> Array with 'batch_size', 'max_per_connection', 'reasoning', and 'status'.
|
||||
* @return void
|
||||
*/
|
||||
public function render_worker_section() {
|
||||
$worker_path = ROBOTSTXT_SMTP_NEWSLETTER_PATH . 'worker.php';
|
||||
?>
|
||||
<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 now queued to the database and require a worker process to send them.', 'robotstxt-smtp-newsletter' ); ?></p>
|
||||
|
||||
<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 ); ?> 10 "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 ); ?> 10 "w{}"
|
||||
</code>
|
||||
|
||||
<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 Newsletter cron interval (default 300 seconds).
|
||||
$cron_interval = defined( 'NEWSLETTER_CRON_INTERVAL' ) ? NEWSLETTER_CRON_INTERVAL : 300;
|
||||
|
||||
// Get rate limits.
|
||||
$rate_per_second = absint( $settings['rate_limit_per_second'] ?? 0 );
|
||||
$rate_per_hour = absint( $settings['rate_limit_per_hour'] ?? 0 );
|
||||
|
|
@ -450,72 +581,63 @@ class Settings_Page {
|
|||
// Check if Amazon SES is active.
|
||||
$is_ses = \Robotstxt_SMTP\Plugin::is_amazon_ses_integration_active();
|
||||
|
||||
$optimal_batch = 10; // Default fallback.
|
||||
$optimal_max_conn = 50; // Default fallback.
|
||||
$reasoning = array();
|
||||
$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;
|
||||
$optimal_max_conn = $is_ses ? 0 : 100; // SES doesn't need max per connection.
|
||||
$reasoning[] = __( 'No rate limits configured.', 'robotstxt-smtp-newsletter' );
|
||||
$reasoning[] = $is_ses
|
||||
$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, 100 max per connection).', 'robotstxt-smtp-newsletter' );
|
||||
: __( 'Using default for SMTP (20 emails/batch).', 'robotstxt-smtp-newsletter' );
|
||||
$limiting_factor = 'default';
|
||||
} else {
|
||||
// Calculate based on rate limits.
|
||||
// Calculate based on rate limits - use conservative values for queuing.
|
||||
$candidates = array();
|
||||
|
||||
// Per-second limit: Calculate how many emails we can send in one cron cycle.
|
||||
if ( $rate_per_second > 0 ) {
|
||||
// Reserve time for connection overhead (10s for SMTP, 2s for SES).
|
||||
$overhead = $is_ses ? 2 : 10;
|
||||
$available_time = max( 1, $cron_interval - $overhead );
|
||||
$max_emails = floor( $rate_per_second * $available_time );
|
||||
// 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: cron interval, 3: overhead, 4: calculated max */
|
||||
__( 'Per-second limit: %1$d emails/sec × (%2$d sec - %3$d sec overhead) = %4$d emails/batch', 'robotstxt-smtp-newsletter' ),
|
||||
$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,
|
||||
$cron_interval,
|
||||
$overhead,
|
||||
$max_emails
|
||||
);
|
||||
}
|
||||
|
||||
// Per-hour limit: Divide by number of cron executions per hour.
|
||||
if ( $rate_per_hour > 0 ) {
|
||||
$batches_per_hour = floor( 3600 / $cron_interval );
|
||||
$max_emails = floor( $rate_per_hour / $batches_per_hour );
|
||||
// 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: batches per hour, 3: calculated max */
|
||||
__( 'Per-hour limit: %1$d emails/hour ÷ %2$d batches/hour = %3$d emails/batch', 'robotstxt-smtp-newsletter' ),
|
||||
$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,
|
||||
$batches_per_hour,
|
||||
$max_emails
|
||||
);
|
||||
}
|
||||
|
||||
// Per-day limit: Divide by number of cron executions per day.
|
||||
if ( $rate_per_day > 0 ) {
|
||||
$batches_per_day = floor( 86400 / $cron_interval );
|
||||
$max_emails = floor( $rate_per_day / $batches_per_day );
|
||||
// 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: batches per day, 3: calculated max */
|
||||
__( 'Per-day limit: %1$d emails/day ÷ %2$d batches/day = %3$d emails/batch', 'robotstxt-smtp-newsletter' ),
|
||||
$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,
|
||||
$batches_per_day,
|
||||
$max_emails
|
||||
);
|
||||
}
|
||||
|
||||
// The optimal batch size is the minimum of all candidates.
|
||||
if ( ! empty( $candidates ) ) {
|
||||
$optimal_batch = min( $candidates );
|
||||
$optimal_batch = min( $candidates );
|
||||
$limiting_factor = array_search( $optimal_batch, $candidates, true );
|
||||
|
||||
// Apply safety margin (90%).
|
||||
|
|
@ -523,36 +645,18 @@ class Settings_Page {
|
|||
|
||||
$reasoning[] = sprintf(
|
||||
/* translators: 1: limiting factor, 2: final batch size */
|
||||
__( 'Most restrictive limit: %1$s. Recommended batch size (with 10%% safety margin): %2$d', 'robotstxt-smtp-newsletter' ),
|
||||
__( 'Most restrictive: %1$s. Recommended batch (with 10%% margin): %2$d', 'robotstxt-smtp-newsletter' ),
|
||||
$limiting_factor,
|
||||
$optimal_batch
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate optimal max_per_connection for SMTP (not needed for SES).
|
||||
if ( ! $is_ses ) {
|
||||
// Max per connection should be 2-5x the batch size, or based on hourly limits.
|
||||
if ( $rate_per_hour > 0 ) {
|
||||
// Conservative: use 10 minutes of the hourly limit.
|
||||
$optimal_max_conn = floor( ( $rate_per_hour / 6 ) * 0.9 );
|
||||
} else {
|
||||
$optimal_max_conn = min( 500, max( 50, $optimal_batch * 3 ) );
|
||||
}
|
||||
$reasoning[] = sprintf(
|
||||
/* translators: %d: max per connection */
|
||||
__( 'Max per connection (SMTP): %d emails before reconnecting to prevent timeouts.', 'robotstxt-smtp-newsletter' ),
|
||||
$optimal_max_conn
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'batch_size' => $optimal_batch,
|
||||
'max_per_connection' => $optimal_max_conn,
|
||||
'reasoning' => $reasoning,
|
||||
'limiting_factor' => $limiting_factor,
|
||||
'cron_interval' => $cron_interval,
|
||||
'is_ses' => $is_ses,
|
||||
'batch_size' => $optimal_batch,
|
||||
'reasoning' => $reasoning,
|
||||
'limiting_factor' => $limiting_factor,
|
||||
'is_ses' => $is_ses,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -571,9 +675,6 @@ class Settings_Page {
|
|||
// 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 ) ) );
|
||||
|
||||
// Max per connection: Allow up to 1,000 for providers that support it.
|
||||
$clean['max_per_connection'] = max( 1, min( 1000, (int) ( $options['max_per_connection'] ?? 50 ) ) );
|
||||
|
||||
return $clean;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
255
admin/class-statistics-page.php
Normal file
255
admin/class-statistics-page.php
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
/**
|
||||
* Statistics Admin Page
|
||||
*
|
||||
* Displays campaign performance metrics and historical data.
|
||||
*
|
||||
* @package ROBOTSTXT_SMTP_Newsletter
|
||||
* @since 2.2.0
|
||||
*/
|
||||
|
||||
namespace Robotstxt_SMTP_Newsletter\Admin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics Page class.
|
||||
*
|
||||
* Provides admin UI for viewing campaign statistics and performance metrics.
|
||||
*/
|
||||
class Statistics_Page {
|
||||
|
||||
/**
|
||||
* Register admin page hooks.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register() {
|
||||
add_submenu_page(
|
||||
'newsletter_main_main',
|
||||
__( 'Email Statistics', 'robotstxt-smtp-newsletter' ),
|
||||
__( 'Statistics', 'robotstxt-smtp-newsletter' ),
|
||||
'manage_options',
|
||||
'robotstxt-smtp-newsletter-stats',
|
||||
array( __CLASS__, 'render_page' )
|
||||
);
|
||||
|
||||
add_action( 'admin_post_delete_campaign_history', array( __CLASS__, 'handle_delete_campaign' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render admin page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function render_page() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions', 'robotstxt-smtp-newsletter' ) );
|
||||
}
|
||||
|
||||
$stats_instance = \Robotstxt_SMTP_Newsletter\Statistics::get_instance();
|
||||
|
||||
// Sync from queue first to get latest data.
|
||||
$stats_instance->sync_from_queue();
|
||||
|
||||
$campaigns = $stats_instance->get_recent_campaigns( 20 );
|
||||
$global = $stats_instance->get_global_stats( 30 );
|
||||
|
||||
// Handle success message.
|
||||
if ( isset( $_GET['deleted'] ) ) {
|
||||
?>
|
||||
<div class="notice notice-success is-dismissible">
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: number of records deleted */
|
||||
esc_html__( '%d queue records deleted.', 'robotstxt-smtp-newsletter' ),
|
||||
absint( $_GET['deleted'] )
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e( 'Email Statistics', 'robotstxt-smtp-newsletter' ); ?></h1>
|
||||
|
||||
<!-- Global Stats -->
|
||||
<h2><?php esc_html_e( 'Global Performance (Last 30 Days)', 'robotstxt-smtp-newsletter' ); ?></h2>
|
||||
<table class="widefat" style="max-width:600px; margin-bottom:30px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong><?php esc_html_e( 'Total Emails Sent', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
||||
<td style="text-align:right;">
|
||||
<?php echo number_format_i18n( $global['total_sent'] ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php esc_html_e( 'Total Failed', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
||||
<td style="text-align:right; color:#d9534f;">
|
||||
<?php echo number_format_i18n( $global['total_failed'] ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php esc_html_e( 'Average Speed', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
||||
<td style="text-align:right;">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %s: emails per hour */
|
||||
esc_html__( '%s emails/hour', 'robotstxt-smtp-newsletter' ),
|
||||
number_format_i18n( $global['avg_emails_per_hour'], 0 )
|
||||
);
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php esc_html_e( 'Success Rate', 'robotstxt-smtp-newsletter' ); ?></strong></td>
|
||||
<td style="text-align:right;">
|
||||
<?php
|
||||
$total = $global['total_sent'] + $global['total_failed'];
|
||||
$rate = $total > 0 ? ( $global['total_sent'] / $total ) * 100 : 0;
|
||||
echo number_format_i18n( $rate, 1 ) . '%';
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Campaign Stats -->
|
||||
<h2><?php esc_html_e( 'Recent Campaigns', 'robotstxt-smtp-newsletter' ); ?></h2>
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e( 'Campaign', '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><?php esc_html_e( 'Started', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
<th><?php esc_html_e( 'Finished', '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>
|
||||
<th><?php esc_html_e( 'Actions', 'robotstxt-smtp-newsletter' ); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if ( empty( $campaigns ) ) : ?>
|
||||
<tr>
|
||||
<td colspan="9" style="text-align:center; padding:40px;">
|
||||
<p><?php esc_html_e( 'No campaigns found', 'robotstxt-smtp-newsletter' ); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ( $campaigns as $campaign ) : ?>
|
||||
<tr>
|
||||
<td><strong><?php echo esc_html( $campaign->campaign_name ); ?></strong></td>
|
||||
<td style="text-align:right;">
|
||||
<?php echo number_format_i18n( $campaign->total_emails ); ?>
|
||||
</td>
|
||||
<td style="text-align:right; color:#5cb85c;">
|
||||
<?php echo number_format_i18n( $campaign->sent_emails ); ?>
|
||||
</td>
|
||||
<td style="text-align:right; color:#d9534f;">
|
||||
<?php echo number_format_i18n( $campaign->failed_emails ); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
if ( $campaign->started_at ) {
|
||||
$started_time = mysql2date( 'Y-m-d H:i:s', $campaign->started_at );
|
||||
echo esc_html( $started_time );
|
||||
} else {
|
||||
echo '-';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
if ( $campaign->finished_at ) {
|
||||
$finished_time = mysql2date( 'Y-m-d H:i:s', $campaign->finished_at );
|
||||
echo esc_html( $finished_time );
|
||||
} else {
|
||||
echo '<em>' . esc_html__( 'In progress', 'robotstxt-smtp-newsletter' ) . '</em>';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td style="text-align:right;">
|
||||
<?php
|
||||
if ( $campaign->duration_seconds ) {
|
||||
$minutes = floor( $campaign->duration_seconds / 60 );
|
||||
$seconds = $campaign->duration_seconds % 60;
|
||||
echo sprintf( '%dm %ds', absint( $minutes ), absint( $seconds ) );
|
||||
} else {
|
||||
echo '-';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td style="text-align:right;">
|
||||
<?php
|
||||
if ( $campaign->emails_per_hour ) {
|
||||
echo number_format_i18n( $campaign->emails_per_hour, 0 ) . '/h<br>';
|
||||
echo '<small>' . number_format_i18n( $campaign->emails_per_minute, 1 ) . '/min</small>';
|
||||
} else {
|
||||
echo '-';
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ( $campaign->newsletter_id ) : ?>
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline;">
|
||||
<?php wp_nonce_field( 'delete_campaign_history', 'delete_campaign_nonce' ); ?>
|
||||
<input type="hidden" name="action" value="delete_campaign_history">
|
||||
<input type="hidden" name="newsletter_id" value="<?php echo absint( $campaign->newsletter_id ); ?>">
|
||||
<button type="submit" class="button button-small" onclick="return confirm('<?php echo esc_attr__( 'Delete all queue records for this campaign? Statistics will be preserved.', 'robotstxt-smtp-newsletter' ); ?>');">
|
||||
<?php esc_html_e( 'Delete Queue', 'robotstxt-smtp-newsletter' ); ?>
|
||||
</button>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
-
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete campaign queue action.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function handle_delete_campaign() {
|
||||
check_admin_referer( 'delete_campaign_history', 'delete_campaign_nonce' );
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( esc_html__( 'Insufficient permissions', 'robotstxt-smtp-newsletter' ) );
|
||||
}
|
||||
|
||||
$newsletter_id = isset( $_POST['newsletter_id'] ) ? absint( $_POST['newsletter_id'] ) : 0;
|
||||
|
||||
if ( $newsletter_id > 0 ) {
|
||||
$queue = \Robotstxt_SMTP_Newsletter\Queue::get_instance();
|
||||
$deleted = $queue->delete_campaign_history( $newsletter_id );
|
||||
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
array(
|
||||
'page' => 'robotstxt-smtp-newsletter-stats',
|
||||
'deleted' => $deleted,
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
wp_safe_redirect( admin_url( 'admin.php?page=robotstxt-smtp-newsletter-stats' ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue