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 / Subscribers / SubscriberSegmentRepository.php
mailpoet / lib / Subscribers Last commit date
ConfirmationEmailTemplate 1 month ago ImportExport 2 weeks ago RestApi 3 days ago Statistics 1 month ago BulkActionController.php 3 days ago BulkActionException.php 1 month ago BulkConfirmationEmailResender.php 2 months ago ConfirmationEmailCustomizer.php 2 months ago ConfirmationEmailMailer.php 2 months ago ConfirmationEmailResolver.php 2 months ago EngagementDataBackfiller.php 2 months ago InactiveSubscribersController.php 4 days ago LinkTokens.php 2 months ago NewSubscriberNotificationMailer.php 2 months ago RequiredCustomFieldValidator.php 2 months ago SegmentsCountRecalculator.php 2 weeks ago Source.php 2 months ago SubscriberActions.php 2 months ago SubscriberCustomFieldRepository.php 3 years ago SubscriberIPsRepository.php 2 years ago SubscriberLimitNotificationEvaluator.php 2 months ago SubscriberLimitNotificationMailer.php 2 months ago SubscriberLimitNotificationScheduler.php 2 months ago SubscriberListingRepository.php 2 weeks ago SubscriberPersonalDataEraser.php 2 months ago SubscriberSaveController.php 3 days ago SubscriberSegmentRepository.php 2 weeks ago SubscriberSubscribeController.php 2 weeks ago SubscriberTagRepository.php 4 years ago SubscribersCountsController.php 2 weeks ago SubscribersEmailCountsController.php 2 weeks ago SubscribersRepository.php 3 days ago TrackingConsentController.php 4 days ago index.php 3 years ago
SubscriberSegmentRepository.php
232 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Subscribers;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\Doctrine\Repository;
9 use MailPoet\Entities\SegmentEntity;
10 use MailPoet\Entities\SubscriberEntity;
11 use MailPoet\Entities\SubscriberSegmentEntity;
12 use MailPoet\WP\Functions as WPFunctions;
13 use MailPoetVendor\Doctrine\ORM\EntityManager;
14 use MailPoetVendor\Doctrine\ORM\Query\Expr\Join;
15
16 /**
17 * @extends Repository<SubscriberSegmentEntity>
18 */
19 class SubscriberSegmentRepository extends Repository {
20 /** @var WPFunctions */
21 private $wp;
22
23 /** @var SegmentsCountRecalculator */
24 private $segmentsCountRecalculator;
25
26 public function __construct(
27 EntityManager $entityManager,
28 WPFunctions $wp,
29 SegmentsCountRecalculator $segmentsCountRecalculator
30 ) {
31 parent::__construct($entityManager);
32 $this->wp = $wp;
33 $this->segmentsCountRecalculator = $segmentsCountRecalculator;
34 }
35
36 protected function getEntityClassName() {
37 return SubscriberSegmentEntity::class;
38 }
39
40 public function getNonDefaultSubscribedSegments(int $subscriberId): array {
41 $qb = $this->entityManager->createQueryBuilder();
42 return $qb->select('ss')
43 ->from(SubscriberSegmentEntity::class, 'ss')
44 ->join('ss.segment', 'seg', Join::WITH, 'seg.type != :typeDefault')
45 ->where('ss.subscriber = :subscriberId')
46 ->andWhere('ss.status = :subscribed')
47 ->setParameter('subscriberId', $subscriberId)
48 ->setParameter('subscribed', SubscriberEntity::STATUS_SUBSCRIBED)
49 ->setParameter('typeDefault', SegmentEntity::TYPE_DEFAULT)
50 ->getQuery()
51 ->getResult();
52 }
53
54 public function deleteAllBySubscriber(SubscriberEntity $subscriber): void {
55 $this->entityManager->createQueryBuilder()
56 ->delete(SubscriberSegmentEntity::class, 'ss')
57 ->where('ss.subscriber = :subscriber')
58 ->setParameter('subscriber', $subscriber)
59 ->getQuery()
60 ->execute();
61 $this->segmentsCountRecalculator->recalculateForSubscribers([(int)$subscriber->getId()]);
62 }
63
64 /**
65 * @param SegmentEntity[] $segments
66 */
67 public function unsubscribeFromSegments(SubscriberEntity $subscriber, array $segments = []): void {
68 $subscriber->setConfirmationsCount(0);
69
70 if (!empty($segments)) {
71 // unsubscribe from segments
72 foreach ($segments as $segment) {
73 // do not remove subscriptions to the WP Users segment
74 if ($segment->getType() === SegmentEntity::TYPE_WP_USERS) {
75 continue;
76 }
77
78 // Defer the recompute: there is a single recalculateForSubscribers()
79 // call for this subscriber at the end of the method.
80 $this->createOrUpdate($subscriber, $segment, SubscriberEntity::STATUS_UNSUBSCRIBED, false, true);
81 }
82 $this->entityManager->flush();
83 } else {
84 $subscriberSegmentsToUnsubscribe = array_values(array_filter(
85 $subscriber->getSubscriberSegments()->toArray(),
86 function (SubscriberSegmentEntity $subscriberSegment): bool {
87 $segment = $subscriberSegment->getSegment();
88 return $subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
89 && $segment instanceof SegmentEntity
90 && $segment->getType() !== SegmentEntity::TYPE_WP_USERS;
91 }
92 ));
93 $subscriberSegmentTable = $this->entityManager->getClassMetadata(SubscriberSegmentEntity::class)->getTableName();
94 $segmentTable = $this->entityManager->getClassMetadata(SegmentEntity::class)->getTableName();
95 $this->entityManager->getConnection()->executeStatement("
96 UPDATE $subscriberSegmentTable ss
97 JOIN $segmentTable s ON s.`id` = ss.`segment_id` AND ss.`subscriber_id` = :subscriberId
98 SET ss.`status` = :status
99 WHERE s.`type` != :typeWordPress
100 ", [
101 'subscriberId' => $subscriber->getId(),
102 'status' => SubscriberEntity::STATUS_UNSUBSCRIBED,
103 'typeWordPress' => SegmentEntity::TYPE_WP_USERS,
104 ]);
105 // Refresh SubscriberSegments status
106 foreach ($subscriber->getSubscriberSegments() as $subscriberSegment) {
107 $this->entityManager->refresh($subscriberSegment);
108 }
109 foreach ($subscriberSegmentsToUnsubscribe as $subscriberSegment) {
110 $this->wp->doAction('mailpoet_segment_unsubscribed', $subscriberSegment);
111 }
112 }
113 $this->segmentsCountRecalculator->recalculateForSubscribers([(int)$subscriber->getId()]);
114 }
115
116 public function resetSubscriptions(SubscriberEntity $subscriber, array $segments): void {
117 // Already existing subscriptions are stored in $existingSegments. Their IDs in $existingSegmentIds.
118 $existingSegments = array_values(array_filter(array_map(
119 function(SubscriberSegmentEntity $subscriberSegmentEntity): ?SegmentEntity {
120 return $subscriberSegmentEntity->getSegment();
121 },
122 $this->findBy(['subscriber' => $subscriber, 'status' => SubscriberEntity::STATUS_SUBSCRIBED])
123 )));
124 $existingSegmentIds = array_map(
125 function(SegmentEntity $segment): int {
126 return $segment->getId() ?? 0;
127 },
128 $existingSegments
129 );
130
131 // $segmentIds are the IDs of the segments we want the user to be subscribed to.
132 $segmentIds = array_map(
133 function(SegmentEntity $segment): int {
134 return $segment->getId() ?? 0;
135 },
136 $segments
137 );
138
139 // $unsubscribedSegments are the segment IDs to which we need to unsubscribe.
140 $unsubscribedSegments = array_diff($existingSegmentIds, $segmentIds);
141
142 // $newlySubscribedSegments are the segment IDs to which we need to newly subscribe.
143 $newlySubscribedSegments = array_diff($segmentIds, $existingSegmentIds);
144 if (!$newlySubscribedSegments && !$unsubscribedSegments) {
145 return;
146 }
147
148 // The segments we need to unsubscribe.
149 $unsubscribe = array_filter(
150 $existingSegments,
151 function(SegmentEntity $segment) use ($unsubscribedSegments): bool {
152 return in_array($segment->getId(), $unsubscribedSegments);
153 }
154 );
155
156 // The segments we need to newly subscribe.
157 $subscribe = array_filter(
158 $segments,
159 function(SegmentEntity $segment) use ($newlySubscribedSegments): bool {
160 return in_array($segment->getId(), $newlySubscribedSegments);
161 }
162 );
163 if ($unsubscribe) {
164 $this->unsubscribeFromSegments($subscriber, $unsubscribe);
165 }
166 if ($subscribe) {
167 $this->subscribeToSegments($subscriber, $subscribe);
168 }
169 }
170
171 /**
172 * @param SegmentEntity[] $segments
173 */
174 public function subscribeToSegments(SubscriberEntity $subscriber, array $segments, bool $skipHooks = false): void {
175 foreach ($segments as $segment) {
176 // Defer the per-segment recompute: subscribing to N segments only changes
177 // this one subscriber's count, so recompute it once after the loop.
178 $this->createOrUpdate($subscriber, $segment, SubscriberEntity::STATUS_SUBSCRIBED, $skipHooks, true);
179 }
180 $this->segmentsCountRecalculator->recalculateForSubscribers([(int)$subscriber->getId()]);
181 }
182
183 public function createOrUpdate(
184 SubscriberEntity $subscriber,
185 SegmentEntity $segment,
186 string $status,
187 bool $skipHooks = false,
188 bool $skipSegmentsCountRecalculation = false
189 ): SubscriberSegmentEntity {
190 $subscriberSegment = $this->findOneBy(['segment' => $segment, 'subscriber' => $subscriber]);
191
192 $oldStatus = null;
193 if ($subscriberSegment instanceof SubscriberSegmentEntity) {
194 $oldStatus = $subscriberSegment->getStatus();
195 $subscriberSegment->setStatus($status);
196 } else {
197 $subscriberSegment = new SubscriberSegmentEntity($segment, $subscriber, $status);
198 $subscriber->getSubscriberSegments()->add($subscriberSegment);
199 $this->entityManager->persist($subscriberSegment);
200 }
201 $this->entityManager->flush();
202
203 // fire subscribed hook for new subscriptions
204 if (
205 !$skipHooks
206 && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
207 && $subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
208 && $oldStatus !== SubscriberEntity::STATUS_SUBSCRIBED
209 ) {
210 $this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
211 }
212 if (
213 !$skipHooks
214 && $oldStatus === SubscriberEntity::STATUS_SUBSCRIBED
215 && $subscriberSegment->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED
216 ) {
217 $this->wp->doAction('mailpoet_segment_unsubscribed', $subscriberSegment);
218 }
219
220 // segments_count only counts 'subscribed' memberships, so it can only change
221 // when the status crosses the subscribed boundary. A transition between two
222 // non-subscribed statuses (e.g. unconfirmed -> bounced) leaves it untouched.
223 $crossesSubscribedBoundary = $oldStatus !== $status
224 && ($oldStatus === SubscriberEntity::STATUS_SUBSCRIBED || $status === SubscriberEntity::STATUS_SUBSCRIBED);
225 if ($crossesSubscribedBoundary && !$skipSegmentsCountRecalculation) {
226 $this->segmentsCountRecalculator->recalculateForSubscribers([(int)$subscriber->getId()]);
227 }
228
229 return $subscriberSegment;
230 }
231 }
232