PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib / Segments / SubscribersFinder.php
mailpoet / lib / Segments Last commit date
DynamicSegments 1 month ago RestApi 1 month ago SegmentDependencyValidator.php 3 years ago SegmentListingRepository.php 1 month ago SegmentSaveController.php 2 weeks ago SegmentSubscribersRepository.php 2 weeks ago SegmentsFinder.php 3 years ago SegmentsRepository.php 2 weeks ago SegmentsSimpleListRepository.php 2 months ago SubscribersFinder.php 1 month ago WP.php 2 weeks ago WooCommerce.php 2 weeks ago index.php 3 years ago
SubscribersFinder.php
224 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Segments;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Entities\ScheduledTaskEntity;
9 use MailPoet\Entities\ScheduledTaskSubscriberEntity;
10 use MailPoet\Entities\SegmentEntity;
11 use MailPoet\Entities\SubscriberEntity;
12 use MailPoet\Entities\SubscriberSegmentEntity;
13 use MailPoet\InvalidStateException;
14 use MailPoet\Newsletter\Sending\ScheduledTaskSubscribersRepository;
15 use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
16 use MailPoetVendor\Doctrine\DBAL\ParameterType;
17 use MailPoetVendor\Doctrine\ORM\EntityManager;
18
19 class SubscribersFinder {
20
21 /** @var SegmentSubscribersRepository */
22 private $segmentSubscriberRepository;
23
24 /** @var SegmentsRepository */
25 private $segmentsRepository;
26
27 /** @var EntityManager */
28 private $entityManager;
29
30 /** @var ScheduledTaskSubscribersRepository */
31 private $scheduledTaskSubscribersRepository;
32
33 public function __construct(
34 SegmentSubscribersRepository $segmentSubscriberRepository,
35 SegmentsRepository $segmentsRepository,
36 EntityManager $entityManager,
37 ScheduledTaskSubscribersRepository $scheduledTaskSubscribersRepository
38 ) {
39 $this->segmentSubscriberRepository = $segmentSubscriberRepository;
40 $this->segmentsRepository = $segmentsRepository;
41 $this->entityManager = $entityManager;
42 $this->scheduledTaskSubscribersRepository = $scheduledTaskSubscribersRepository;
43 }
44
45 /**
46 * @return array
47 * @throws InvalidStateException
48 */
49 public function findSubscribersInSegments($subscribersToProcessIds, $newsletterSegmentsIds, ?int $filterSegmentId = null) {
50 $result = [];
51 foreach ($newsletterSegmentsIds as $segmentId) {
52 $segment = $this->segmentsRepository->findOneById($segmentId);
53 if (!$segment instanceof SegmentEntity) {
54 continue; // skip deleted segments
55 }
56 $result = array_merge($result, $this->findSubscribersInSegment($segment, $subscribersToProcessIds));
57 }
58
59 if (is_int($filterSegmentId)) {
60 $filterSegment = $this->segmentsRepository->verifyDynamicSegmentExists($filterSegmentId);
61 $idsInFilterSegment = $this->findSubscribersInSegment($filterSegment, $subscribersToProcessIds);
62 $result = array_intersect($result, $idsInFilterSegment);
63 }
64
65 return $this->unique($result);
66 }
67
68 /**
69 * @param int[] $newsletterSegmentsIds
70 * @return int[]
71 * @throws InvalidStateException
72 */
73 public function getSubscriberIdsFromSegments(array $newsletterSegmentsIds, ?int $filterSegmentId = null): array {
74 $result = [];
75 foreach ($newsletterSegmentsIds as $segmentId) {
76 $segment = $this->segmentsRepository->findOneById($segmentId);
77 if (!$segment instanceof SegmentEntity) {
78 continue;
79 }
80 try {
81 $result = array_merge($result, $this->segmentSubscriberRepository->getSubscriberIdsInSegment((int)$segment->getId()));
82 } catch (InvalidStateException $exception) {
83 continue;
84 }
85 }
86
87 if (is_int($filterSegmentId)) {
88 $filterSegment = $this->segmentsRepository->verifyDynamicSegmentExists($filterSegmentId);
89 $filterSubscriberIds = $this->segmentSubscriberRepository->getSubscriberIdsInSegment((int)$filterSegment->getId());
90 $result = array_intersect($result, $filterSubscriberIds);
91 }
92
93 return array_values($this->unique($result));
94 }
95
96 private function findSubscribersInSegment(SegmentEntity $segment, $subscribersToProcessIds): array {
97 try {
98 return $this->segmentSubscriberRepository->findSubscribersIdsInSegment((int)$segment->getId(), $subscribersToProcessIds);
99 } catch (InvalidStateException $e) {
100 return [];
101 }
102 }
103
104 /**
105 * @param ScheduledTaskEntity $task
106 * @param array<int> $segmentIds
107 *
108 * @return void
109 */
110 public function addSubscribersToTaskFromSegments(ScheduledTaskEntity $task, array $segmentIds, ?int $filterSegmentId = null): void {
111 // Prepare subscribers on the DB side for performance reasons
112 if (is_int($filterSegmentId)) {
113 try {
114 $this->segmentsRepository->verifyDynamicSegmentExists($filterSegmentId);
115 } catch (InvalidStateException $exception) {
116 return;
117 }
118 }
119 $staticSegmentIds = [];
120 $dynamicSegmentIds = [];
121 foreach ($segmentIds as $segment) {
122 $segment = $this->segmentsRepository->findOneById($segment);
123 if ($segment instanceof SegmentEntity) {
124 if ($segment->isStatic()) {
125 $staticSegmentIds[] = (int)$segment->getId();
126 } elseif ($segment->getType() === SegmentEntity::TYPE_DYNAMIC) {
127 $dynamicSegmentIds[] = (int)$segment->getId();
128 }
129 }
130 }
131 $count = 0;
132 if (!empty($staticSegmentIds)) {
133 $count += $this->addSubscribersToTaskFromStaticSegments($task, $staticSegmentIds, $filterSegmentId);
134 }
135 if (!empty($dynamicSegmentIds)) {
136 $count += $this->addSubscribersToTaskFromDynamicSegments($task, $dynamicSegmentIds, $filterSegmentId);
137 }
138 if ($count > 0) {
139 $this->entityManager->refresh($task);
140 }
141 }
142
143 /**
144 * @param ScheduledTaskEntity $task
145 * @param array<int> $segmentIds
146 *
147 * @return int
148 */
149 private function addSubscribersToTaskFromStaticSegments(ScheduledTaskEntity $task, array $segmentIds, ?int $filterSegmentId = null) {
150 $scheduledTaskSubscriberTable = $this->entityManager->getClassMetadata(ScheduledTaskSubscriberEntity::class)->getTableName();
151 $subscriberSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
152 $subscriberTable = $this->entityManager->getClassMetadata(SubscriberEntity::class)->getTableName();
153
154 $connection = $this->entityManager->getConnection();
155 $selectQueryBuilder = $connection->createQueryBuilder();
156 $selectQueryBuilder
157 // No DISTINCT needed: the INSERT IGNORE relies on the (task_id, subscriber_id)
158 // primary key of scheduled_task_subscribers to drop duplicates, and DISTINCT
159 // forces an expensive temporary table on large segments.
160 ->select(':task_id as task_id', 'subscribers.id as subscriber_id', ':processed as processed')
161 ->from($subscriberSegmentTable, 'relation')
162 ->join('relation', $subscriberTable, 'subscribers', 'subscribers.id = relation.subscriber_id')
163 ->where('subscribers.deleted_at IS NULL')
164 ->andWhere('subscribers.status = :subscribers_status')
165 ->andWhere('relation.status = :relation_status')
166 ->andWhere($selectQueryBuilder->expr()->in('relation.segment_id', ':segment_ids'))
167 ->setParameter('task_id', $task->getId(), ParameterType::INTEGER)
168 ->setParameter('processed', ScheduledTaskSubscriberEntity::STATUS_UNPROCESSED, ParameterType::INTEGER)
169 ->setParameter('subscribers_status', SubscriberEntity::STATUS_SUBSCRIBED, ParameterType::STRING)
170 ->setParameter('relation_status', SubscriberEntity::STATUS_SUBSCRIBED, ParameterType::STRING)
171 ->setParameter('segment_ids', $segmentIds, ArrayParameterType::INTEGER);
172
173 if ($filterSegmentId) {
174 $filterSegmentSubscriberIds = $this->segmentSubscriberRepository->findSubscribersIdsInSegment($filterSegmentId);
175 $selectQueryBuilder
176 ->andWhere($selectQueryBuilder->expr()->in('subscribers.id', ':filterSegmentSubscriberIds'))
177 ->setParameter('filterSegmentSubscriberIds', $filterSegmentSubscriberIds, ArrayParameterType::INTEGER);
178 }
179
180 // queryBuilder doesn't support INSERT IGNORE directly
181 $sql = "INSERT IGNORE INTO $scheduledTaskSubscriberTable (task_id, subscriber_id, processed) " . $selectQueryBuilder->getSQL();
182 $result = $connection->executeQuery($sql, $selectQueryBuilder->getParameters(), $selectQueryBuilder->getParameterTypes());
183
184 return (int)$result->rowCount();
185 }
186
187 /**
188 * @param ScheduledTaskEntity $task
189 * @param array<int> $segmentIds
190 *
191 * @return int
192 */
193 private function addSubscribersToTaskFromDynamicSegments(ScheduledTaskEntity $task, array $segmentIds, ?int $filterSegmentId = null) {
194 $count = 0;
195 foreach ($segmentIds as $segmentId) {
196 $count += $this->addSubscribersToTaskFromDynamicSegment($task, (int)$segmentId, $filterSegmentId);
197 }
198 return $count;
199 }
200
201 private function addSubscribersToTaskFromDynamicSegment(ScheduledTaskEntity $task, int $segmentId, ?int $filterSegmentId) {
202 $count = 0;
203 $subscriberIds = $this->segmentSubscriberRepository->getSubscriberIdsInSegment($segmentId);
204
205 if ($filterSegmentId) {
206 $filterSegmentSubscriberIds = $this->segmentSubscriberRepository->getSubscriberIdsInSegment($filterSegmentId);
207 $subscriberIds = array_intersect($subscriberIds, $filterSegmentSubscriberIds);
208 }
209
210 if ($subscriberIds) {
211 $count += $this->scheduledTaskSubscribersRepository->addSubscribersByIds($task, $subscriberIds);
212 }
213 return $count;
214 }
215
216 private function unique(array $subscriberIds) {
217 $result = [];
218 foreach ($subscriberIds as $id) {
219 $result[$id] = $id;
220 }
221 return $result;
222 }
223 }
224