No Description

NewsletterStatisticsRepository.php 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <?php
  2. namespace MailPoet\Newsletter\Statistics;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Doctrine\Repository;
  5. use MailPoet\Entities\NewsletterEntity;
  6. use MailPoet\Entities\ScheduledTaskEntity;
  7. use MailPoet\Entities\StatisticsBounceEntity;
  8. use MailPoet\Entities\StatisticsClickEntity;
  9. use MailPoet\Entities\StatisticsOpenEntity;
  10. use MailPoet\Entities\StatisticsUnsubscribeEntity;
  11. use MailPoet\Entities\StatisticsWooCommercePurchaseEntity;
  12. use MailPoet\Entities\UserAgentEntity;
  13. use MailPoet\WooCommerce\Helper as WCHelper;
  14. use MailPoetVendor\Doctrine\ORM\EntityManager;
  15. use MailPoetVendor\Doctrine\ORM\QueryBuilder;
  16. use MailPoetVendor\Doctrine\ORM\UnexpectedResultException;
  17. /**
  18. * @extends Repository<NewsletterEntity>
  19. */
  20. class NewsletterStatisticsRepository extends Repository {
  21. /** @var WCHelper */
  22. private $wcHelper;
  23. public function __construct(
  24. EntityManager $entityManager,
  25. WCHelper $wcHelper
  26. ) {
  27. parent::__construct($entityManager);
  28. $this->wcHelper = $wcHelper;
  29. }
  30. protected function getEntityClassName() {
  31. return NewsletterEntity::class;
  32. }
  33. public function getStatistics(NewsletterEntity $newsletter): NewsletterStatistics {
  34. $stats = new NewsletterStatistics(
  35. $this->getStatisticsClickCount($newsletter),
  36. $this->getStatisticsOpenCount($newsletter),
  37. $this->getStatisticsUnsubscribeCount($newsletter),
  38. $this->getStatisticsBounceCount($newsletter),
  39. $this->getTotalSentCount($newsletter),
  40. $this->getWooCommerceRevenue($newsletter)
  41. );
  42. $stats->setMachineOpenCount($this->getStatisticsMachineOpenCount($newsletter));
  43. return $stats;
  44. }
  45. /**
  46. * @param NewsletterEntity[] $newsletters
  47. * @return NewsletterStatistics[]
  48. */
  49. public function getBatchStatistics(array $newsletters): array {
  50. $totalSentCounts = $this->getTotalSentCounts($newsletters);
  51. $clickCounts = $this->getStatisticCounts(StatisticsClickEntity::class, $newsletters);
  52. $openCounts = $this->getStatisticCounts(StatisticsOpenEntity::class, $newsletters);
  53. $unsubscribeCounts = $this->getStatisticCounts(StatisticsUnsubscribeEntity::class, $newsletters);
  54. $bounceCounts = $this->getStatisticCounts(StatisticsBounceEntity::class, $newsletters);
  55. $wooCommerceRevenues = $this->getWooCommerceRevenues($newsletters);
  56. $statistics = [];
  57. foreach ($newsletters as $newsletter) {
  58. $id = $newsletter->getId();
  59. $statistics[$id] = new NewsletterStatistics(
  60. $clickCounts[$id] ?? 0,
  61. $openCounts[$id] ?? 0,
  62. $unsubscribeCounts[$id] ?? 0,
  63. $bounceCounts[$id] ?? 0,
  64. $totalSentCounts[$id] ?? 0,
  65. $wooCommerceRevenues[$id] ?? null
  66. );
  67. }
  68. return $statistics;
  69. }
  70. public function getTotalSentCount(NewsletterEntity $newsletter): int {
  71. $counts = $this->getTotalSentCounts([$newsletter]);
  72. return $counts[$newsletter->getId()] ?? 0;
  73. }
  74. public function getStatisticsClickCount(NewsletterEntity $newsletter): int {
  75. $counts = $this->getStatisticCounts(StatisticsClickEntity::class, [$newsletter]);
  76. return $counts[$newsletter->getId()] ?? 0;
  77. }
  78. public function getStatisticsOpenCount(NewsletterEntity $newsletter): int {
  79. $counts = $this->getStatisticCounts(StatisticsOpenEntity::class, [$newsletter]);
  80. return $counts[$newsletter->getId()] ?? 0;
  81. }
  82. public function getStatisticsMachineOpenCount(NewsletterEntity $newsletter): int {
  83. $qb = $this->getStatisticsQuery(StatisticsOpenEntity::class, [$newsletter]);
  84. $result = $qb->andWhere('(stats.userAgentType = :userAgentType)')
  85. ->setParameter('userAgentType', UserAgentEntity::USER_AGENT_TYPE_MACHINE)
  86. ->getQuery()
  87. ->getOneOrNullResult();
  88. if (empty($result)) return 0;
  89. return $result['cnt'] ?? 0;
  90. }
  91. public function getStatisticsUnsubscribeCount(NewsletterEntity $newsletter): int {
  92. $counts = $this->getStatisticCounts(StatisticsUnsubscribeEntity::class, [$newsletter]);
  93. return $counts[$newsletter->getId()] ?? 0;
  94. }
  95. public function getStatisticsBounceCount(NewsletterEntity $newsletter): int {
  96. $counts = $this->getStatisticCounts(StatisticsBounceEntity::class, [$newsletter]);
  97. return $counts[$newsletter->getId()] ?? 0;
  98. }
  99. public function getWooCommerceRevenue(NewsletterEntity $newsletter) {
  100. $revenues = $this->getWooCommerceRevenues([$newsletter]);
  101. return $revenues[$newsletter->getId()] ?? null;
  102. }
  103. /**
  104. * @param NewsletterEntity $newsletter
  105. * @return int
  106. */
  107. public function getChildrenCount(NewsletterEntity $newsletter) {
  108. try {
  109. return (int)$this->entityManager
  110. ->createQueryBuilder()
  111. ->select('COUNT(n.id) as cnt')
  112. ->from(NewsletterEntity::class, 'n')
  113. ->where('n.parent = :newsletter')
  114. ->setParameter('newsletter', $newsletter)
  115. ->getQuery()
  116. ->getSingleScalarResult();
  117. } catch (UnexpectedResultException $e) {
  118. return 0;
  119. }
  120. }
  121. private function getTotalSentCounts(array $newsletters): array {
  122. $results = $this->doctrineRepository
  123. ->createQueryBuilder('n')
  124. ->select('n.id, SUM(q.countProcessed) AS cnt')
  125. ->join('n.queues', 'q')
  126. ->join('q.task', 't')
  127. ->where('t.status = :status')
  128. ->setParameter('status', ScheduledTaskEntity::STATUS_COMPLETED)
  129. ->andWhere('q.newsletter IN (:newsletters)')
  130. ->setParameter('newsletters', $newsletters)
  131. ->groupBy('n.id')
  132. ->getQuery()
  133. ->getResult();
  134. $counts = [];
  135. foreach ($results ?: [] as $result) {
  136. $counts[(int)$result['id']] = (int)$result['cnt'];
  137. }
  138. return $counts;
  139. }
  140. private function getStatisticCounts(string $statisticsEntityName, array $newsletters): array {
  141. $qb = $this->getStatisticsQuery($statisticsEntityName, $newsletters);
  142. if (in_array($statisticsEntityName, [StatisticsOpenEntity::class, StatisticsClickEntity::class], true)) {
  143. $qb->andWhere('(stats.userAgentType = :userAgentType) OR (stats.userAgentType IS NULL)')
  144. ->setParameter('userAgentType', UserAgentEntity::USER_AGENT_TYPE_HUMAN);
  145. }
  146. $results = $qb
  147. ->getQuery()
  148. ->getResult();
  149. $counts = [];
  150. foreach ($results ?: [] as $result) {
  151. $counts[(int)$result['id']] = (int)$result['cnt'];
  152. }
  153. return $counts;
  154. }
  155. private function getStatisticsQuery(string $statisticsEntityName, array $newsletters): QueryBuilder {
  156. return $this->entityManager->createQueryBuilder()
  157. ->select('IDENTITY(stats.newsletter) AS id, COUNT(DISTINCT stats.subscriber) as cnt')
  158. ->from($statisticsEntityName, 'stats')
  159. ->where('stats.newsletter IN (:newsletters)')
  160. ->groupBy('stats.newsletter')
  161. ->setParameter('newsletters', $newsletters);
  162. }
  163. private function getWooCommerceRevenues(array $newsletters) {
  164. if (!$this->wcHelper->isWooCommerceActive()) {
  165. return null;
  166. }
  167. $currency = $this->wcHelper->getWoocommerceCurrency();
  168. $results = $this->entityManager
  169. ->createQueryBuilder()
  170. ->select('IDENTITY(stats.newsletter) AS id, SUM(stats.orderPriceTotal) AS total, COUNT(stats.id) AS cnt')
  171. ->from(StatisticsWooCommercePurchaseEntity::class, 'stats')
  172. ->where('stats.newsletter IN (:newsletters)')
  173. ->andWhere('stats.orderCurrency = :currency')
  174. ->setParameter('newsletters', $newsletters)
  175. ->setParameter('currency', $currency)
  176. ->groupBy('stats.newsletter')
  177. ->getQuery()
  178. ->getResult();
  179. $revenues = [];
  180. foreach ($results ?: [] as $result) {
  181. $revenues[(int)$result['id']] = new WooCommerceRevenue(
  182. $currency,
  183. (float)$result['total'],
  184. (int)$result['cnt'],
  185. $this->wcHelper
  186. );
  187. }
  188. return $revenues;
  189. }
  190. }