diff --git a/lib/Models/StatisticsClicks.php b/lib/Models/StatisticsClicks.php index 61b4fa4c2d..8744ae49af 100644 --- a/lib/Models/StatisticsClicks.php +++ b/lib/Models/StatisticsClicks.php @@ -2,8 +2,6 @@ namespace MailPoet\Models; -use DateTimeInterface; - /** * @property int $newsletterId * @property int $subscriberId @@ -13,25 +11,4 @@ use DateTimeInterface; */ class StatisticsClicks extends Model { public static $_table = MP_STATISTICS_CLICKS_TABLE; // phpcs:ignore PSR2.Classes.PropertyDeclaration - - public static function findLatestPerNewsletterBySubscriber(Subscriber $subscriber, DateTimeInterface $from, DateTimeInterface $to) { - // subquery to find latest click IDs for each newsletter - $table = self::$_table; - $latestClickIdsPerNewsletterQuery = " - SELECT MAX(id) - FROM $table - WHERE subscriber_id = :subscriber_id - AND updated_at > :from - AND updated_at < :to - GROUP BY newsletter_id - "; - - return static::tableAlias('clicks') - ->whereRaw("clicks.id IN ($latestClickIdsPerNewsletterQuery)", [ - 'subscriber_id' => $subscriber->id, - 'from' => $from->format('Y-m-d H:i:s'), - 'to' => $to->format('Y-m-d H:i:s'), - ]) - ->findMany(); - } } diff --git a/lib/Models/StatisticsWooCommercePurchases.php b/lib/Models/StatisticsWooCommercePurchases.php index 3f8de0cd89..fce3ed217c 100644 --- a/lib/Models/StatisticsWooCommercePurchases.php +++ b/lib/Models/StatisticsWooCommercePurchases.php @@ -2,8 +2,6 @@ namespace MailPoet\Models; -use WC_Order; - /** * @property int $newsletterId * @property int $subscriberId @@ -15,27 +13,4 @@ use WC_Order; */ class StatisticsWooCommercePurchases extends Model { public static $_table = MP_STATISTICS_WOOCOMMERCE_PURCHASES_TABLE; // phpcs:ignore PSR2.Classes.PropertyDeclaration - - public static function createOrUpdateByClickDataAndOrder(StatisticsClicks $click, WC_Order $order) { - // search by subscriber and newsletter IDs (instead of click itself) to avoid duplicities - // when a new click from the subscriber appeared since last tracking for given newsletter - // (this will keep the originally tracked click - likely the click that led to the order) - $statistics = self::where('order_id', $order->get_id()) - ->where('subscriber_id', $click->subscriberId) - ->where('newsletter_id', $click->newsletterId) - ->findOne(); - - if (!$statistics instanceof self) { - $statistics = self::create(); - $statistics->newsletterId = $click->newsletterId; - $statistics->subscriberId = $click->subscriberId; - $statistics->queueId = $click->queueId; - $statistics->clickId = (int)$click->id; - $statistics->orderId = $order->get_id(); - } - - $statistics->orderCurrency = $order->get_currency(); - $statistics->orderPriceTotal = (float)$order->get_total(); - return $statistics->save(); - } } diff --git a/lib/Statistics/StatisticsWooCommercePurchasesRepository.php b/lib/Statistics/StatisticsWooCommercePurchasesRepository.php index d604de02e6..082d938440 100644 --- a/lib/Statistics/StatisticsWooCommercePurchasesRepository.php +++ b/lib/Statistics/StatisticsWooCommercePurchasesRepository.php @@ -3,6 +3,8 @@ namespace MailPoet\Statistics; use MailPoet\Doctrine\Repository; +use MailPoet\Entities\NewsletterEntity; +use MailPoet\Entities\SendingQueueEntity; use MailPoet\Entities\StatisticsClickEntity; use MailPoet\Entities\StatisticsWooCommercePurchaseEntity; @@ -25,9 +27,12 @@ class StatisticsWooCommercePurchasesRepository extends Repository { ]); if (!$statistics instanceof StatisticsWooCommercePurchaseEntity) { + $newsletter = $click->getNewsletter(); + $queue = $click->getQueue(); + if ((!$newsletter instanceof NewsletterEntity) || (!$queue instanceof SendingQueueEntity)) return; $statistics = new StatisticsWooCommercePurchaseEntity( - $click->getNewsletter(), - $click->getQueue(), + $newsletter, + $queue, $click, $order->get_id(), $order->get_currency(), diff --git a/lib/Statistics/Track/WooCommercePurchases.php b/lib/Statistics/Track/WooCommercePurchases.php index 497d3f40ce..a3bf1b3cab 100644 --- a/lib/Statistics/Track/WooCommercePurchases.php +++ b/lib/Statistics/Track/WooCommercePurchases.php @@ -2,9 +2,12 @@ namespace MailPoet\Statistics\Track; -use MailPoet\Models\StatisticsClicks; -use MailPoet\Models\StatisticsWooCommercePurchases; -use MailPoet\Models\Subscriber; +use MailPoet\Entities\NewsletterEntity; +use MailPoet\Entities\StatisticsClickEntity; +use MailPoet\Entities\SubscriberEntity; +use MailPoet\Statistics\StatisticsClicksRepository; +use MailPoet\Statistics\StatisticsWooCommercePurchasesRepository; +use MailPoet\Subscribers\SubscribersRepository; use MailPoet\Util\Cookies; use MailPoet\WooCommerce\Helper; use WC_Order; @@ -18,12 +21,27 @@ class WooCommercePurchases { /** @var Cookies */ private $cookies; + /** @var StatisticsWooCommercePurchasesRepository */ + private $statisticsWooCommercePurchasesRepository; + + /** @var StatisticsClicksRepository */ + private $statisticsClicksRepository; + + /** @var SubscribersRepository */ + private $subscribersRepository; + public function __construct( Helper $woocommerceHelper, + StatisticsWooCommercePurchasesRepository $statisticsWooCommercePurchasesRepository, + StatisticsClicksRepository $statisticsClicksRepository, + SubscribersRepository $subscribersRepository, Cookies $cookies ) { $this->woocommerceHelper = $woocommerceHelper; $this->cookies = $cookies; + $this->statisticsWooCommercePurchasesRepository = $statisticsWooCommercePurchasesRepository; + $this->statisticsClicksRepository = $statisticsClicksRepository; + $this->subscribersRepository = $subscribersRepository; } public function trackPurchase($id, $useCookies = true) { @@ -40,13 +58,18 @@ class WooCommercePurchases { $from = clone $fromDate; $from->modify(-self::USE_CLICKS_SINCE_DAYS_AGO . ' days'); $to = $order->get_date_created(); + if (is_null($to)) { + return; + } // track purchases from all clicks matched by order email $processedNewsletterIdsMap = []; $orderEmailClicks = $this->getClicks($order->get_billing_email(), $from, $to); foreach ($orderEmailClicks as $click) { - StatisticsWooCommercePurchases::createOrUpdateByClickDataAndOrder($click, $order); - $processedNewsletterIdsMap[$click->newsletterId] = true; + $this->statisticsWooCommercePurchasesRepository->createOrUpdateByClickDataAndOrder($click, $order); + $newsletter = $click->getNewsletter(); + if (!$newsletter instanceof NewsletterEntity) continue; + $processedNewsletterIdsMap[$newsletter->getId()] = true; } if (!$useCookies) { @@ -56,35 +79,44 @@ class WooCommercePurchases { // track purchases from clicks matched by cookie email (only for newsletters not tracked by order) $cookieEmailClicks = $this->getClicks($this->getSubscriberEmailFromCookie(), $from, $to); foreach ($cookieEmailClicks as $click) { - if (isset($processedNewsletterIdsMap[$click->newsletterId])) { + $newsletter = $click->getNewsletter(); + if (!$newsletter instanceof NewsletterEntity) continue; + if (isset($processedNewsletterIdsMap[$newsletter->getId()])) { continue; // do not track click for newsletters that were already tracked by order email } - StatisticsWooCommercePurchases::createOrUpdateByClickDataAndOrder($click, $order); + $this->statisticsWooCommercePurchasesRepository->createOrUpdateByClickDataAndOrder($click, $order); } } - private function getClicks($email, $from, $to) { - $subscriber = Subscriber::findOne($email); - if (!$subscriber instanceof Subscriber) { + /** + * @param ?string $email + * @param \DateTimeInterface $from + * @param \DateTimeInterface $to + * @return StatisticsClickEntity[] + */ + private function getClicks(?string $email, \DateTimeInterface $from, \DateTimeInterface $to): array { + if (!$email) return []; + $subscriber = $this->subscribersRepository->findOneBy(['email' => $email]); + if (!$subscriber instanceof SubscriberEntity) { return []; } - return StatisticsClicks::findLatestPerNewsletterBySubscriber($subscriber, $from, $to); + return $this->statisticsClicksRepository->findLatestPerNewsletterBySubscriber($subscriber, $from, $to); } - private function getSubscriberEmailFromCookie() { + private function getSubscriberEmailFromCookie(): ?string { $cookieData = $this->cookies->get(Clicks::REVENUE_TRACKING_COOKIE_NAME); if (!$cookieData) { return null; } - $click = StatisticsClicks::findOne($cookieData['statistics_clicks']); - if (!$click instanceof StatisticsClicks) { + $click = $this->statisticsClicksRepository->findOneById($cookieData['statistics_clicks']); + if (!$click instanceof StatisticsClickEntity) { return null; } - $subscriber = Subscriber::findOne($click->subscriberId); - if ($subscriber) { - return $subscriber->email; + $subscriber = $click->getSubscriber(); + if ($subscriber instanceof SubscriberEntity) { + return $subscriber->getEmail(); } return null; } diff --git a/tests/integration/Statistics/Track/WooCommercePurchasesTest.php b/tests/integration/Statistics/Track/WooCommercePurchasesTest.php index 72b05f65d1..1ca75d9a56 100644 --- a/tests/integration/Statistics/Track/WooCommercePurchasesTest.php +++ b/tests/integration/Statistics/Track/WooCommercePurchasesTest.php @@ -9,7 +9,10 @@ use MailPoet\Models\SendingQueue; use MailPoet\Models\StatisticsClicks; use MailPoet\Models\StatisticsWooCommercePurchases; use MailPoet\Models\Subscriber; +use MailPoet\Statistics\StatisticsClicksRepository; +use MailPoet\Statistics\StatisticsWooCommercePurchasesRepository; use MailPoet\Statistics\Track\WooCommercePurchases; +use MailPoet\Subscribers\SubscribersRepository; use MailPoet\Tasks\Sending; use MailPoet\Util\Cookies; use MailPoet\WooCommerce\Helper as WooCommerceHelper; @@ -52,7 +55,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { $wrongClick = $this->createClick($this->link, $wrongSubscriber, 1); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(1); @@ -62,7 +71,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItTracksPayment() { $click = $this->createClick($this->link, $this->subscriber); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(1); @@ -85,7 +100,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { $click2 = $this->createClick($link, $this->subscriber, 1); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(2); @@ -108,12 +129,24 @@ class WooCommercePurchasesTest extends \MailPoetTest { // first order $orderMock = $this->createOrderMock($this->subscriber->email, 10.0, 123); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); // second order $orderMock = $this->createOrderMock($this->subscriber->email, 20.0, 456); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); expect(count(StatisticsWooCommercePurchases::findMany()))->equals(2); @@ -124,7 +157,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { $this->createClick($this->link, $this->subscriber, 5); $latestClick = $this->createClick($this->link, $this->subscriber, 1); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::orderByDesc('created_at')->findMany(); @@ -135,7 +174,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItTracksPaymentOnlyOnce() { $this->createClick($this->link, $this->subscriber); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $woocommercePurchases->trackPurchase($orderMock->get_id()); expect(count(StatisticsWooCommercePurchases::findMany()))->equals(1); @@ -144,7 +189,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItTracksPaymentOnlyOnceWhenNewClickAppears() { $this->createClick($this->link, $this->subscriber, 5); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $this->createClick($this->link, $this->subscriber, 1); @@ -155,7 +206,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItDoesNotTrackPaymentWhenClickTooOld() { $this->createClick($this->link, $this->subscriber, 20); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); expect(count(StatisticsWooCommercePurchases::findMany()))->equals(0); } @@ -163,7 +220,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItDoesNotTrackPaymentForDifferentEmail() { $this->createClick($this->link, $this->subscriber); $orderMock = $this->createOrderMock('different.email@example.com'); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); expect(count(StatisticsWooCommercePurchases::findMany()))->equals(0); } @@ -171,7 +234,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { public function testItDoesNotTrackPaymentWhenClickNewerThanOrder() { $this->createClick($this->link, $this->subscriber, 0); $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); expect(count(StatisticsWooCommercePurchases::findMany()))->equals(0); } @@ -181,7 +250,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { $this->createClick($this->link, $this->subscriber, 0); // wrong click, should not be tracked $orderMock = $this->createOrderMock($this->subscriber->email); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect($purchaseStats)->count(1); @@ -201,7 +276,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { ]); $orderMock = $this->createOrderMock($orderEmail); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(1); @@ -224,7 +305,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { ]); $orderMock = $this->createOrderMock($orderEmail); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(1); @@ -252,7 +339,13 @@ class WooCommercePurchasesTest extends \MailPoetTest { ]); $orderMock = $this->createOrderMock($orderEmail); - $woocommercePurchases = new WooCommercePurchases($this->createWooCommerceHelperMock($orderMock), $this->cookies); + $woocommercePurchases = new WooCommercePurchases( + $this->createWooCommerceHelperMock($orderMock), + $this->diContainer->get(StatisticsWooCommercePurchasesRepository::class), + $this->diContainer->get(StatisticsClicksRepository::class), + $this->diContainer->get(SubscribersRepository::class), + $this->cookies + ); $woocommercePurchases->trackPurchase($orderMock->get_id()); $purchaseStats = StatisticsWooCommercePurchases::findMany(); expect(count($purchaseStats))->equals(2);