Ei kuvausta

SubscribersRepository.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. namespace MailPoet\Subscribers;
  3. if (!defined('ABSPATH')) exit;
  4. use MailPoet\Doctrine\Repository;
  5. use MailPoet\Entities\SegmentEntity;
  6. use MailPoet\Entities\SubscriberCustomFieldEntity;
  7. use MailPoet\Entities\SubscriberEntity;
  8. use MailPoet\Entities\SubscriberSegmentEntity;
  9. use MailPoet\Entities\UserAgentEntity;
  10. use MailPoet\WP\Functions as WPFunctions;
  11. use MailPoetVendor\Carbon\Carbon;
  12. use MailPoetVendor\Doctrine\DBAL\Connection;
  13. use MailPoetVendor\Doctrine\ORM\EntityManager;
  14. use MailPoetVendor\Doctrine\ORM\Query\Expr\Join;
  15. /**
  16. * @extends Repository<SubscriberEntity>
  17. */
  18. class SubscribersRepository extends Repository {
  19. /** @var WPFunctions */
  20. private $wp;
  21. protected $ignoreColumnsForUpdate = [
  22. 'wp_user_id',
  23. 'is_woocommerce_user',
  24. 'email',
  25. 'created_at',
  26. 'last_subscribed_at',
  27. ];
  28. public function __construct(
  29. EntityManager $entityManager,
  30. WPFunctions $wp
  31. ) {
  32. $this->wp = $wp;
  33. parent::__construct($entityManager);
  34. }
  35. protected function getEntityClassName() {
  36. return SubscriberEntity::class;
  37. }
  38. /**
  39. * @return int
  40. */
  41. public function getTotalSubscribers() {
  42. $query = $this->entityManager
  43. ->createQueryBuilder()
  44. ->select('count(n.id)')
  45. ->from(SubscriberEntity::class, 'n')
  46. ->where('n.deletedAt IS NULL AND n.status IN (:statuses)')
  47. ->setParameter('statuses', [
  48. SubscriberEntity::STATUS_SUBSCRIBED,
  49. SubscriberEntity::STATUS_UNCONFIRMED,
  50. SubscriberEntity::STATUS_INACTIVE,
  51. ])
  52. ->getQuery();
  53. return (int)$query->getSingleScalarResult();
  54. }
  55. public function findBySegment(int $segmentId): array {
  56. return $this->entityManager
  57. ->createQueryBuilder()
  58. ->select('s')
  59. ->from(SubscriberEntity::class, 's')
  60. ->join('s.subscriberSegments', 'ss', Join::WITH, 'ss.segment = :segment')
  61. ->setParameter('segment', $segmentId)
  62. ->getQuery()->getResult();
  63. }
  64. public function findExclusiveSubscribersBySegment(int $segmentId): array {
  65. return $this->entityManager->createQueryBuilder()
  66. ->select('s')
  67. ->from(SubscriberEntity::class, 's')
  68. ->join('s.subscriberSegments', 'ss', Join::WITH, 'ss.segment = :segment')
  69. ->leftJoin('s.subscriberSegments', 'ss2', Join::WITH, 'ss2.segment <> :segment AND ss2.status = :subscribed')
  70. ->leftJoin('ss2.segment', 'seg', Join::WITH, 'seg.deletedAt IS NULL')
  71. ->groupBy('s.id')
  72. ->andHaving('COUNT(seg.id) = 0')
  73. ->setParameter('segment', $segmentId)
  74. ->setParameter('subscribed', SubscriberEntity::STATUS_SUBSCRIBED)
  75. ->getQuery()->getResult();
  76. }
  77. /**
  78. * @return int - number of processed ids
  79. */
  80. public function bulkTrash(array $ids): int {
  81. if (empty($ids)) {
  82. return 0;
  83. }
  84. $this->entityManager->createQueryBuilder()
  85. ->update(SubscriberEntity::class, 's')
  86. ->set('s.deletedAt', 'CURRENT_TIMESTAMP()')
  87. ->where('s.id IN (:ids)')
  88. ->setParameter('ids', $ids)
  89. ->getQuery()->execute();
  90. return count($ids);
  91. }
  92. /**
  93. * @return int - number of processed ids
  94. */
  95. public function bulkRestore(array $ids): int {
  96. if (empty($ids)) {
  97. return 0;
  98. }
  99. $this->entityManager->createQueryBuilder()
  100. ->update(SubscriberEntity::class, 's')
  101. ->set('s.deletedAt', ':deletedAt')
  102. ->where('s.id IN (:ids)')
  103. ->setParameter('deletedAt', null)
  104. ->setParameter('ids', $ids)
  105. ->getQuery()->execute();
  106. return count($ids);
  107. }
  108. /**
  109. * @return int - number of processed ids
  110. */
  111. public function bulkDelete(array $ids): int {
  112. if (empty($ids)) {
  113. return 0;
  114. }
  115. $count = 0;
  116. $this->entityManager->transactional(function (EntityManager $entityManager) use ($ids, &$count) {
  117. // Delete subscriber segments
  118. $this->bulkRemoveFromAllSegments($ids);
  119. // Delete subscriber custom fields
  120. $subscriberCustomFieldTable = $entityManager->getClassMetadata(SubscriberCustomFieldEntity::class)->getTableName();
  121. $subscriberTable = $entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
  122. $entityManager->getConnection()->executeUpdate("
  123. DELETE scs FROM $subscriberCustomFieldTable scs
  124. JOIN $subscriberTable s ON s.`id` = scs.`subscriber_id`
  125. WHERE scs.`subscriber_id` IN (:ids)
  126. AND s.`is_woocommerce_user` = false
  127. AND s.`wp_user_id` IS NULL
  128. ", ['ids' => $ids], ['ids' => Connection::PARAM_INT_ARRAY]);
  129. $queryBuilder = $entityManager->createQueryBuilder();
  130. $count = $queryBuilder->delete(SubscriberEntity::class, 's')
  131. ->where('s.id IN (:ids)')
  132. ->andWhere('s.wpUserId IS NULL')
  133. ->andWhere('s.isWoocommerceUser = false')
  134. ->setParameter('ids', $ids)
  135. ->getQuery()->execute();
  136. });
  137. return $count;
  138. }
  139. /**
  140. * @return int - number of processed ids
  141. */
  142. public function bulkRemoveFromSegment(SegmentEntity $segment, array $ids): int {
  143. if (empty($ids)) {
  144. return 0;
  145. }
  146. $subscriberSegmentsTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
  147. $count = $this->entityManager->getConnection()->executeUpdate("
  148. DELETE ss FROM $subscriberSegmentsTable ss
  149. WHERE ss.`subscriber_id` IN (:ids)
  150. AND ss.`segment_id` = :segment_id
  151. ", ['ids' => $ids, 'segment_id' => $segment->getId()], ['ids' => Connection::PARAM_INT_ARRAY]);
  152. return $count;
  153. }
  154. /**
  155. * @return int - number of processed ids
  156. */
  157. public function bulkRemoveFromAllSegments(array $ids): int {
  158. if (empty($ids)) {
  159. return 0;
  160. }
  161. $subscriberSegmentsTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
  162. $segmentsTable = $this->entityManager->getClassMetadata(SegmentEntity::class)->getTableName();
  163. $count = $this->entityManager->getConnection()->executeUpdate("
  164. DELETE ss FROM $subscriberSegmentsTable ss
  165. JOIN $segmentsTable s ON s.id = ss.segment_id AND s.`type` = :typeDefault
  166. WHERE ss.`subscriber_id` IN (:ids)
  167. ", [
  168. 'ids' => $ids,
  169. 'typeDefault' => SegmentEntity::TYPE_DEFAULT,
  170. ], ['ids' => Connection::PARAM_INT_ARRAY]);
  171. return $count;
  172. }
  173. /**
  174. * @return int - number of processed ids
  175. */
  176. public function bulkAddToSegment(SegmentEntity $segment, array $ids): int {
  177. if (empty($ids)) {
  178. return 0;
  179. }
  180. $subscribers = $this->entityManager
  181. ->createQueryBuilder()
  182. ->select('s')
  183. ->from(SubscriberEntity::class, 's')
  184. ->leftJoin('s.subscriberSegments', 'ss', Join::WITH, 'ss.segment = :segment')
  185. ->where('s.id IN (:ids)')
  186. ->andWhere('ss.segment IS NULL')
  187. ->setParameter('ids', $ids)
  188. ->setParameter('segment', $segment)
  189. ->getQuery()->execute();
  190. $this->entityManager->transactional(function (EntityManager $entityManager) use ($subscribers, $segment) {
  191. foreach ($subscribers as $subscriber) {
  192. $subscriberSegment = new SubscriberSegmentEntity($segment, $subscriber, SubscriberEntity::STATUS_SUBSCRIBED);
  193. $this->entityManager->persist($subscriberSegment);
  194. }
  195. $this->entityManager->flush();
  196. });
  197. return count($subscribers);
  198. }
  199. public function woocommerceUserExists(): bool {
  200. $subscribers = $this->entityManager
  201. ->createQueryBuilder()
  202. ->select('s')
  203. ->from(SubscriberEntity::class, 's')
  204. ->join('s.subscriberSegments', 'ss')
  205. ->join('ss.segment', 'segment')
  206. ->where('segment.type = :segmentType')
  207. ->setParameter('segmentType', SegmentEntity::TYPE_WC_USERS)
  208. ->andWhere('s.isWoocommerceUser = true')
  209. ->getQuery()
  210. ->setMaxResults(1)
  211. ->execute();
  212. return count($subscribers) > 0;
  213. }
  214. /**
  215. * @return int - number of processed ids
  216. */
  217. public function bulkMoveToSegment(SegmentEntity $segment, array $ids): int {
  218. if (empty($ids)) {
  219. return 0;
  220. }
  221. $this->bulkRemoveFromAllSegments($ids);
  222. return $this->bulkAddToSegment($segment, $ids);
  223. }
  224. public function bulkUnsubscribe(array $ids): int {
  225. $this->entityManager->createQueryBuilder()
  226. ->update(SubscriberEntity::class, 's')
  227. ->set('s.status', ':status')
  228. ->where('s.id IN (:ids)')
  229. ->setParameter('status', SubscriberEntity::STATUS_UNSUBSCRIBED)
  230. ->setParameter('ids', $ids)
  231. ->getQuery()->execute();
  232. return count($ids);
  233. }
  234. public function findWpUserIdAndEmailByEmails(array $emails): array {
  235. return $this->entityManager->createQueryBuilder()
  236. ->select('s.wpUserId AS wp_user_id, LOWER(s.email) AS email')
  237. ->from(SubscriberEntity::class, 's')
  238. ->where('s.email IN (:emails)')
  239. ->setParameter('emails', $emails)
  240. ->getQuery()->getResult();
  241. }
  242. public function findIdAndEmailByEmails(array $emails): array {
  243. return $this->entityManager->createQueryBuilder()
  244. ->select('s.id, s.email')
  245. ->from(SubscriberEntity::class, 's')
  246. ->where('s.email IN (:emails)')
  247. ->setParameter('emails', $emails)
  248. ->getQuery()->getResult();
  249. }
  250. /**
  251. * @return int[]
  252. */
  253. public function findIdsOfDeletedByEmails(array $emails): array {
  254. return $this->entityManager->createQueryBuilder()
  255. ->select('s.id')
  256. ->from(SubscriberEntity::class, 's')
  257. ->where('s.email IN (:emails)')
  258. ->andWhere('s.deletedAt IS NOT NULL')
  259. ->setParameter('emails', $emails)
  260. ->getQuery()->getResult();
  261. }
  262. public function getCurrentWPUser(): ?SubscriberEntity {
  263. $wpUser = WPFunctions::get()->wpGetCurrentUser();
  264. if (empty($wpUser->ID)) {
  265. return null; // Don't look up a subscriber for guests
  266. }
  267. return $this->findOneBy(['wpUserId' => $wpUser->ID]);
  268. }
  269. public function findByUpdatedScoreNotInLastMonth(int $limit): array {
  270. $dateTime = (new Carbon())->subMonths(1);
  271. return $this->entityManager->createQueryBuilder()
  272. ->select('s')
  273. ->from(SubscriberEntity::class, 's')
  274. ->where('s.engagementScoreUpdatedAt IS NULL')
  275. ->orWhere('s.engagementScoreUpdatedAt < :dateTime')
  276. ->setParameter('dateTime', $dateTime)
  277. ->getQuery()
  278. ->setMaxResults($limit)
  279. ->getResult();
  280. }
  281. public function maybeUpdateLastEngagement(SubscriberEntity $subscriberEntity, ?UserAgentEntity $userAgent = null): void {
  282. if ($userAgent instanceof UserAgentEntity && $userAgent->getUserAgentType() === UserAgentEntity::USER_AGENT_TYPE_MACHINE) {
  283. return;
  284. }
  285. $now = Carbon::createFromTimestamp((int)$this->wp->currentTime('timestamp'));
  286. // Do not update engagement if was recently updated to avoid unnecessary updates in DB
  287. if ($subscriberEntity->getLastEngagementAt() && $subscriberEntity->getLastEngagementAt() > $now->subMinute()) {
  288. return;
  289. }
  290. // Update last engagement for human (and also unknown) user agent
  291. $subscriberEntity->setLastEngagementAt($now);
  292. $this->flush();
  293. }
  294. /**
  295. * @param array $ids
  296. * @return string[]
  297. */
  298. public function getUndeletedSubscribersEmailsByIds(array $ids): array {
  299. return $this->entityManager->createQueryBuilder()
  300. ->select('s.email')
  301. ->from(SubscriberEntity::class, 's')
  302. ->where('s.deletedAt IS NULL')
  303. ->andWhere('s.id IN (:ids)')
  304. ->setParameter('ids', $ids)
  305. ->getQuery()
  306. ->getArrayResult();
  307. }
  308. }