diff --git a/lib/Cron/Daemon.php b/lib/Cron/Daemon.php index 943ccdab14..7567abc92f 100644 --- a/lib/Cron/Daemon.php +++ b/lib/Cron/Daemon.php @@ -74,5 +74,6 @@ class Daemon { yield $this->workersFactory->createStatsNotificationsWorkerForAutomatedEmails(); yield $this->workersFactory->createSubscriberLinkTokensWorker(); yield $this->workersFactory->createSubscribersEngagementScoreWorker(); + yield $this->workersFactory->createSubscribersCountCacheRecalculationWorker(); } } diff --git a/lib/Cron/Triggers/WordPress.php b/lib/Cron/Triggers/WordPress.php index 1963c20c81..e58d3dd5e5 100644 --- a/lib/Cron/Triggers/WordPress.php +++ b/lib/Cron/Triggers/WordPress.php @@ -15,6 +15,7 @@ use MailPoet\Cron\Workers\SendingQueue\SendingQueue as SendingQueueWorker; use MailPoet\Cron\Workers\StatsNotifications\AutomatedEmails; use MailPoet\Cron\Workers\StatsNotifications\Worker as StatsNotificationsWorker; use MailPoet\Cron\Workers\SubscriberLinkTokens; +use MailPoet\Cron\Workers\SubscribersCountCacheRecalculation; use MailPoet\Cron\Workers\SubscribersEngagementScore; use MailPoet\Cron\Workers\UnsubscribeTokens; use MailPoet\Cron\Workers\WooCommercePastOrders; @@ -213,6 +214,13 @@ class WordPress { 'status' => ['null', ScheduledTask::STATUS_SCHEDULED], ]); + // subscriber engagement score + $subscribersCountCacheRecalculationTasks = $this->getTasksCount([ + 'type' => SubscribersCountCacheRecalculation::TASK_TYPE, + 'scheduled_in' => [self::SCHEDULED_IN_THE_PAST], + 'status' => ['null', ScheduledTask::STATUS_SCHEDULED], + ]); + // check requirements for each worker $sendingQueueActive = (($scheduledQueues || $runningQueues) && !$sendingLimitReached && !$sendingIsPaused); $bounceSyncActive = ($mpSendingEnabled && ($bounceDueTasks || !$bounceFutureTasks)); @@ -237,6 +245,7 @@ class WordPress { || $unsubscribeTokensTasks || $subscriberLinkTokensTasks || $subscriberEngagementScoreTasks + || $subscribersCountCacheRecalculationTasks ); } diff --git a/lib/Cron/Workers/SubscribersCountCacheRecalculation.php b/lib/Cron/Workers/SubscribersCountCacheRecalculation.php new file mode 100644 index 0000000000..9bf6b4ac26 --- /dev/null +++ b/lib/Cron/Workers/SubscribersCountCacheRecalculation.php @@ -0,0 +1,75 @@ +transientCache = $transientCache; + $this->segmentsRepository = $segmentsRepository; + $this->segmentSubscribersRepository = $segmentSubscribersRepository; + } + + public function processTaskStrategy(ScheduledTask $task, $timer) { + $segments = $this->segmentsRepository->findAll(); + foreach ($segments as $segment) { + $this->recalculateSegmentCache($timer, (int)$segment->getId(), $segment); + } + + // update cache for subscribers without segment + $this->recalculateSegmentCache($timer, 0); + + return true; + } + + private function recalculateSegmentCache($timer, int $segmentId, ?SegmentEntity $segment = null): void { + $this->cronHelper->enforceExecutionLimit($timer); + $now = Carbon::now(); + $item = $this->transientCache->getItem(TransientCache::SUBSCRIBERS_STATISTICS_COUNT_KEY, $segmentId); + if ($item === null || $now->diffInMinutes($item['created_at']) > self::EXPIRATION_IN_MINUTES) { + $this->transientCache->invalidateItem(TransientCache::SUBSCRIBERS_STATISTICS_COUNT_KEY, $segmentId); + if ($segment) { + $this->segmentSubscribersRepository->getSubscribersStatisticsCount($segment); + } else { + $this->segmentSubscribersRepository->getSubscribersWithoutSegmentStatisticsCount(); + } + } + } + + public function getNextRunDate() { + return Carbon::createFromTimestamp($this->wp->currentTime('timestamp')); + } + + public function shouldBeScheduled(): bool { + $now = Carbon::now(); + $oldestCreatedAt = $this->transientCache->getOldestCreatedAt(TransientCache::SUBSCRIBERS_STATISTICS_COUNT_KEY); + return $oldestCreatedAt === null || $now->diffInMinutes($oldestCreatedAt) > self::EXPIRATION_IN_MINUTES; + } +} diff --git a/lib/Cron/Workers/WorkersFactory.php b/lib/Cron/Workers/WorkersFactory.php index d880c4e1f9..a63d4448aa 100644 --- a/lib/Cron/Workers/WorkersFactory.php +++ b/lib/Cron/Workers/WorkersFactory.php @@ -105,4 +105,9 @@ class WorkersFactory { public function createWooCommercePastOrdersWorker() { return $this->container->get(WooCommercePastOrders::class); } + + /** @return SubscribersCountCacheRecalculation */ + public function createSubscribersCountCacheRecalculationWorker() { + return $this->container->get(SubscribersCountCacheRecalculation::class); + } } diff --git a/lib/DI/ContainerConfigurator.php b/lib/DI/ContainerConfigurator.php index d6da03a86d..cbc8a8cb05 100644 --- a/lib/DI/ContainerConfigurator.php +++ b/lib/DI/ContainerConfigurator.php @@ -169,6 +169,7 @@ class ContainerConfigurator implements IContainerConfigurator { $container->autowire(\MailPoet\Cron\Workers\AuthorizedSendingEmailsCheck::class)->setPublic(true); $container->autowire(\MailPoet\Cron\Workers\WooCommercePastOrders::class)->setPublic(true); $container->autowire(\MailPoet\Cron\Workers\SubscribersEngagementScore::class)->setPublic(true); + $container->autowire(\MailPoet\Cron\Workers\SubscribersCountCacheRecalculation::class)->setPublic(true); // Custom field $container->autowire(\MailPoet\CustomFields\ApiDataSanitizer::class); $container->autowire(\MailPoet\CustomFields\CustomFieldsRepository::class)->setPublic(true);