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 / Subscription / Manage.php
mailpoet / lib / Subscription Last commit date
AdminUserSubscription.php 2 months ago Blacklist.php 1 year ago Comment.php 2 months ago Form.php 2 weeks ago Manage.php 5 days ago ManageSubscriptionFormRenderer.php 5 days ago Pages.php 5 days ago Registration.php 2 months ago SubscriptionUrlFactory.php 5 days ago Throttling.php 2 months ago index.php 3 years ago
Manage.php
513 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Subscription;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use MailPoet\CustomFields\CustomFieldsRepository;
9 use MailPoet\Entities\SegmentEntity;
10 use MailPoet\Entities\StatisticsUnsubscribeEntity;
11 use MailPoet\Entities\SubscriberEntity;
12 use MailPoet\Entities\SubscriberSegmentEntity;
13 use MailPoet\Form\Util\FieldNameObfuscator;
14 use MailPoet\Newsletter\Scheduler\WelcomeScheduler;
15 use MailPoet\Segments\SegmentsRepository;
16 use MailPoet\Statistics\Track\Unsubscribes;
17 use MailPoet\Subscribers\LinkTokens;
18 use MailPoet\Subscribers\NewSubscriberNotificationMailer;
19 use MailPoet\Subscribers\SubscriberSaveController;
20 use MailPoet\Subscribers\SubscriberSegmentRepository;
21 use MailPoet\Subscribers\SubscribersRepository;
22 use MailPoet\Util\Url as UrlHelper;
23 use MailPoetVendor\Doctrine\DBAL\ArrayParameterType;
24
25 class Manage {
26
27 /** @var UrlHelper */
28 private $urlHelper;
29
30 /** @var FieldNameObfuscator */
31 private $fieldNameObfuscator;
32
33 /** @var LinkTokens */
34 private $linkTokens;
35
36 /** @var Unsubscribes */
37 private $unsubscribesTracker;
38
39 /** @var NewSubscriberNotificationMailer */
40 private $newSubscriberNotificationMailer;
41
42 /** @var WelcomeScheduler */
43 private $welcomeScheduler;
44
45 /** @var CustomFieldsRepository */
46 private $customFieldsRepository;
47
48 /** @var SegmentsRepository */
49 private $segmentsRepository;
50
51 /** @var SubscribersRepository */
52 private $subscribersRepository;
53
54 /** @var SubscriberSegmentRepository */
55 private $subscriberSegmentRepository;
56
57 /** @var SubscriberSaveController */
58 private $subscriberSaveController;
59
60 public function __construct(
61 UrlHelper $urlHelper,
62 FieldNameObfuscator $fieldNameObfuscator,
63 LinkTokens $linkTokens,
64 Unsubscribes $unsubscribesTracker,
65 NewSubscriberNotificationMailer $newSubscriberNotificationMailer,
66 WelcomeScheduler $welcomeScheduler,
67 CustomFieldsRepository $customFieldsRepository,
68 SegmentsRepository $segmentsRepository,
69 SubscribersRepository $subscribersRepository,
70 SubscriberSegmentRepository $subscriberSegmentRepository,
71 SubscriberSaveController $subscriberSaveController
72 ) {
73 $this->urlHelper = $urlHelper;
74 $this->fieldNameObfuscator = $fieldNameObfuscator;
75 $this->unsubscribesTracker = $unsubscribesTracker;
76 $this->linkTokens = $linkTokens;
77 $this->newSubscriberNotificationMailer = $newSubscriberNotificationMailer;
78 $this->welcomeScheduler = $welcomeScheduler;
79 $this->segmentsRepository = $segmentsRepository;
80 $this->subscribersRepository = $subscribersRepository;
81 $this->subscriberSegmentRepository = $subscriberSegmentRepository;
82 $this->customFieldsRepository = $customFieldsRepository;
83 $this->subscriberSaveController = $subscriberSaveController;
84 }
85
86 public function onSave() {
87 $action = (isset($_POST['action']) && is_string($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) : '');
88 $token = (isset($_POST['token']) && is_string($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '');
89
90 if ($action !== 'mailpoet_subscription_update' || empty($_POST['data'])) {
91 $this->urlHelper->redirectBack();
92 return;
93 }
94
95 //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
96 $subscriberData = $this->fieldNameObfuscator->deobfuscateFormPayload(wp_unslash((array)$_POST['data']));
97 $subscriberData = $this->sanitizeFormValue($subscriberData);
98 if (!is_array($subscriberData)) {
99 $subscriberData = [];
100 }
101 // Never trust posted method/copy: stamp them server-side from the actual checkbox state.
102 unset($subscriberData['tracking_consent_method'], $subscriberData['tracking_consent_copy']);
103 if ($this->hasInvalidStatus($subscriberData) || $this->hasMalformedLegacySegmentIds($subscriberData)) {
104 $this->urlHelper->redirectBack(['error' => true]);
105 return;
106 }
107
108 $result = ['error' => true];
109 if (!empty($subscriberData['email'])) {
110 $subscriber = $this->subscribersRepository->findOneBy(['email' => $subscriberData['email']]);
111
112 if ($subscriber && $this->linkTokens->verifyToken($subscriber, $token)) {
113 if ($subscriberData['email'] !== Pages::DEMO_EMAIL) {
114 $previousStatus = $subscriber->getStatus();
115 $shouldTrackUnsubscribe = (
116 ($subscriberData['status'] ?? '') === SubscriberEntity::STATUS_UNSUBSCRIBED
117 && $subscriber instanceof SubscriberEntity
118 && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
119 );
120 if (array_key_exists('tracking_consent', $subscriberData)) {
121 $postedConsent = (bool)$subscriberData['tracking_consent'];
122 $currentlyGranted = $subscriber->getTrackingConsent() === SubscriberEntity::TRACKING_CONSENT_GRANTED;
123 if ($postedConsent === $currentlyGranted) {
124 // Checkbox state unchanged from what was rendered — don't rewrite consent.
125 // An untouched 'unknown' subscriber must stay 'unknown', not be flipped to
126 // 'denied' just because they saved the manage page for another reason.
127 unset($subscriberData['tracking_consent']);
128 } else {
129 $subscriberData['tracking_consent'] = $postedConsent
130 ? SubscriberEntity::TRACKING_CONSENT_GRANTED
131 : SubscriberEntity::TRACKING_CONSENT_DENIED;
132 $subscriberData['tracking_consent_method'] = SubscriberEntity::TRACKING_CONSENT_METHOD_MANAGE_PAGE;
133 $subscriberData['tracking_consent_copy'] = ManageSubscriptionFormRenderer::getTrackingConsentCopy();
134 }
135 }
136 $subscriber = $this->subscriberSaveController->createOrUpdate($this->filterToEditableFields($subscriberData), $subscriber);
137 if ($shouldTrackUnsubscribe) {
138 $this->unsubscribesTracker->track(
139 (int)$subscriber->getId(),
140 StatisticsUnsubscribeEntity::SOURCE_MANAGE
141 );
142 }
143 $this->subscriberSaveController->updateCustomFields($this->filterOutEmptyMandatoryFields($subscriberData), $subscriber);
144 $this->updateSubscriptions(
145 $subscriber,
146 $subscriberData,
147 $previousStatus !== SubscriberEntity::STATUS_SUBSCRIBED
148 && $subscriber->getStatus() === SubscriberEntity::STATUS_SUBSCRIBED
149 );
150 }
151 $result = ['success' => true];
152 }
153 }
154
155 $this->urlHelper->redirectBack($result);
156 }
157
158 private function updateSubscriptions(
159 SubscriberEntity $subscriber,
160 array $subscriberData,
161 bool $isGlobalResubscribe
162 ): void {
163 if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED) {
164 return;
165 }
166
167 if (array_key_exists('segment_choices', $subscriberData)) {
168 $this->updateSubscriptionsFromSegmentChoices(
169 $subscriber,
170 $subscriberData['segment_choices'],
171 $isGlobalResubscribe
172 );
173 return;
174 }
175
176 $segmentsIds = $this->getLegacySegmentIds($subscriberData);
177 $legacySegmentIds = $segmentsIds;
178 $segments = $this->getVisibleDefaultManageSegmentsByIds($legacySegmentIds);
179 $segmentsIds = array_map('intval', array_keys($segments));
180 if ($legacySegmentIds && !$segmentsIds) {
181 return;
182 }
183
184 // Unsubscribe from all other segments already subscribed to
185 // but don't change disallowed segments
186 foreach ($subscriber->getSubscriberSegments() as $subscriberSegment) {
187 $segment = $subscriberSegment->getSegment();
188 if (!$segment) {
189 continue;
190 }
191
192 if (!$this->isVisibleDefaultManageSegment($segment)) {
193 continue;
194 }
195 if (!in_array((int)$segment->getId(), $segmentsIds, true)) {
196 $this->subscriberSegmentRepository->createOrUpdate(
197 $subscriber,
198 $segment,
199 SubscriberEntity::STATUS_UNSUBSCRIBED,
200 false,
201 true
202 );
203 }
204 }
205
206 $currentSegmentIds = $this->getCurrentSubscribedSegmentIds($subscriber);
207 $newSegmentIds = array_diff($segmentsIds, $currentSegmentIds);
208
209 foreach ($segmentsIds as $segmentId) {
210 $this->subscriberSegmentRepository->createOrUpdate(
211 $subscriber,
212 $segments[$segmentId],
213 SubscriberEntity::STATUS_SUBSCRIBED,
214 false,
215 true
216 );
217 }
218
219 $this->subscribersRepository->recalculateSegmentsCount([(int)$subscriber->getId()]);
220
221 $this->sendNotificationsForNewSegments(
222 $subscriber,
223 $isGlobalResubscribe ? $this->getCurrentSubscribedSegmentIds($subscriber) : $newSegmentIds
224 );
225 }
226
227 /**
228 * @param mixed $segmentChoices
229 */
230 private function updateSubscriptionsFromSegmentChoices(
231 SubscriberEntity $subscriber,
232 $segmentChoices,
233 bool $isGlobalResubscribe
234 ): void {
235 $choices = $this->getSegmentChoices($segmentChoices);
236 $segments = $this->getVisibleDefaultManageSegmentsByIds(array_keys($choices));
237 $subscribeIds = [];
238 $unsubscribeIds = [];
239
240 foreach ($choices as $segmentId => $choice) {
241 if (!isset($segments[$segmentId])) {
242 continue;
243 }
244 if ($choice === 'subscribed') {
245 $subscribeIds[] = $segmentId;
246 } elseif ($choice === 'unsubscribed') {
247 $unsubscribeIds[] = $segmentId;
248 }
249 }
250
251 $currentSegmentIds = $this->getCurrentSubscribedSegmentIds($subscriber);
252
253 foreach ($unsubscribeIds as $segmentId) {
254 $this->subscriberSegmentRepository->createOrUpdate(
255 $subscriber,
256 $segments[$segmentId],
257 SubscriberEntity::STATUS_UNSUBSCRIBED,
258 false,
259 true
260 );
261 }
262
263 foreach ($subscribeIds as $segmentId) {
264 $this->subscriberSegmentRepository->createOrUpdate(
265 $subscriber,
266 $segments[$segmentId],
267 SubscriberEntity::STATUS_SUBSCRIBED,
268 false,
269 true
270 );
271 }
272
273 $this->subscribersRepository->recalculateSegmentsCount([(int)$subscriber->getId()]);
274
275 $this->sendNotificationsForNewSegments(
276 $subscriber,
277 $isGlobalResubscribe
278 ? $this->getCurrentSubscribedSegmentIds($subscriber)
279 : array_diff($subscribeIds, $currentSegmentIds)
280 );
281 }
282
283 /**
284 * The manage-subscription form only edits the subscriber's name, email,
285 * global status, and tracking consent. Subscription choices and custom
286 * fields are handled separately. Keep only those fields when saving the
287 * subscriber so any other submitted key is ignored. `status` is already
288 * validated by hasInvalidStatus(); `tracking_consent`/`_method`/`_copy`
289 * are already stamped server-side in onSave().
290 */
291 private function filterToEditableFields(array $subscriberData): array {
292 return array_intersect_key(
293 $subscriberData,
294 array_flip(['email', 'first_name', 'last_name', 'status', 'tracking_consent', 'tracking_consent_method', 'tracking_consent_copy'])
295 );
296 }
297
298 private function hasInvalidStatus(array $subscriberData): bool {
299 if (!isset($subscriberData['status'])) {
300 return false;
301 }
302 return !in_array($subscriberData['status'], [
303 SubscriberEntity::STATUS_SUBSCRIBED,
304 SubscriberEntity::STATUS_UNSUBSCRIBED,
305 ], true);
306 }
307
308 /**
309 * @param mixed $value
310 * @return mixed
311 */
312 private function sanitizeFormValue($value, ?string $parentKey = null) {
313 if (is_array($value)) {
314 $sanitized = [];
315 foreach ($value as $key => $item) {
316 $sanitizedKey = $parentKey === 'segment_choices' ? $key : sanitize_text_field((string)$key);
317 $childParentKey = $parentKey === 'segments' ? 'segments' : (string)$sanitizedKey;
318 $sanitized[$sanitizedKey] = $this->sanitizeFormValue($item, $childParentKey);
319 }
320 return $sanitized;
321 }
322 if ($parentKey === 'segments') {
323 return is_scalar($value) ? (string)$value : '';
324 }
325 return sanitize_text_field(is_scalar($value) ? (string)$value : '');
326 }
327
328 /**
329 * @return int[]
330 */
331 private function getLegacySegmentIds(array $subscriberData): array {
332 if (!isset($subscriberData['segments']) || !is_array($subscriberData['segments'])) {
333 return [];
334 }
335
336 $segmentIds = [];
337 foreach ($subscriberData['segments'] as $segmentId) {
338 $segmentId = $this->normalizePositiveIntegerId($segmentId);
339 if ($segmentId === null) {
340 continue;
341 }
342 $segmentIds[] = $segmentId;
343 }
344 return array_values(array_unique($segmentIds));
345 }
346
347 private function hasMalformedLegacySegmentIds(array $subscriberData): bool {
348 if (!isset($subscriberData['segments'])) {
349 return false;
350 }
351 if ($subscriberData['segments'] === '') {
352 return false;
353 }
354 if (!is_array($subscriberData['segments'])) {
355 return true;
356 }
357
358 foreach ($subscriberData['segments'] as $segmentId) {
359 if ($segmentId === '') {
360 continue;
361 }
362 if ($this->normalizePositiveIntegerId($segmentId) === null) {
363 return true;
364 }
365 }
366 return false;
367 }
368
369 /**
370 * @param mixed $segmentChoices
371 * @return array<int, string>
372 */
373 private function getSegmentChoices($segmentChoices): array {
374 if (!is_array($segmentChoices)) {
375 return [];
376 }
377
378 $choices = [];
379 foreach ($segmentChoices as $segmentId => $choice) {
380 $segmentId = $this->normalizePositiveIntegerId($segmentId);
381 if ($segmentId === null || !is_string($choice)) {
382 continue;
383 }
384 if (!in_array($choice, ['subscribed', 'unsubscribed'], true)) {
385 continue;
386 }
387 $choices[$segmentId] = $choice;
388 }
389 return $choices;
390 }
391
392 /**
393 * @param mixed $segmentId
394 */
395 private function normalizePositiveIntegerId($segmentId): ?int {
396 if (is_int($segmentId)) {
397 return $segmentId > 0 ? $segmentId : null;
398 }
399 if (!is_string($segmentId) || $segmentId === '' || $segmentId[0] === '0' || !ctype_digit($segmentId)) {
400 return null;
401 }
402 $normalized = (int)$segmentId;
403 if ((string)$normalized !== $segmentId || $normalized <= 0) {
404 return null;
405 }
406 return $normalized;
407 }
408
409 /**
410 * @param int[] $segmentIds
411 * @return array<int, SegmentEntity>
412 */
413 private function getVisibleDefaultManageSegmentsByIds(array $segmentIds): array {
414 $segmentIds = array_values(array_unique(array_filter(array_map('intval', $segmentIds))));
415 if (!$segmentIds) {
416 return [];
417 }
418
419 $segments = $this->segmentsRepository->createQueryBuilder('s')
420 ->where('s.id IN (:ids)')
421 ->andWhere('s.type = :type')
422 ->andWhere('s.deletedAt IS NULL')
423 ->andWhere('s.displayInManageSubscriptionPage = :displayInManageSubscriptionPage')
424 ->setParameter('ids', $segmentIds, ArrayParameterType::INTEGER)
425 ->setParameter('type', SegmentEntity::TYPE_DEFAULT)
426 ->setParameter('displayInManageSubscriptionPage', true)
427 ->getQuery()
428 ->getResult();
429
430 $segmentsMap = [];
431 foreach ($segments as $segment) {
432 if ($segment instanceof SegmentEntity && $segment->getId()) {
433 $segmentsMap[(int)$segment->getId()] = $segment;
434 }
435 }
436 return $segmentsMap;
437 }
438
439 private function isVisibleDefaultManageSegment(SegmentEntity $segment): bool {
440 return (
441 $segment->getType() === SegmentEntity::TYPE_DEFAULT
442 && $segment->getDeletedAt() === null
443 && $segment->getDisplayInManageSubscriptionPage()
444 );
445 }
446
447 /**
448 * @return int[]
449 */
450 private function getCurrentSubscribedSegmentIds(SubscriberEntity $subscriber): array {
451 $subscriberSegments = $this->subscriberSegmentRepository->findBy([
452 'status' => SubscriberEntity::STATUS_SUBSCRIBED,
453 'subscriber' => $subscriber,
454 ]);
455 return array_values(array_filter(array_map(function (SubscriberSegmentEntity $subscriberSegment): ?int {
456 $segment = $subscriberSegment->getSegment();
457 return $segment ? (int)$segment->getId() : null;
458 }, $subscriberSegments)));
459 }
460
461 /**
462 * @param int[] $newSegmentIds
463 */
464 private function sendNotificationsForNewSegments(SubscriberEntity $subscriber, array $newSegmentIds): void {
465 $newSegmentIds = array_values(array_unique(array_map('intval', $newSegmentIds)));
466 if ($subscriber->getStatus() !== SubscriberEntity::STATUS_SUBSCRIBED || !$newSegmentIds) {
467 return;
468 }
469
470 $newSegments = $this->segmentsRepository->findByIds($newSegmentIds);
471 $this->newSubscriberNotificationMailer->send($subscriber, $newSegments);
472 $this->welcomeScheduler->scheduleSubscriberWelcomeNotification(
473 $subscriber->getId(),
474 $newSegmentIds
475 );
476 }
477
478 private function filterOutEmptyMandatoryFields(array $subscriberData): array {
479 $mandatory = $this->getMandatory();
480 foreach ($mandatory as $name) {
481 if (!isset($subscriberData[$name])) {
482 continue;
483 }
484 if (is_array($subscriberData[$name]) && count(array_filter($subscriberData[$name])) === 0) {
485 unset($subscriberData[$name]);
486 }
487 if (is_string($subscriberData[$name]) && strlen(trim($subscriberData[$name])) === 0) {
488 unset($subscriberData[$name]);
489 }
490 }
491 return $subscriberData;
492 }
493
494 /**
495 * @return string[]
496 */
497 private function getMandatory(): array {
498 $mandatory = [];
499 $requiredCustomFields = $this->customFieldsRepository->findAllActive();
500 foreach ($requiredCustomFields as $customField) {
501 $params = $customField->getParams();
502 if (
503 is_array($params)
504 && isset($params['required'])
505 && $params['required']
506 ) {
507 $mandatory[] = 'cf_' . $customField->getId();
508 }
509 }
510 return $mandatory;
511 }
512 }
513