<?php namespace MailPoet\Newsletter\Scheduler; if (!defined('ABSPATH')) exit; use MailPoet\Cron\Workers\SendingQueue\SendingQueue; use MailPoet\Entities\NewsletterEntity; use MailPoet\Entities\NewsletterOptionFieldEntity; use MailPoet\Entities\ScheduledTaskEntity; use MailPoet\Entities\ScheduledTaskSubscriberEntity; use MailPoet\Entities\SendingQueueEntity; use MailPoet\Entities\SubscriberEntity; use MailPoet\Newsletter\Sending\ScheduledTasksRepository; use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository; use MailPoet\Newsletter\Sending\SendingQueuesRepository; class AutomaticEmailScheduler { /** @var Scheduler */ private $scheduler; /** @var ScheduledTasksRepository */ private $scheduledTasksRepository; /** @var SendingQueuesRepository */ private $sendingQueuesRepository; /** @var ScheduledTaskSubscribersRepository */ private $scheduledTaskSubscribersRepository; public function __construct( Scheduler $scheduler, ScheduledTasksRepository $scheduledTasksRepository, ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository, SendingQueuesRepository $sendingQueuesRepository ) { $this->scheduler = $scheduler; $this->scheduledTasksRepository = $scheduledTasksRepository; $this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository; $this->sendingQueuesRepository = $sendingQueuesRepository; } public function scheduleAutomaticEmail( string $group, string $event, ?callable $schedulingCondition = null, ?SubscriberEntity $subscriber = null, ?array $meta = null, ?callable $metaModifier = null ) { $newsletters = $this->scheduler->getNewsletters(NewsletterEntity::TYPE_AUTOMATIC, $group); if (empty($newsletters)) return false; foreach ($newsletters as $newsletter) { if ($newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EVENT) !== $event) continue; if (is_callable($schedulingCondition) && !$schedulingCondition($newsletter)) continue; /** * $meta will be the same for all newsletters by default. If we need to store newsletter-specific meta, the * $metaModifier callback can be used. * * This was introduced because of WooCommerce product purchase automatic emails. We only want to store the * product IDs that specifically triggered a newsletter, but $meta includes ALL the product IDs * or category IDs from an order. */ if (is_callable($metaModifier)) { $meta = $metaModifier($newsletter, $meta); } $this->createAutomaticEmailScheduledTask($newsletter, $subscriber, $meta); } } public function scheduleOrRescheduleAutomaticEmail(string $group, string $event, SubscriberEntity $subscriber, array $meta): void { $newsletters = $this->scheduler->getNewsletters(NewsletterEntity::TYPE_AUTOMATIC, $group); if (empty($newsletters)) { return; } foreach ($newsletters as $newsletter) { if ($newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EVENT) !== $event) { continue; } // try to find existing scheduled task for given subscriber $task = $this->scheduledTasksRepository->findOneScheduledByNewsletterAndSubscriber($newsletter, $subscriber); if ($task) { $this->rescheduleAutomaticEmailSendingTask($newsletter, $task, $meta); } else { $this->createAutomaticEmailScheduledTask($newsletter, $subscriber, $meta); } } } public function rescheduleAutomaticEmail(string $group, string $event, SubscriberEntity $subscriber): void { $newsletters = $this->scheduler->getNewsletters(NewsletterEntity::TYPE_AUTOMATIC, $group); if (empty($newsletters)) { return; } foreach ($newsletters as $newsletter) { if ($newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EVENT) !== $event) { continue; } // try to find existing scheduled task for given subscriber $task = $this->scheduledTasksRepository->findOneScheduledByNewsletterAndSubscriber($newsletter, $subscriber); if ($task) { $this->rescheduleAutomaticEmailSendingTask($newsletter, $task); } } } public function cancelAutomaticEmail(string $group, string $event, SubscriberEntity $subscriber): void { $newsletters = $this->scheduler->getNewsletters(NewsletterEntity::TYPE_AUTOMATIC, $group); if (empty($newsletters)) { return; } foreach ($newsletters as $newsletter) { if ($newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_EVENT) !== $event) { continue; } // try to find existing scheduled task for given subscriber $task = $this->scheduledTasksRepository->findOneScheduledByNewsletterAndSubscriber($newsletter, $subscriber); if ($task) { $queue = $task->getSendingQueue(); if ($queue instanceof SendingQueueEntity) { $this->sendingQueuesRepository->remove($queue); } $this->scheduledTaskSubscribersRepository->deleteByTask($task); // In case any of task associated SchedulesTaskSubscriberEntity was loaded we need to detach them foreach ($task->getSubscribers() as $taskSubscriber) { $this->scheduledTaskSubscribersRepository->detach($taskSubscriber); } $this->scheduledTasksRepository->remove($task); $this->scheduledTasksRepository->flush(); } } } public function createAutomaticEmailScheduledTask(NewsletterEntity $newsletter, ?SubscriberEntity $subscriber, ?array $meta = null): ScheduledTaskEntity { $scheduledTask = new ScheduledTaskEntity(); $scheduledTask->setType(SendingQueue::TASK_TYPE); $scheduledTask->setStatus(SendingQueueEntity::STATUS_SCHEDULED); $scheduledTask->setPriority(ScheduledTaskEntity::PRIORITY_MEDIUM); $scheduledTask->setScheduledAt($this->scheduler->getScheduledTimeWithDelay( $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AFTER_TIME_TYPE), $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AFTER_TIME_NUMBER) )); $this->scheduledTasksRepository->persist($scheduledTask); $this->scheduledTasksRepository->flush(); $sendingQueue = new SendingQueueEntity(); $sendingQueue->setNewsletter($newsletter); $sendingQueue->setTask($scheduledTask); $scheduledTask->setSendingQueue($sendingQueue); if ($meta) { $scheduledTask->setMeta($meta); $sendingQueue->setMeta($meta); } $this->sendingQueuesRepository->persist($sendingQueue); $this->sendingQueuesRepository->flush(); if ($newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_SEND_TO) === 'user' && $subscriber) { $scheduledTaskSubscriber = new ScheduledTaskSubscriberEntity($scheduledTask, $subscriber); $this->scheduledTaskSubscribersRepository->persist($scheduledTaskSubscriber); $this->scheduledTaskSubscribersRepository->flush(); $scheduledTask->getSubscribers()->add($scheduledTaskSubscriber); } return $scheduledTask; } private function rescheduleAutomaticEmailSendingTask(NewsletterEntity $newsletter, ScheduledTaskEntity $scheduledTask, ?array $meta = null): void { $sendingQueue = $this->sendingQueuesRepository->findOneBy(['task' => $scheduledTask]); if (!$sendingQueue) { return; } if ($meta) { $sendingQueue->setMeta($meta); $scheduledTask->setMeta($meta); } // compute new 'scheduled_at' from now $scheduledTask->setScheduledAt($this->scheduler->getScheduledTimeWithDelay( $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AFTER_TIME_TYPE), $newsletter->getOptionValue(NewsletterOptionFieldEntity::NAME_AFTER_TIME_NUMBER) )); $this->sendingQueuesRepository->flush(); } }