feca-mailshots-plugin/feca_mailshots_plugin/src/Admin/RunMailshotAdminPage.php

246 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace FecaMailshots\Admin;
use FecaMailshots\Application\MailshotRunService;
use FecaMailshots\Application\MailshotService;
use FecaMailshots\WordPress\WordPressFacade;
final class RunMailshotAdminPage
{
private const RESULT_OPTION_KEY = 'feca_mailshots_run_ui_result';
private const CAPABILITY = 'edit_pages';
/** @var callable(): MailshotRunService */
private $runServiceFactory;
/** @var callable(): MailshotService */
private $mailshotServiceFactory;
private WordPressFacade $wp;
/** @param callable(): MailshotRunService $runServiceFactory @param callable(): MailshotService $mailshotServiceFactory */
public function __construct(callable $runServiceFactory, callable $mailshotServiceFactory, WordPressFacade $wp)
{
$this->runServiceFactory = $runServiceFactory;
$this->mailshotServiceFactory = $mailshotServiceFactory;
$this->wp = $wp;
}
public function register(): void
{
$this->wp->addAction('admin_menu', [$this, 'registerMenu']);
$this->wp->addAction('admin_post_feca_mailshots_run_api', [$this, 'handleApi']);
$this->wp->addAction('admin_post_feca_mailshots_run_execute_ui', [$this, 'handleRunUi']);
$this->wp->addAction('admin_post_feca_mailshots_retry_failed_ui', [$this, 'handleRetryFailedUi']);
$this->wp->addAction('admin_post_feca_mailshots_retry_recipient_ui', [$this, 'handleRetryRecipientUi']);
}
public function registerMenu(): void
{
$this->wp->addSubmenuPage('feca-mailshot', 'Run Mailshot', 'Run Mailshot', self::CAPABILITY, 'feca-mailshots-run', [$this, 'render']);
}
public function render(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
echo 'Permission denied';
return;
}
$mailshots = $this->mailshotService()->list();
$selectedMailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
if ($selectedMailshotId <= 0 && $mailshots !== []) {
$selectedMailshotId = (int) ($mailshots[0]['id'] ?? 0);
}
$result = $this->result();
$lastRunRows = $selectedMailshotId > 0 ? $this->mailshotService()->lastRun($selectedMailshotId) : [];
$action = htmlspecialchars($this->wp->adminUrl('admin-post.php'));
echo '<div class="wrap"><h1>Run Mailshot</h1>';
echo '<p>Execute full sends and retry failures from the current mailshot last-run rows.</p>';
if ($result !== null) {
$ok = !empty($result['ok']);
$bg = $ok ? '#f1f8e9' : '#ffebee';
$border = $ok ? '#8bc34a' : '#ef9a9a';
echo '<div style="padding:10px;border:1px solid ' . $border . ';background:' . $bg . ';margin:12px 0;">';
echo '<strong>' . ($ok ? 'Run action completed.' : 'Run action failed.') . '</strong>';
if ($ok) {
echo '<p>attempted=' . (int) ($result['attempted'] ?? 0)
. ', sent=' . (int) ($result['sent'] ?? 0)
. ', failed=' . (int) ($result['failed'] ?? 0)
. ', warnings=' . (int) ($result['warnings'] ?? 0)
. ', skipped=' . (int) ($result['skipped'] ?? 0)
. '</p>';
}
if (!empty($result['errors']) && is_array($result['errors'])) {
echo '<p style="margin:6px 0 0 0;">' . htmlspecialchars(implode('; ', $result['errors'])) . '</p>';
}
echo '<details style="margin-top:8px;"><summary>Show technical details</summary><pre style="margin-top:8px;">' . htmlspecialchars(json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) . '</pre></details>';
echo '</div>';
}
echo '<div style="padding:12px;border:1px solid #dcdcde;background:#fff;margin-bottom:12px;">';
echo '<h2 style="margin-top:0;">1. Select Mailshot</h2>';
echo '<form method="get" action="' . htmlspecialchars($this->wp->adminUrl('admin.php')) . '" style="margin-bottom:0;">';
echo '<input type="hidden" name="page" value="feca-mailshots-run">';
echo '<label>Mailshot: <select name="mailshot_id">';
foreach ($mailshots as $m) {
$id = (int) ($m['id'] ?? 0);
$sel = $id === $selectedMailshotId ? ' selected' : '';
$label = trim((string) ($m['Purpose'] ?? ''));
if ($label === '') {
$label = 'Mailshot #' . $id;
}
echo '<option value="' . $id . '"' . $sel . '>' . htmlspecialchars($label) . '</option>';
}
echo '</select></label> <button class="button" type="submit">Load</button>';
echo '</form>';
echo '</div>';
echo '<form method="post" action="' . $action . '" style="padding:12px;border:1px solid #dcdcde;background:#fff;margin-bottom:12px;">';
echo '<h2 style="margin-top:0;">2. Run Actions</h2>';
echo '<input type="hidden" name="mailshot_id" value="' . $selectedMailshotId . '">';
echo '<button class="button button-primary" type="submit" name="action" value="feca_mailshots_run_execute_ui" onclick="return confirm(\'Run mailshot now for the selected recipients?\');">Run Mailshot</button> ';
echo '<button class="button" type="submit" name="action" value="feca_mailshots_retry_failed_ui" onclick="return confirm(\'Retry all failed recipients for this mailshot?\');">Retry Failed Sends</button>';
echo '</form>';
echo '<div style="padding:12px;border:1px solid #dcdcde;background:#fff;">';
echo '<h2 style="margin-top:0;">3. Last Run Rows</h2>';
if ($lastRunRows === []) {
echo '<p>No rows in mailshot_last_run for this mailshot.</p>';
} else {
echo '<table class="widefat striped"><thead><tr><th>Recipient Key</th><th>Email</th><th>Status</th><th>Attempts</th><th>Error/Warning</th><th>Retry</th></tr></thead><tbody>';
foreach ($lastRunRows as $row) {
$recipientKey = (string) ($row['recipient_key'] ?? '');
$status = (string) ($row['status'] ?? '');
$msg = (string) (($row['error_message'] ?? '') ?: ($row['warning_message'] ?? ''));
echo '<tr>';
echo '<td>' . htmlspecialchars($recipientKey) . '</td>';
echo '<td>' . htmlspecialchars((string) ($row['recipient_email_last'] ?? '')) . '</td>';
echo '<td>' . htmlspecialchars($status) . '</td>';
echo '<td>' . (int) ($row['attempt_count'] ?? 0) . '</td>';
echo '<td>' . htmlspecialchars($msg) . '</td>';
echo '<td>';
if ($status === 'failed') {
echo '<form method="post" action="' . $action . '">';
echo '<input type="hidden" name="action" value="feca_mailshots_retry_recipient_ui">';
echo '<input type="hidden" name="mailshot_id" value="' . $selectedMailshotId . '">';
echo '<input type="hidden" name="recipient_key" value="' . htmlspecialchars($recipientKey, ENT_QUOTES) . '">';
echo '<button class="button" type="submit" onclick="return confirm(\'Retry this failed recipient now?\');">Retry</button>';
echo '</form>';
}
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
}
echo '</div>';
echo '</div>';
}
public function handleApi(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
$this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
return;
}
$op = (string) ($this->wp->requestParam('op', '') ?? '');
$service = $this->runService();
try {
if ($op === 'run_mailshot') {
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$this->wp->sendJson($service->runMailshot($mailshotId));
return;
}
if ($op === 'retry_failed') {
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$this->wp->sendJson($service->retryFailed($mailshotId));
return;
}
if ($op === 'retry_recipient') {
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$recipientKey = (string) ($this->wp->requestParam('recipient_key', '') ?? '');
$this->wp->sendJson($service->retryRecipient($mailshotId, $recipientKey));
return;
}
$this->wp->sendJson(['ok' => false, 'error' => 'Unknown op'], 400);
} catch (\Throwable $e) {
$this->wp->sendJson(['ok' => false, 'error' => $e->getMessage()], 500);
}
}
public function handleRunUi(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
$this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
return;
}
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$result = $this->runService()->runMailshot($mailshotId);
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
$this->redirect($mailshotId);
}
public function handleRetryFailedUi(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
$this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
return;
}
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$result = $this->runService()->retryFailed($mailshotId);
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
$this->redirect($mailshotId);
}
public function handleRetryRecipientUi(): void
{
if (!$this->wp->currentUserCan(self::CAPABILITY)) {
$this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
return;
}
$mailshotId = (int) ($this->wp->requestParam('mailshot_id', '0') ?? '0');
$recipientKey = (string) ($this->wp->requestParam('recipient_key', '') ?? '');
$result = $this->runService()->retryRecipient($mailshotId, $recipientKey);
$this->wp->updateOption(self::RESULT_OPTION_KEY, $result);
$this->redirect($mailshotId);
}
private function redirect(int $mailshotId): void
{
$url = $this->wp->adminUrl('admin.php?page=feca-mailshots-run&mailshot_id=' . $mailshotId);
if (!headers_sent()) {
header('Location: ' . $url, true, 302);
exit;
}
}
/** @return array<string,mixed>|null */
private function result(): ?array
{
$raw = $this->wp->getOption(self::RESULT_OPTION_KEY, null);
return is_array($raw) ? $raw : null;
}
private function runService(): MailshotRunService
{
return ($this->runServiceFactory)();
}
private function mailshotService(): MailshotService
{
return ($this->mailshotServiceFactory)();
}
}