<?php declare(strict_types = 1);
namespace MailPoet\Cron\Workers;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\LogEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SubscriberEntity;
use MailPoet\Logging\LoggerFactory;
use MailPoet\Logging\LogRepository;
use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
use MailPoet\Subscribers\BulkConfirmationEmailResender;
use MailPoet\Subscribers\ConfirmationEmailMailer;
use MailPoet\Subscribers\SubscribersRepository;
use MailPoet\Tasks\Subscribers\BatchIterator;
use MailPoetVendor\Carbon\Carbon;
class BulkConfirmationEmailResend extends SimpleWorker {
const TASK_TYPE = 'bulk_confirmation_email_resend';
const AUTOMATIC_SCHEDULING = false;
const SUPPORT_MULTIPLE_INSTANCES = false;
const BATCH_SIZE = 20;
private const AUDIT_LOG_MESSAGE_COMPLETED = 'Bulk confirmation email resend completed.';
private const LOG_LEVEL_INFO = 200;
/** @var ConfirmationEmailMailer */
private $confirmationEmailMailer;
/** @var SubscribersRepository */
private $subscribersRepository;
/** @var ScheduledTaskSubscribersRepository */
private $scheduledTaskSubscribersRepository;
/** @var LogRepository */
private $logRepository;
public function __construct(
ConfirmationEmailMailer $confirmationEmailMailer,
SubscribersRepository $subscribersRepository,
ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository,
LogRepository $logRepository
) {
$this->confirmationEmailMailer = $confirmationEmailMailer;
$this->subscribersRepository = $subscribersRepository;
$this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository;
$this->logRepository = $logRepository;
parent::__construct();
}
public function processTaskStrategy(ScheduledTaskEntity $task, $timer) {
$subscriberBatches = new BatchIterator($task->getId(), self::BATCH_SIZE);
$sentCount = 0;
$failedCount = 0;
$skippedByReason = [];
$oldestLifecycleDate = Carbon::now()->subDays(BulkConfirmationEmailResender::BULK_CONFIRMATION_MAX_SUBSCRIBER_AGE_DAYS)->millisecond(0);
foreach ($subscriberBatches as $subscriberIds) {
$this->cronHelper->enforceExecutionLimit($timer);
$sentIds = [];
foreach ($subscriberIds as $subscriberId) {
$subscriber = $this->subscribersRepository->findOneById((int)$subscriberId);
if (!$subscriber instanceof SubscriberEntity) {
$this->saveSkipped($task, (int)$subscriberId, 'not_found', $skippedByReason);
$failedCount++;
continue;
}
try {
$result = $this->confirmationEmailMailer->sendAdminConfirmationEmail($subscriber, $oldestLifecycleDate);
} catch (\Throwable $throwable) {
$this->scheduledTaskSubscribersRepository->saveError($task, (int)$subscriberId, 'send_failed:mailer_error');
$failedCount++;
continue;
}
if ($result['status'] === 'sent') {
$sentIds[] = (int)$subscriberId;
$sentCount++;
} elseif ($result['status'] === 'skipped') {
$this->saveSkipped($task, (int)$subscriberId, $result['reason'] ?? 'not_found', $skippedByReason);
$failedCount++;
} else {
$this->scheduledTaskSubscribersRepository->saveError($task, (int)$subscriberId, 'send_failed:' . ($result['reason'] ?? 'sending_method'));
$failedCount++;
}
}
$this->scheduledTaskSubscribersRepository->updateProcessedSubscribers($task, $sentIds);
}
$meta = $task->getMeta() ?? [];
$meta['sent_count'] = $sentCount;
$meta['failed_count'] = $failedCount;
$meta['skipped_by_reason'] = $skippedByReason;
$task->setMeta($meta);
$this->scheduledTasksRepository->persist($task);
$this->scheduledTasksRepository->flush();
$this->saveCompletionAuditLog($task, $sentCount, $failedCount, $skippedByReason);
return true;
}
/**
* @param array<string, int> $skippedByReason
*/
private function saveSkipped(ScheduledTaskEntity $task, int $subscriberId, string $reason, array &$skippedByReason): void {
$skippedByReason[$reason] = ($skippedByReason[$reason] ?? 0) + 1;
$this->scheduledTaskSubscribersRepository->saveError($task, $subscriberId, 'skipped:' . $reason);
}
/**
* @param array<string, int> $skippedByReason
*/
private function saveCompletionAuditLog(ScheduledTaskEntity $task, int $sentCount, int $failedCount, array $skippedByReason): void {
$log = new LogEntity();
$log->setName(LoggerFactory::TOPIC_CRON);
$log->setLevel(self::LOG_LEVEL_INFO);
$log->setMessage(self::AUDIT_LOG_MESSAGE_COMPLETED);
$log->setRawMessage(self::AUDIT_LOG_MESSAGE_COMPLETED);
$log->setContext([
'action' => 'bulk_confirmation_email_resend_completed',
'task_id' => $task->getId(),
'sent_count' => $sentCount,
'failed_count' => $failedCount,
'skipped_by_reason' => $skippedByReason,
'final_status' => ScheduledTaskEntity::STATUS_COMPLETED,
]);
$this->logRepository->saveLog($log);
}
}