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 / SubscriberSaveController.php
mailpoet / lib / Subscribers Last commit date
ConfirmationEmailTemplate 1 month ago ImportExport 2 weeks ago RestApi 4 days ago Statistics 1 month ago BulkActionController.php 4 days ago BulkActionException.php 2 months 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 5 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 4 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 4 days ago TrackingConsentController.php 5 days ago index.php 3 years ago
SubscriberSaveController.php
421 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 // Proof-of-consent fields are stamped server-side only. The manage page sets
120 // them by calling createOrUpdate() directly; this client-data path (admin/API)
121 // may change the consent state but must never forge the record of how or
122 // against what wording it was given.
123 unset($data['tracking_consent_method'], $data['tracking_consent_copy']);
124
125 if (empty($data['segments'])) {
126 $data['segments'] = [];
127 }
128 $data['segments'] = array_merge($data['segments'], $this->getNonDefaultSubscribedSegments($data));
129 $newSegments = $this->findNewSegments($data);
130
131 if (empty($data['tags'])) {
132 $data['tags'] = [];
133 }
134
135 $oldSubscriber = $this->findSubscriber($data);
136 $oldStatus = $oldSubscriber ? $oldSubscriber->getStatus() : null;
137 if (
138 $oldSubscriber instanceof SubscriberEntity
139 && isset($data['status'])
140 && ($data['status'] === SubscriberEntity::STATUS_UNSUBSCRIBED)
141 && ($oldSubscriber->getStatus() !== SubscriberEntity::STATUS_UNSUBSCRIBED)
142 ) {
143 $currentUser = $this->wp->wpGetCurrentUser();
144 $this->unsubscribesTracker->track(
145 (int)$oldSubscriber->getId(),
146 StatisticsUnsubscribeEntity::SOURCE_ADMINISTRATOR,
147 null,
148 $currentUser->display_name // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps
149 );
150 }
151
152 if (isset($data['email']) && $this->isNewEmail($data['email'], $oldSubscriber)) {
153 $this->verifyEmailIsUnique($data['email']);
154 }
155
156 $subscriber = $this->createOrUpdate($data, $oldSubscriber);
157
158 $this->updateCustomFields($data, $subscriber);
159 $this->updateTags($data['tags'] ?? [], $subscriber);
160
161 $segments = isset($data['segments']) ? $this->findSegments($data['segments']) : null;
162 // check for status change
163 if (
164 $oldStatus === SubscriberEntity::STATUS_SUBSCRIBED
165 && $subscriber->getStatus() === SubscriberEntity::STATUS_UNSUBSCRIBED
166 ) {
167 // make sure we unsubscribe the user from all segments
168 $this->subscriberSegmentRepository->unsubscribeFromSegments($subscriber);
169 } elseif ($segments !== null) {
170 $this->subscriberSegmentRepository->resetSubscriptions($subscriber, $segments);
171 }
172
173 if (!empty($newSegments)) {
174 $this->welcomeScheduler->scheduleSubscriberWelcomeNotification($subscriber->getId(), $newSegments);
175 }
176
177 // when global status changes to subscribed, fire subscribed hook for all subscribed segments
178 if (
179 $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
180 && $oldStatus !== null // don't trigger for new subscribers (handled in subscriber segments repository)
181 && $oldStatus !== SubscriberEntity::STATUS_SUBSCRIBED
182 ) {
183 $segments = $subscriber->getSubscriberSegments();
184 foreach ($segments as $subscriberSegment) {
185 if ($subscriberSegment->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED) {
186 $this->wp->doAction('mailpoet_segment_subscribed', $subscriberSegment);
187 }
188 }
189 }
190
191 return $subscriber;
192 }
193
194 private function getNonDefaultSubscribedSegments(array $data): array {
195 if (!isset($data['id']) || (int)$data['id'] <= 0) {
196 return [];
197 }
198
199 $subscribedSegments = $this->subscriberSegmentRepository->getNonDefaultSubscribedSegments($data['id']);
200 return array_filter(array_map(function(SubscriberSegmentEntity $subscriberSegment): int {
201 $segment = $subscriberSegment->getSegment();
202 if (!$segment) {
203 return 0;
204 }
205 return (int)$segment->getId();
206 }, $subscribedSegments));
207 }
208
209 private function findSegments(array $segmentIds): array {
210 return $this->segmentsRepository->findByIds($segmentIds);
211 }
212
213 private function findNewSegments(array $data): array {
214 $oldSegmentIds = [];
215 if (isset($data['id']) && (int)$data['id'] > 0) {
216 $subscribersSegments = $this->subscriberSegmentRepository->findBy(['subscriber' => $data['id']]);
217 foreach ($subscribersSegments as $subscribersSegment) {
218 $segment = $subscribersSegment->getSegment();
219 if (!$segment) {
220 continue;
221 }
222 $oldSegmentIds[] = (int)$segment->getId();
223 }
224 }
225
226 return array_diff($data['segments'], $oldSegmentIds);
227 }
228
229 /**
230 * @throws ValidationException
231 */
232 public function createOrUpdate(array $data, ?SubscriberEntity $subscriber): SubscriberEntity {
233 if (!$subscriber) {
234 $subscriber = $this->createSubscriber();
235 if (!isset($data['source'])) $data['source'] = Source::ADMINISTRATOR;
236 }
237
238 if (isset($data['email'])) $subscriber->setEmail($data['email']);
239 if (isset($data['first_name'])) $subscriber->setFirstName(sanitize_text_field($data['first_name']));
240 if (isset($data['last_name'])) $subscriber->setLastName(sanitize_text_field($data['last_name']));
241 if (isset($data['status'])) $subscriber->setStatus($data['status']);
242 if (isset($data['tracking_consent'])) {
243 $subscriber->setTrackingConsent(
244 (string)$data['tracking_consent'],
245 $data['tracking_consent_method'] ?? SubscriberEntity::TRACKING_CONSENT_METHOD_ADMIN,
246 $data['tracking_consent_copy'] ?? null
247 );
248 }
249 if (isset($data['source'])) $subscriber->setSource($data['source']);
250 if (isset($data['wp_user_id'])) $subscriber->setWpUserId($data['wp_user_id']);
251 if (isset($data['subscribed_ip'])) $subscriber->setSubscribedIp($data['subscribed_ip']);
252 if (isset($data['confirmed_ip'])) $subscriber->setConfirmedIp($data['confirmed_ip']);
253 if (isset($data['is_woocommerce_user'])) $subscriber->setIsWoocommerceUser((bool)$data['is_woocommerce_user']);
254 if ($this->settings->isSettingEnabled('collect_subscriber_timezones.enabled')) {
255 $timeZone = SubscriberEntity::sanitizeTimeZone($data[SubscriberEntity::TIME_ZONE_FIELD_NAME] ?? null);
256 if ($timeZone !== null) {
257 $subscriber->setTimeZone($timeZone);
258 $subscriber->setTimeZoneSource(SubscriberEntity::TIME_ZONE_SOURCE_FORM);
259 $subscriber->setTimeZoneConfidence(SubscriberEntity::TIME_ZONE_CONFIDENCE_BROWSER);
260 $subscriber->setTimeZoneUpdatedAt(Carbon::now()->millisecond(0));
261 }
262 }
263 if (array_key_exists('timezone', $data)) {
264 $this->updateTimeZoneManually($subscriber, $data['timezone']);
265 }
266 $createdAt = isset($data['created_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['created_at']) : null;
267 if ($createdAt) $subscriber->setCreatedAt($createdAt);
268 $confirmedAt = isset($data['confirmed_at']) ? Carbon::createFromFormat('Y-m-d H:i:s', $data['confirmed_at']) : null;
269 if ($confirmedAt) $subscriber->setConfirmedAt($confirmedAt);
270
271 // wipe any unconfirmed data at this point
272 $subscriber->setUnconfirmedData(null);
273
274 // Validate the email (Saving group) + everything else (Default group)
275 $subscriber->setValidationGroups(['Saving', 'Default']);
276
277 try {
278 $this->subscribersRepository->persist($subscriber);
279 $this->subscribersRepository->flush();
280 } catch (ValidationException $exception) {
281 // detach invalid entity because it can block another work with doctrine
282 $this->subscribersRepository->detach($subscriber);
283 throw $exception;
284 }
285
286 return $subscriber;
287 }
288
289 /**
290 * @param mixed $value
291 */
292 private function updateTimeZoneManually(SubscriberEntity $subscriber, $value): void {
293 if ($value === '' || $value === null) {
294 if ($subscriber->getTimeZone() === null) {
295 return;
296 }
297 $subscriber->setTimeZone(null);
298 $subscriber->setTimeZoneSource(null);
299 $subscriber->setTimeZoneConfidence(null);
300 $subscriber->setTimeZoneUpdatedAt(null);
301 return;
302 }
303
304 $timeZone = SubscriberEntity::sanitizeTimeZone($value);
305 if ($timeZone === null || $timeZone === $subscriber->getTimeZone()) {
306 return;
307 }
308 $subscriber->setTimeZone($timeZone);
309 $subscriber->setTimeZoneSource(SubscriberEntity::TIME_ZONE_SOURCE_MANUAL);
310 $subscriber->setTimeZoneConfidence(SubscriberEntity::TIME_ZONE_CONFIDENCE_MANUAL);
311 $subscriber->setTimeZoneUpdatedAt(Carbon::now()->millisecond(0));
312 }
313
314 private function isNewEmail(string $email, ?SubscriberEntity $subscriber): bool {
315 if ($subscriber && ($subscriber->getEmail() === $email)) return false;
316 return true;
317 }
318
319 /**
320 * @throws ConflictException
321 */
322 private function verifyEmailIsUnique(string $email): void {
323 $existingSubscriber = $this->subscribersRepository->findOneBy(['email' => $email]);
324 if ($existingSubscriber) {
325 // translators: %s is email address which already exists.
326 $exceptionMessage = sprintf(__('A subscriber with E-mail "%s" already exists.', 'mailpoet'), $email);
327 throw new ConflictException($exceptionMessage);
328 }
329 }
330
331 private function createSubscriber(): SubscriberEntity {
332 $subscriber = new SubscriberEntity();
333 $subscriber->setUnsubscribeToken($this->security->generateUnsubscribeTokenByEntity($subscriber));
334 $subscriber->setLinkToken(Security::generateHash(SubscriberEntity::LINK_TOKEN_LENGTH));
335 $subscriber->setStatus(!$this->settings->get('signup_confirmation.enabled') ? SubscriberEntity::STATUS_SUBSCRIBED : SubscriberEntity::STATUS_UNCONFIRMED);
336
337 return $subscriber;
338 }
339
340 private function findSubscriber(array &$data): ?SubscriberEntity {
341 if (isset($data['id']) && (int)$data['id'] > 0) {
342 $subscriber = $this->subscribersRepository->findOneById((int)$data['id']);
343 unset($data['id']);
344 return $subscriber;
345 }
346
347 return null;
348 }
349
350 public function updateCustomFields(array $data, SubscriberEntity $subscriber): void {
351 $customFieldsMap = [];
352 foreach ($data as $key => $value) {
353 if (strpos($key, 'cf_') === 0) {
354 $customFieldsMap[(int)substr($key, 3)] = $value;
355 }
356 }
357
358 if (empty($customFieldsMap)) {
359 return;
360 }
361
362 $customFields = $this->customFieldsRepository->findBy(['id' => array_keys($customFieldsMap), 'deletedAt' => null]);
363 foreach ($customFields as $customField) {
364 $customFieldId = $customField->getId();
365 if ($customFieldId === null || !array_key_exists($customFieldId, $customFieldsMap)) {
366 continue;
367 }
368 $this->subscriberCustomFieldRepository->createOrUpdate($subscriber, $customField, $customFieldsMap[$customFieldId]);
369 }
370 }
371
372 /**
373 * Replaces the subscriber's tags with the given list.
374 *
375 * $tags is either an array of arrays containing name, id etc. of the tag or an array of strings - the names
376 * of the tag. The tag names are used to upsert tags; any existing tag not present in the list is removed.
377 *
378 * Fires the `mailpoet_subscriber_tag_added` and `mailpoet_subscriber_tag_removed` WP actions, which is how
379 * tag automations are triggered.
380 */
381 public function updateTags(array $tags, SubscriberEntity $subscriber): void {
382 $removedTags = [];
383
384 $tags = array_map(
385 function($tag): string {
386 if (is_array($tag)) {
387 return array_key_exists('name', $tag) ? (string)$tag['name'] : '';
388 }
389 return (string)$tag;
390 },
391 $tags
392 );
393 foreach ($subscriber->getSubscriberTags() as $subscriberTag) {
394 $tag = $subscriberTag->getTag();
395 if (!$tag || !in_array($tag->getName(), $tags, true)) {
396 $subscriber->getSubscriberTags()->removeElement($subscriberTag);
397 $removedTags[] = $subscriberTag;
398 }
399 }
400
401 $newlyAddedTags = [];
402 foreach ($tags as $tagName) {
403 $tag = $this->tagRepository->createOrUpdate(['name' => $tagName]);
404 $subscriberTag = $subscriber->getSubscriberTag($tag);
405 if (!$subscriberTag) {
406 $subscriberTag = new SubscriberTagEntity($tag, $subscriber);
407 $subscriber->getSubscriberTags()->add($subscriberTag);
408 $this->subscriberTagRepository->persist($subscriberTag);
409 $newlyAddedTags[] = $subscriberTag;
410 }
411 }
412 $this->subscriberTagRepository->flush();
413 foreach ($newlyAddedTags as $subscriberTag) {
414 $this->wp->doAction('mailpoet_subscriber_tag_added', $subscriberTag);
415 }
416 foreach ($removedTags as $subscriberTag) {
417 $this->wp->doAction('mailpoet_subscriber_tag_removed', $subscriberTag);
418 }
419 }
420 }
421