<?php declare(strict_types = 1);
namespace MailPoet\Newsletter;
if (!defined('ABSPATH')) exit;
use MailPoet\Entities\NewsletterEntity;
use MailPoet\Entities\NewsletterOptionFieldEntity;
use MailPoet\Entities\ScheduledTaskEntity;
use MailPoet\Entities\SendingQueueEntity;
use MailPoet\InvalidStateException;
use MailPoet\Newsletter\Scheduler\PostNotificationScheduler;
use MailPoet\Newsletter\Scheduler\Scheduler;
use MailPoet\Services\AuthorizedEmailsController;
use MailPoet\Util\License\Features\Subscribers as SubscribersFeature;
use MailPoetVendor\Carbon\Carbon;
/**
* Centralized newsletter status-toggle (active/draft) flow used by the REST
* endpoint at `PUT mailpoet/v1/newsletters/{id}/status`.
*
* Lifts the status change, paused-queue resume, post-notification reschedule,
* and authorized-sender gating into one place so the REST endpoint can stay a
* thin HTTP adapter.
*/
class StatusController {
/** @var NewslettersRepository */
private $newslettersRepository;
/** @var SubscribersFeature */
private $subscribersFeature;
/** @var AuthorizedEmailsController */
private $authorizedEmailsController;
/** @var NewsletterValidator */
private $newsletterValidator;
/** @var Scheduler */
private $scheduler;
/** @var PostNotificationScheduler */
private $postNotificationScheduler;
public function __construct(
NewslettersRepository $newslettersRepository,
SubscribersFeature $subscribersFeature,
AuthorizedEmailsController $authorizedEmailsController,
NewsletterValidator $newsletterValidator,
Scheduler $scheduler,
PostNotificationScheduler $postNotificationScheduler
) {
$this->newslettersRepository = $newslettersRepository;
$this->subscribersFeature = $subscribersFeature;
$this->authorizedEmailsController = $authorizedEmailsController;
$this->newsletterValidator = $newsletterValidator;
$this->scheduler = $scheduler;
$this->postNotificationScheduler = $postNotificationScheduler;
}
/**
* @throws BulkActionException
*/
public function setStatus(NewsletterEntity $newsletter, string $status): NewsletterEntity {
if ($status === NewsletterEntity::STATUS_ACTIVE && $this->subscribersFeature->check()) {
throw new BulkActionException(
__('Subscribers limit reached.', 'mailpoet'),
'mailpoet_newsletters_subscribers_limit',
403
);
}
if ($status === NewsletterEntity::STATUS_ACTIVE && !$this->authorizedEmailsController->isSenderAddressValid($newsletter)) {
throw new BulkActionException(
__('The sender address is not an authorized sender domain.', 'mailpoet'),
'mailpoet_newsletters_unauthorized_sender',
403
);
}
if ($status === NewsletterEntity::STATUS_ACTIVE) {
$validationError = $this->newsletterValidator->validate($newsletter);
if ($validationError !== null) {
throw new BulkActionException(
$validationError,
'mailpoet_newsletters_validation_failed',
403
);
}
}
$this->newslettersRepository->prefetchOptions([$newsletter]);
$newsletter->setStatus($status);
if ($newsletter->getStatus() === NewsletterEntity::STATUS_ACTIVE) {
// Unpause any tasks that were halted by an earlier deactivation so a
// toggle-off/on cycle resumes rather than orphaning the queue.
foreach ($newsletter->getUnfinishedQueues() as $queue) {
$task = $queue->getTask();
if ($task && $task->getStatus() === ScheduledTaskEntity::STATUS_PAUSED) {
$task->setStatus(ScheduledTaskEntity::STATUS_SCHEDULED);
}
}
}
// Past-due post notifications need their next run date computed and a
// new sending task scheduled; otherwise re-activation would silently
// miss the next interval.
if ($newsletter->getType() === NewsletterEntity::TYPE_NOTIFICATION && $status === NewsletterEntity::STATUS_ACTIVE) {
$scheduleOption = $newsletter->getOption(NewsletterOptionFieldEntity::NAME_SCHEDULE);
if ($scheduleOption === null) {
throw new BulkActionException(
__('This email has incorrect state.', 'mailpoet'),
'mailpoet_newsletters_missing_schedule',
400
);
}
$nextRunDate = $this->scheduler->getNextRunDate($scheduleOption->getValue());
foreach ($newsletter->getQueues() as $queue) {
$task = $queue->getTask();
if (
$task
&& $task->getScheduledAt() <= Carbon::now()->millisecond(0)
&& $task->getStatus() === SendingQueueEntity::STATUS_SCHEDULED
) {
$parsedDate = $nextRunDate ? Carbon::createFromFormat('Y-m-d H:i:s', $nextRunDate) : null;
if ($parsedDate === false) {
throw InvalidStateException::create()->withMessage('Invalid next run date generated');
}
$task->setScheduledAt($parsedDate);
}
}
$this->postNotificationScheduler->createPostNotificationSendingTask($newsletter);
}
$this->newslettersRepository->flush();
return $newsletter;
}
}