diff --git a/mailpoet/lib/Homepage/HomepageDataController.php b/mailpoet/lib/Homepage/HomepageDataController.php index effb621946..0f4f564b2a 100644 --- a/mailpoet/lib/Homepage/HomepageDataController.php +++ b/mailpoet/lib/Homepage/HomepageDataController.php @@ -161,10 +161,24 @@ class HomepageDataController { $listData[$list['id']]['unsubscribed'] = $list['count']; } + $subscribedCount = $this->subscribersRepository->getCountOfCreatedAfterWithStatues($thirtyDaysAgo, [SubscriberEntity::STATUS_SUBSCRIBED]); + $unsubscribedCount = $this->subscribersRepository->getCountOfUnsubscribedAfter($thirtyDaysAgo); + $subscribedSubscribersCount = $this->subscribersRepository->getCountOfSubscribersForStates([SubscriberEntity::STATUS_SUBSCRIBED]); + $subscribedSubscribers30DaysAgo = $subscribedSubscribersCount - $subscribedCount + $unsubscribedCount; + if ($subscribedSubscribers30DaysAgo > 0) { + $globalChangePercent = (($subscribedSubscribersCount - $subscribedSubscribers30DaysAgo) / $subscribedSubscribers30DaysAgo) * 100; + if (floor($globalChangePercent) !== (float)$globalChangePercent) { + $globalChangePercent = round($globalChangePercent, 1); + } + } else { + $globalChangePercent = $subscribedSubscribersCount * 100; + } + return [ 'global' => [ - 'subscribed' => $this->subscribersRepository->getCountOfCreatedAfterWithStatues($thirtyDaysAgo, [SubscriberEntity::STATUS_SUBSCRIBED]), - 'unsubscribed' => $this->subscribersRepository->getCountOfUnsubscribedAfter($thirtyDaysAgo), + 'subscribed' => $subscribedCount, + 'unsubscribed' => $unsubscribedCount, + 'change' => $globalChangePercent, ], 'lists' => array_values($listData), ]; diff --git a/mailpoet/lib/Subscribers/SubscribersRepository.php b/mailpoet/lib/Subscribers/SubscribersRepository.php index 15c5deeae3..521d75c2c5 100644 --- a/mailpoet/lib/Subscribers/SubscribersRepository.php +++ b/mailpoet/lib/Subscribers/SubscribersRepository.php @@ -51,18 +51,22 @@ class SubscribersRepository extends Repository { } public function getTotalSubscribers(): int { + return $this->getCountOfSubscribersForStates([ + SubscriberEntity::STATUS_SUBSCRIBED, + SubscriberEntity::STATUS_UNCONFIRMED, + SubscriberEntity::STATUS_INACTIVE, + ]); + } + + public function getCountOfSubscribersForStates(array $states): int { $query = $this->entityManager ->createQueryBuilder() ->select('count(n.id)') ->from(SubscriberEntity::class, 'n') ->where('n.deletedAt IS NULL AND n.status IN (:statuses)') - ->setParameter('statuses', [ - SubscriberEntity::STATUS_SUBSCRIBED, - SubscriberEntity::STATUS_UNCONFIRMED, - SubscriberEntity::STATUS_INACTIVE, - ]) + ->setParameter('statuses', $states) ->getQuery(); - return (int)$query->getSingleScalarResult(); + return intval($query->getSingleScalarResult()); } public function invalidateTotalSubscribersCache(): void { @@ -390,7 +394,8 @@ class SubscribersRepository extends Repository { ->select('COUNT(s.id)') ->from(StatisticsUnsubscribeEntity::class, 'su') ->join('su.subscriber', 's') - ->where('s.createdAt > :unsubscribedAfter') + ->where('s.createdAt <= :unsubscribedAfter') + ->andWhere('su.createdAt > :unsubscribedAfter') ->andWhere('s.status = :status') ->andWhere('s.deletedAt IS NULL') ->setParameter('unsubscribedAfter', $unsubscribedAfter) diff --git a/mailpoet/tasks/phpstan/phpstan-7-baseline.neon b/mailpoet/tasks/phpstan/phpstan-7-baseline.neon index dc75c47d27..31fceb0754 100644 --- a/mailpoet/tasks/phpstan/phpstan-7-baseline.neon +++ b/mailpoet/tasks/phpstan/phpstan-7-baseline.neon @@ -815,11 +815,6 @@ parameters: count: 1 path: ../../lib/Subscribers/SubscribersRepository.php - - - message: "#^Cannot cast mixed to int\\.$#" - count: 1 - path: ../../lib/Subscribers/SubscribersRepository.php - - message: "#^Method MailPoet\\\\Subscribers\\\\SubscribersRepository\\:\\:findIdsOfDeletedByEmails\\(\\) should return array\\ but returns array\\\\>\\.$#" count: 1 diff --git a/mailpoet/tasks/phpstan/phpstan-8-baseline.neon b/mailpoet/tasks/phpstan/phpstan-8-baseline.neon index 01c71c9ec5..0bc69f590e 100644 --- a/mailpoet/tasks/phpstan/phpstan-8-baseline.neon +++ b/mailpoet/tasks/phpstan/phpstan-8-baseline.neon @@ -815,11 +815,6 @@ parameters: count: 1 path: ../../lib/Subscribers/SubscribersRepository.php - - - message: "#^Cannot cast mixed to int\\.$#" - count: 1 - path: ../../lib/Subscribers/SubscribersRepository.php - - message: "#^Method MailPoet\\\\Subscribers\\\\SubscribersRepository\\:\\:findIdsOfDeletedByEmails\\(\\) should return array\\ but returns array\\\\>\\.$#" count: 1 diff --git a/mailpoet/tasks/phpstan/phpstan-8.1-baseline.neon b/mailpoet/tasks/phpstan/phpstan-8.1-baseline.neon index b45744f916..ea46c4c6da 100644 --- a/mailpoet/tasks/phpstan/phpstan-8.1-baseline.neon +++ b/mailpoet/tasks/phpstan/phpstan-8.1-baseline.neon @@ -814,11 +814,6 @@ parameters: count: 1 path: ../../lib/Subscribers/SubscribersRepository.php - - - message: "#^Cannot cast mixed to int\\.$#" - count: 1 - path: ../../lib/Subscribers/SubscribersRepository.php - - message: "#^Method MailPoet\\\\Subscribers\\\\SubscribersRepository\\:\\:findIdsOfDeletedByEmails\\(\\) should return array\\ but returns array\\\\>\\.$#" count: 1 diff --git a/mailpoet/tests/integration/Homepage/HomepageDataControllerTest.php b/mailpoet/tests/integration/Homepage/HomepageDataControllerTest.php index db1d094c46..16b105f2fa 100644 --- a/mailpoet/tests/integration/Homepage/HomepageDataControllerTest.php +++ b/mailpoet/tests/integration/Homepage/HomepageDataControllerTest.php @@ -195,8 +195,8 @@ class HomepageDataControllerTest extends \MailPoetTest { $oldUnsubscribedStats->setCreatedAt($thirtyOneDaysAgo); $this->entityManager->persist($oldUnsubscribedStats); $this->entityManager->flush(); - // Freshly unsubscribed - $newUnsubscribed = (new Subscriber())->withCreatedAt($twentyNineDaysAgo)->withStatus(SubscriberEntity::STATUS_UNSUBSCRIBED)->create(); + // Freshly unsubscribed (but created before the period) + $newUnsubscribed = (new Subscriber())->withCreatedAt($thirtyOneDaysAgo)->withStatus(SubscriberEntity::STATUS_UNSUBSCRIBED)->create(); $newUnsubscribedStats = new StatisticsUnsubscribeEntity(null, null, $newUnsubscribed); $newUnsubscribedStats->setCreatedAt($twentyNineDaysAgo); $this->entityManager->persist($newUnsubscribedStats); @@ -207,6 +207,46 @@ class HomepageDataControllerTest extends \MailPoetTest { expect($subscribersStats['global']['unsubscribed'])->equals(1); } + public function testItFetchesCorrectGlobalSubscriberChange(): void { + $thirtyOneDaysAgo = Carbon::now()->subDays(31); + $twentyNineDaysAgo = Carbon::now()->subDays(29); + + // 10 New Subscribers + for ($i = 0; $i < 10; $i++) { + (new Subscriber())->withCreatedAt($twentyNineDaysAgo)->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)->create(); + } + $subscribersStats = $this->homepageDataController->getPageData()['subscribersStats']; + expect($subscribersStats['global']['change'])->equals(1000); + + // 10 New Subscribers + 5 Old Subscribers + for ($i = 0; $i < 5; $i++) { + (new Subscriber())->withCreatedAt($thirtyOneDaysAgo)->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)->create(); + } + $subscribersStats = $this->homepageDataController->getPageData()['subscribersStats']; + expect($subscribersStats['global']['change'])->equals(200); + + // 10 New Subscribers + 6 Old Subscribers + (new Subscriber())->withCreatedAt($thirtyOneDaysAgo)->withStatus(SubscriberEntity::STATUS_SUBSCRIBED)->create(); + $subscribersStats = $this->homepageDataController->getPageData()['subscribersStats']; + expect($subscribersStats['global']['change'])->equals( 166.7); + + // 10 New Subscribers + 6 Old Subscribers + 10 New Unsubscribed + for ($i = 0; $i < 10; $i++) { + $unsubscribed = (new Subscriber())->withCreatedAt($thirtyOneDaysAgo)->withStatus(SubscriberEntity::STATUS_UNSUBSCRIBED)->create(); + $this->entityManager->persist(new StatisticsUnsubscribeEntity(null, null, $unsubscribed)); + $this->entityManager->flush(); + } + $subscribersStats = $this->homepageDataController->getPageData()['subscribersStats']; + expect($subscribersStats['global']['change'])->equals( 0); + + // 10 New Subscribers + 6 Old Subscribers + 11 New Unsubscribed + $unsubscribed = (new Subscriber())->withCreatedAt($thirtyOneDaysAgo)->withStatus(SubscriberEntity::STATUS_UNSUBSCRIBED)->create(); + $this->entityManager->persist(new StatisticsUnsubscribeEntity(null, null, $unsubscribed)); + $this->entityManager->flush(); + $subscribersStats = $this->homepageDataController->getPageData()['subscribersStats']; + expect($subscribersStats['global']['change'])->equals( -5.9); + } + public function testItFetchesCorrectListLevelSubscribedStats(): void { $thirtyOneDaysAgo = Carbon::now()->subDays(31); $twentyNineDaysAgo = Carbon::now()->subDays(29);