PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.33.1
MailPoet – Newsletters, Email Marketing, and Automation v5.33.1
5.37.0 5.36.1 5.36.0 5.35.1 5.35.0 5.34.3 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 / SubscriberSaveController.php
mailpoet / lib / Subscribers Last commit date
ConfirmationEmailTemplate 2 months ago ImportExport 1 month ago RestApi 1 month ago Statistics 2 months ago BulkActionController.php 3 months ago BulkActionException.php 3 months ago BulkConfirmationEmailResender.php 3 months ago ConfirmationEmailCustomizer.php 3 months ago ConfirmationEmailMailer.php 3 months ago ConfirmationEmailResolver.php 3 months ago EngagementDataBackfiller.php 3 months ago InactiveSubscribersController.php 1 month ago LinkTokens.php 3 months ago NewSubscriberNotificationMailer.php 3 months ago RequiredCustomFieldValidator.php 3 months ago SegmentsCountRecalculator.php 1 month ago Source.php 3 months ago SubscriberActions.php 3 months ago SubscriberCustomFieldRepository.php 3 years ago SubscriberIPsRepository.php 2 years ago SubscriberLimitNotificationEvaluator.php 3 months ago SubscriberLimitNotificationMailer.php 3 months ago SubscriberLimitNotificationScheduler.php 3 months ago SubscriberListingRepository.php 1 month ago SubscriberPersonalDataEraser.php 3 months ago SubscriberSaveController.php 3 months ago SubscriberSegmentRepository.php 1 month ago SubscriberSubscribeController.php 1 month ago SubscriberTagRepository.php 4 years ago SubscribersCountsController.php 1 month ago SubscribersEmailCountsController.php 1 month ago SubscribersRepository.php 1 month ago index.php 3 years ago
SubscriberSaveController.php
380 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\ConflictException;
9 use MailPoet\CustomFields\CustomFieldsRepository;
10 use MailPoet\Doctrine\Validator\ValidationException;
11 use MailPoet\Entities\StatisticsUnsubscribeEntity;
12 use MailPoet\Entities\SubscriberEntity;
13 use MailPoet\Entities\SubscriberSegmentEntity;
14 use MailPoet\Entities\SubscriberTagEntity;
15 use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
16 use MailPoet\Segments\SegmentsRepository;
17 use MailPoet\Settings\SettingsController;
18 use MailPoet\Statistics\Track\Unsubscribes;
19 use MailPoet\Tags\TagRepository;
20 use MailPoet\Util\Security;
21 use MailPoet\WP\Functions as WPFunctions;
22 use MailPoetVendor\Carbon\Carbon;
23
24 class SubscriberSaveController {
25 /** @var CustomFieldsRepository */
26 private $customFieldsRepository;
27
28 /** @var Security */
29 private $security;
30
31 /** @var SettingsController */
32 private $settings;
33
34 /** @var SegmentsRepository */
35 private $segmentsRepository;
36
37 /** @var SubscriberCustomFieldRepository */
38 private $subscriberCustomFieldRepository;
39
40 /** @var SubscribersRepository */
41 private $subscribersRepository;
42
43 /** @var SubscriberSegmentRepository */
44 private $subscriberSegmentRepository;
45
46 /** @var SubscriberTagRepository */
47 private $subscriberTagRepository;
48
49 /** @var TagRepository */
50 private $tagRepository;
51
52 /** @var Unsubscribes */
53 private $unsubscribesTracker;
54
55 /** @var WelcomeScheduler */
56 private $welcomeScheduler;
57
58 /** @var WPFunctions */
59 private $wp;
60
61 public function __construct(
62 CustomFieldsRepository $customFieldsRepository,
63 Security $security,
64 SettingsController $settings,
65 SegmentsRepository $segmentsRepository,
66 SubscriberCustomFieldRepository $subscriberCustomFieldRepository,
67 SubscribersRepository $subscribersRepository,
68 SubscriberSegmentRepository $subscriberSegmentRepository,
69 SubscriberTagRepository $subscriberTagRepository,
70 TagRepository $tagRepository,
71 Unsubscribes $unsubscribesTracker,
72 WelcomeScheduler $welcomeScheduler,
73 WPFunctions $wp
74 ) {
75 $this->customFieldsRepository = $customFieldsRepository;
76 $this->security = $security;
77 $this->settings = $settings;
78 $this->segmentsRepository = $segmentsRepository;
79 $this->subscriberSegmentRepository = $subscriberSegmentRepository;
80 $this->subscribersRepository = $subscribersRepository;
81 $this->subscriberCustomFieldRepository = $subscriberCustomFieldRepository;
82 $this->tagRepository = $tagRepository;
83 $this->unsubscribesTracker = $unsubscribesTracker;
84 $this->welcomeScheduler = $welcomeScheduler;
85 $this->wp = $wp;
86 $this->subscriberTagRepository = $subscriberTagRepository;
87 }
88
89 public function filterOutReservedColumns(array $subscriberData): array {
90 $reservedColumns = [
91 'id',
92 'wp_user_id',
93 'is_woocommerce_user',
94 'status',
95 'subscribed_ip',
96 'confirmed_ip',
97 'confirmed_at',
98 'created_at',
99 'updated_at',
100 'deleted_at',
101 'unconfirmed_data',
102 ];
103 return array_diff_key(
104 $subscriberData,
105 array_flip($reservedColumns)
106 );
107 }
108
109 /**
110 * @throws ConflictException
111 * @throws ValidationException
112 * @throws \Exception
113 */
114 public function save(array $data): SubscriberEntity {
115 if (!empty($data)) {
116 $data = $this->wp->stripslashesDeep($data);
117 }
118
119 if (empty($data['segments'])) {
120 $data['segments'] = [];
121 }
122 $data['segments'] = array_merge($data['segments'], $this->getNonDefaultSubscribedSegments($data));
123 $newSegments = $this->findNewSegments($data);
124
125 if (empty($data['tags'])) {
126 $data['tags'] = [];
127 }
128
129 $oldSubscriber = $this->findSubscriber($data);
130 $oldStatus = $oldSubscriber ? $oldSubscriber->getStatus() : null;
131 if (
132 $oldSubscriber instanceof SubscriberEntity
133 && isset($data['status'])
134 && ($data['status'] === SubscriberEntity::STATUS_UNSUBSCRIBED)
135 && ($oldSubscriber->getStatus() !== SubscriberEntity::STATUS_UNSUBSCRIBED)
136 ) {
137 $currentUser = $this->wp->wpGetCurrentUser();
138 $this->unsubscribesTracker->track(
139 (int)$oldSubscriber->getId(),
140 StatisticsUnsubscribeEntity::SOURCE_ADMINISTRATOR,
141 null,
142 $currentUser->display_name // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
143 );
144 }
145
146 if (isset($data['email']) && $this->isNewEmail($data['email'], $oldSubscriber)) {
147 $this->verifyEmailIsUnique($data['email']);
148 }
149
150 $subscriber = $this->createOrUpdate($data, $oldSubscriber);
151
152 $this->updateCustomFields($data, $subscriber);
153 $this->updateTags($data['tags'] ?? [], $subscriber);
154
155 $segments = isset($data['segments']) ? $this->findSegments($data['segments']) : null;
156 // check for status change
157 if (
158 $oldStatus === SubscriberEntity::STATUS_SUBSCRIBED
159 && $subscriber->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED
160 ) {
161 // make sure we unsubscribe the user from all segments
162 $this->subscriberSegmentRepository->unsubscribeFromSegments($subscriber);
163 } elseif ($segments !== null) {
164 $this->subscriberSegmentRepository->resetSubscriptions($subscriber, $segments);
165 }
166
167 if (!empty($newSegments)) {
168 $this->welcomeScheduler->scheduleSubscriberWelcomeNotification($subscriber->getId(), $newSegments);
169 }
170
171 // when global status changes to subscribed, fire subscribed hook for all subscribed segments
172 if (
173 $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
174 && $oldStatus !== null // don't trigger for new subscribers (handled in subscriber segments repository)
175 && $oldStatus !== SubscriberEntity::STATUS_SUBSCRIBED
176 ) {
177 $segments = $subscriber->getSubscriberSegments();
178 foreach ($segments as $subscriberSegment) {
179 if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
180 $this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
181 }
182 }
183 }
184
185 return $subscriber;
186 }
187
188 private function getNonDefaultSubscribedSegments(array $data): array {
189 if (!isset($data['id']) || (int)$data['id'] <= 0) {
190 return [];
191 }
192
193 $subscribedSegments = $this->subscriberSegmentRepository->getNonDefaultSubscribedSegments($data['id']);
194 return array_filter(array_map(function(SubscriberSegmentEntity $subscriberSegment): int {
195 $segment = $subscriberSegment->getSegment();
196 if (!$segment) {
197 return 0;
198 }
199 return (int)$segment->getId();
200 }, $subscribedSegments));
201 }
202
203 private function findSegments(array $segmentIds): array {
204 return $this->segmentsRepository->findByIds($segmentIds);
205 }
206
207 private function findNewSegments(array $data): array {
208 $oldSegmentIds = [];
209 if (isset($data['id']) && (int)$data['id'] > 0) {
210 $subscribersSegments = $this->subscriberSegmentRepository->findBy(['subscriber' => $data['id']]);
211 foreach ($subscribersSegments as $subscribersSegment) {
212 $segment = $subscribersSegment->getSegment();
213 if (!$segment) {
214 continue;
215 }
216 $oldSegmentIds[] = (int)$segment->getId();
217 }
218 }
219
220 return array_diff($data['segments'], $oldSegmentIds);
221 }
222
223 /**
224 * @throws ValidationException
225 */
226 public function createOrUpdate(array $data, ?SubscriberEntity $subscriber): SubscriberEntity {
227 if (!$subscriber) {
228 $subscriber = $this->createSubscriber();
229 if (!isset($data['source'])) $data['source'] = Source::ADMINISTRATOR;
230 }
231
232 if (isset($data['email'])) $subscriber->setEmail($data['email']);
233 if (isset($data['first_name'])) $subscriber->setFirstName($data['first_name']);
234 if (isset($data['last_name'])) $subscriber->setLastName($data['last_name']);
235 if (isset($data['status'])) $subscriber->setStatus($data['status']);
236 if (isset($data['source'])) $subscriber->setSource($data['source']);
237 if (isset($data['wp_user_id'])) $subscriber->setWpUserId($data['wp_user_id']);
238 if (isset($data['subscribed_ip'])) $subscriber->setSubscribedIp($data['subscribed_ip']);
239 if (isset($data['confirmed_ip'])) $subscriber->setConfirmedIp($data['confirmed_ip']);
240 if (isset($data['is_woocommerce_user'])) $subscriber->setIsWoocommerceUser((bool)$data['is_woocommerce_user']);
241 if ($this->settings->isSettingEnabled('collect_subscriber_timezones.enabled')) {
242 $timeZone = SubscriberEntity::sanitizeTimeZone($data[SubscriberEntity::TIME_ZONE_FIELD_NAME] ?? null);
243 if ($timeZone !== null) {
244 $subscriber->setTimeZone($timeZone);
245 $subscriber->setTimeZoneSource(SubscriberEntity::TIME_ZONE_SOURCE_FORM);
246 $subscriber->setTimeZoneConfidence(SubscriberEntity::TIME_ZONE_CONFIDENCE_BROWSER);
247 $subscriber->setTimeZoneUpdatedAt(Carbon::now()->millisecond(0));
248 }
249 }
250 $createdAt = isset($data['created_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['created_at']) : null;
251 if ($createdAt) $subscriber->setCreatedAt($createdAt);
252 $confirmedAt = isset($data['confirmed_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['confirmed_at']) : null;
253 if ($confirmedAt) $subscriber->setConfirmedAt($confirmedAt);
254
255 // wipe any unconfirmed data at this point
256 $subscriber->setUnconfirmedData(null);
257
258 // Validate the email (Saving group) + everything else (Default group)
259 $subscriber->setValidationGroups(['Saving', 'Default']);
260
261 try {
262 $this->subscribersRepository->persist($subscriber);
263 $this->subscribersRepository->flush();
264 } catch (ValidationException $exception) {
265 // detach invalid entity because it can block another work with doctrine
266 $this->subscribersRepository->detach($subscriber);
267 throw $exception;
268 }
269
270 return $subscriber;
271 }
272
273 private function isNewEmail(string $email, ?SubscriberEntity $subscriber): bool {
274 if ($subscriber && ($subscriber->getEmail() === $email)) return false;
275 return true;
276 }
277
278 /**
279 * @throws ConflictException
280 */
281 private function verifyEmailIsUnique(string $email): void {
282 $existingSubscriber = $this->subscribersRepository->findOneBy(['email' => $email]);
283 if ($existingSubscriber) {
284 // translators: %s is email address which already exists.
285 $exceptionMessage = sprintf(__('A subscriber with E-mail "%s" already exists.', 'mailpoet'), $email);
286 throw new ConflictException($exceptionMessage);
287 }
288 }
289
290 private function createSubscriber(): SubscriberEntity {
291 $subscriber = new SubscriberEntity();
292 $subscriber->setUnsubscribeToken($this->security->generateUnsubscribeTokenByEntity($subscriber));
293 $subscriber->setLinkToken(Security::generateHash(SubscriberEntity::LINK_TOKEN_LENGTH));
294 $subscriber->setStatus(!$this->settings->get('signup_confirmation.enabled') ? SubscriberEntity::STATUS_SUBSCRIBED : SubscriberEntity::STATUS_UNCONFIRMED);
295
296 return $subscriber;
297 }
298
299 private function findSubscriber(array &$data): ?SubscriberEntity {
300 if (isset($data['id']) && (int)$data['id'] > 0) {
301 $subscriber = $this->subscribersRepository->findOneById((int)$data['id']);
302 unset($data['id']);
303 return $subscriber;
304 }
305
306 return null;
307 }
308
309 public function updateCustomFields(array $data, SubscriberEntity $subscriber): void {
310 $customFieldsMap = [];
311 foreach ($data as $key => $value) {
312 if (strpos($key, 'cf_') === 0) {
313 $customFieldsMap[(int)substr($key, 3)] = $value;
314 }
315 }
316
317 if (empty($customFieldsMap)) {
318 return;
319 }
320
321 $customFields = $this->customFieldsRepository->findBy(['id' => array_keys($customFieldsMap), 'deletedAt' => null]);
322 foreach ($customFields as $customField) {
323 $customFieldId = $customField->getId();
324 if ($customFieldId === null || !array_key_exists($customFieldId, $customFieldsMap)) {
325 continue;
326 }
327 $this->subscriberCustomFieldRepository->createOrUpdate($subscriber, $customField, $customFieldsMap[$customFieldId]);
328 }
329 }
330
331 /**
332 * Replaces the subscriber's tags with the given list.
333 *
334 * $tags is either an array of arrays containing name, id etc. of the tag or an array of strings - the names
335 * of the tag. The tag names are used to upsert tags; any existing tag not present in the list is removed.
336 *
337 * Fires the `mailpoet_subscriber_tag_added` and `mailpoet_subscriber_tag_removed` WP actions, which is how
338 * tag automations are triggered.
339 */
340 public function updateTags(array $tags, SubscriberEntity $subscriber): void {
341 $removedTags = [];
342
343 $tags = array_map(
344 function($tag): string {
345 if (is_array($tag)) {
346 return array_key_exists('name', $tag) ? (string)$tag['name'] : '';
347 }
348 return (string)$tag;
349 },
350 $tags
351 );
352 foreach ($subscriber->getSubscriberTags() as $subscriberTag) {
353 $tag = $subscriberTag->getTag();
354 if (!$tag || !in_array($tag->getName(), $tags, true)) {
355 $subscriber->getSubscriberTags()->removeElement($subscriberTag);
356 $removedTags[] = $subscriberTag;
357 }
358 }
359
360 $newlyAddedTags = [];
361 foreach ($tags as $tagName) {
362 $tag = $this->tagRepository->createOrUpdate(['name' => $tagName]);
363 $subscriberTag = $subscriber->getSubscriberTag($tag);
364 if (!$subscriberTag) {
365 $subscriberTag = new SubscriberTagEntity($tag, $subscriber);
366 $subscriber->getSubscriberTags()->add($subscriberTag);
367 $this->subscriberTagRepository->persist($subscriberTag);
368 $newlyAddedTags[] = $subscriberTag;
369 }
370 }
371 $this->subscriberTagRepository->flush();
372 foreach ($newlyAddedTags as $subscriberTag) {
373 $this->wp->doAction('mailpoet_subscriber_tag_added', $subscriberTag);
374 }
375 foreach ($removedTags as $subscriberTag) {
376 $this->wp->doAction('mailpoet_subscriber_tag_removed', $subscriberTag);
377 }
378 }
379 }
380