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 / SubscriberSubscribeController.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
SubscriberSubscribeController.php
395 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\Captcha\BehavioralSignals;
9 use MailPoet\Captcha\CaptchaConstants;
10 use MailPoet\Captcha\CaptchaSession;
11 use MailPoet\Captcha\Validator\CaptchaValidator;
12 use MailPoet\Captcha\Validator\RecaptchaValidator;
13 use MailPoet\Captcha\Validator\TurnstileValidator;
14 use MailPoet\Captcha\Validator\ValidationError;
15 use MailPoet\Entities\FormEntity;
16 use MailPoet\Entities\SubscriberEntity;
17 use MailPoet\Entities\SubscriberTagEntity;
18 use MailPoet\Form\FormsRepository;
19 use MailPoet\Form\Util\FieldNameObfuscator;
20 use MailPoet\NotFoundException;
21 use MailPoet\Segments\SubscribersFinder;
22 use MailPoet\Settings\SettingsController;
23 use MailPoet\Statistics\StatisticsFormsRepository;
24 use MailPoet\Subscription\Throttling as SubscriptionThrottling;
25 use MailPoet\Tags\TagRepository;
26 use MailPoet\UnexpectedValueException;
27 use MailPoet\WP\Functions as WPFunctions;
28
29 class SubscriberSubscribeController {
30 /** @var FormsRepository */
31 private $formsRepository;
32
33 /** @var CaptchaSession */
34 private $captchaSession;
35
36 /** @var FieldNameObfuscator */
37 private $fieldNameObfuscator;
38
39 /** @var SettingsController */
40 private $settings;
41
42 /** @var RequiredCustomFieldValidator */
43 private $requiredCustomFieldValidator;
44
45 /** @var SubscriberActions */
46 private $subscriberActions;
47
48 /** @var WPFunctions */
49 private $wp;
50
51 /** @var SubscriptionThrottling */
52 private $throttling;
53
54 /** @var StatisticsFormsRepository */
55 private $statisticsFormsRepository;
56
57 /** @var SubscribersFinder */
58 private $subscribersFinder;
59
60 /** @var TagRepository */
61 private $tagRepository;
62
63 /** @var SubscriberTagRepository */
64 private $subscriberTagRepository;
65 /** @var CaptchaValidator */
66 private $builtInCaptchaValidator;
67
68 /** @var RecaptchaValidator */
69 private $recaptchaValidator;
70
71 /** @var TurnstileValidator */
72 private $turnstileValidator;
73
74 /** @var BehavioralSignals */
75 private $behavioralSignals;
76
77 public function __construct(
78 CaptchaSession $captchaSession,
79 SubscriberActions $subscriberActions,
80 SubscribersFinder $subscribersFinder,
81 SubscriptionThrottling $throttling,
82 FieldNameObfuscator $fieldNameObfuscator,
83 RequiredCustomFieldValidator $requiredCustomFieldValidator,
84 SettingsController $settings,
85 FormsRepository $formsRepository,
86 StatisticsFormsRepository $statisticsFormsRepository,
87 TagRepository $tagRepository,
88 SubscriberTagRepository $subscriberTagRepository,
89 WPFunctions $wp,
90 CaptchaValidator $builtInCaptchaValidator,
91 RecaptchaValidator $recaptchaValidator,
92 TurnstileValidator $turnstileValidator,
93 BehavioralSignals $behavioralSignals
94 ) {
95 $this->formsRepository = $formsRepository;
96 $this->captchaSession = $captchaSession;
97 $this->requiredCustomFieldValidator = $requiredCustomFieldValidator;
98 $this->fieldNameObfuscator = $fieldNameObfuscator;
99 $this->settings = $settings;
100 $this->subscriberActions = $subscriberActions;
101 $this->subscribersFinder = $subscribersFinder;
102 $this->wp = $wp;
103 $this->throttling = $throttling;
104 $this->statisticsFormsRepository = $statisticsFormsRepository;
105 $this->tagRepository = $tagRepository;
106 $this->subscriberTagRepository = $subscriberTagRepository;
107 $this->builtInCaptchaValidator = $builtInCaptchaValidator;
108 $this->recaptchaValidator = $recaptchaValidator;
109 $this->turnstileValidator = $turnstileValidator;
110 $this->behavioralSignals = $behavioralSignals;
111 }
112
113 public function subscribe(array $data): array {
114 $form = $this->getForm($data);
115
116 if (!empty($data['email'])) {
117 throw new UnexpectedValueException(__('Please leave the first field empty.', 'mailpoet'));
118 }
119
120 $captchaSettings = $this->settings->get('captcha');
121 $data = $this->initCaptcha($captchaSettings, $form, $data);
122 $data = $this->deobfuscateFormPayload($data);
123
124 try {
125 $this->requiredCustomFieldValidator->validate($data, $form);
126 } catch (\Exception $e) {
127 throw new UnexpectedValueException($e->getMessage());
128 }
129
130 $segmentIds = $this->getSegmentIds($form, $data['segments'] ?? []);
131
132 // Keep `segments` in $data until after CAPTCHA validation so that, if the
133 // behavioral-baseline path stashes the submission for a deferred challenge,
134 // the stash still carries the selected segments for the resubmit.
135 $meta = $this->validateCaptcha($captchaSettings, $data, $form);
136 if (isset($meta['error'])) {
137 return $meta;
138 }
139 unset($data['segments']);
140
141 $submittedTimeZone = SubscriberEntity::sanitizeTimeZone($data[SubscriberEntity::TIME_ZONE_FIELD_NAME] ?? null);
142
143 // only accept fields defined in the form
144 $formFieldIds = array_filter(array_map(function (array $formField): ?string {
145 if (!isset($formField['id'])) {
146 return null;
147 }
148 return is_numeric($formField['id']) ? "cf_{$formField['id']}" : $formField['id'];
149 }, $form->getBlocksByTypes(FormEntity::FORM_FIELD_TYPES)));
150 $data = array_intersect_key($data, array_flip($formFieldIds));
151 if ($submittedTimeZone !== null) {
152 $data[SubscriberEntity::TIME_ZONE_FIELD_NAME] = $submittedTimeZone;
153 }
154
155 // make sure we don't allow too many subscriptions with the same ip address
156 $timeout = $this->throttling->throttle();
157
158 if ($timeout > 0) {
159 $timeToWait = $this->throttling->secondsToTimeString($timeout);
160 $meta['refresh_captcha'] = true;
161 // translators: %s is the amount of time the user has to wait.
162 $meta['error'] = sprintf(__('You need to wait %s before subscribing again.', 'mailpoet'), $timeToWait);
163 return $meta;
164 }
165
166 /**
167 * Fires before a subscription gets created.
168 * To interrupt the subscription process, you can throw an MailPoet\Exception.
169 * The error message will then be displayed to the user.
170 *
171 * @param array $data The subscription data.
172 * @param array $segmentIds The segment IDs the user gets subscribed to.
173 * @param FormEntity $form The form the user used to subscribe.
174 */
175 $this->wp->doAction('mailpoet_subscription_before_subscribe', $data, $segmentIds, $form);
176
177 [$subscriber, $subscriptionMeta] = $this->subscriberActions->subscribe($data, $segmentIds);
178
179 if (
180 isset($data['captcha_session_id']) && (
181 ($captchaSettings['type'] ?? null) === CaptchaConstants::TYPE_BUILTIN
182 || CaptchaConstants::isDisabled($captchaSettings['type'] ?? null)
183 )
184 ) {
185 // Captcha has been verified, invalidate the session vars
186 $this->captchaSession->reset($data['captcha_session_id']);
187 }
188
189 // record form statistics
190 $this->statisticsFormsRepository->record($form, $subscriber);
191
192 // add tags to subscriber if they are filled
193 $formSettings = $form->getSettings();
194 $this->addTagsToSubscriber($formSettings['tags'] ?? [], $subscriber);
195
196 // Confirmation email failed. We want to show the error message
197 if ($subscriptionMeta['confirmationEmailResult'] instanceof \Exception) {
198 $meta['error'] = $subscriptionMeta['confirmationEmailResult']->getMessage();
199 return $meta;
200 }
201 if (!empty($subscriptionMeta['error'])) {
202 $meta['error'] = $subscriptionMeta['error'];
203 return $meta;
204 }
205
206 $this->wp->doAction('mailpoet_subscription_after_subscribe', $subscriber, $data, $segmentIds, $form);
207
208 if (!empty($formSettings['on_success'])) {
209 if ($formSettings['on_success'] === 'page') {
210 // redirect to a page on a success, pass the page url in the meta
211 $meta['redirect_url'] = $this->wp->getPermalink($formSettings['success_page']);
212 } else if ($formSettings['on_success'] === 'url') {
213 $meta['redirect_url'] = $formSettings['success_url'];
214 }
215 }
216
217 return $meta;
218 }
219
220 /**
221 * Checks if the subscriber is subscribed to any segments in the form
222 *
223 * @param FormEntity $form The form entity
224 * @param SubscriberEntity $subscriber The subscriber entity
225 * @return bool True if the subscriber is subscribed to any of the segments in the form
226 */
227 public function isSubscribedToAnyFormSegments(FormEntity $form, SubscriberEntity $subscriber): bool {
228 $formSegments = array_merge($form->getSegmentBlocksSegmentIds(), $form->getSettingsSegmentIds());
229
230 $subscribersFound = $this->subscribersFinder->findSubscribersInSegments([$subscriber->getId()], $formSegments);
231 if (!empty($subscribersFound)) return true;
232
233 return false;
234 }
235
236 private function deobfuscateFormPayload($data): array {
237 return $this->fieldNameObfuscator->deobfuscateFormPayload($data);
238 }
239
240 private function initCaptcha(?array $captchaSettings, FormEntity $form, array $data): array {
241 $type = $captchaSettings['type'] ?? null;
242
243 if ($type === CaptchaConstants::TYPE_BUILTIN) {
244 // When serving the built-in CAPTCHA for the first time, generate a new session ID.
245 if (!isset($data['captcha_session_id'])) {
246 $data['captcha_session_id'] = $this->captchaSession->generateSessionId();
247 }
248 $sessionId = $data['captcha_session_id'];
249
250 if (!isset($data['captcha'])) {
251 // Save form data to session
252 $this->captchaSession->setFormData($sessionId, array_merge($data, ['form_id' => $form->getId()]));
253 } elseif ($this->captchaSession->getFormData($sessionId)) {
254 // Restore form data from session, but keep the current request's captcha
255 // and behavioral signals so the resubmit reflects accumulated interaction
256 // rather than the (possibly bot-like) snapshot from the first submit.
257 $preserve = ['captcha' => $data['captcha']];
258 if (isset($data[BehavioralSignals::FIELD_NAME])) {
259 $preserve[BehavioralSignals::FIELD_NAME] = $data[BehavioralSignals::FIELD_NAME];
260 }
261 $data = array_merge($this->captchaSession->getFormData($sessionId), $preserve);
262 }
263 return $data;
264 }
265
266 // Disabled with behavioral baseline: restore stashed form data on resubmit
267 // (after a previous behavioral escalation). The first submit stashes inside
268 // the escalation path; here we only handle the restore side.
269 if (
270 CaptchaConstants::isDisabled($type)
271 && isset($data['captcha_session_id'], $data['captcha'])
272 ) {
273 $stashed = $this->captchaSession->getFormData($data['captcha_session_id']);
274 if (is_array($stashed)) {
275 // Keep the current request's behavioral signals over the stash so the
276 // resubmit's signal check reflects accumulated interaction, not the
277 // (possibly bot-like) snapshot that triggered the original challenge.
278 $preserve = [
279 'captcha' => $data['captcha'],
280 'captcha_session_id' => $data['captcha_session_id'],
281 ];
282 if (isset($data[BehavioralSignals::FIELD_NAME])) {
283 $preserve[BehavioralSignals::FIELD_NAME] = $data[BehavioralSignals::FIELD_NAME];
284 }
285 $data = array_merge($stashed, $preserve);
286 }
287 }
288
289 return $data;
290 }
291
292 private function validateCaptcha($captchaSettings, $data, FormEntity $form): array {
293 $type = $captchaSettings['type'] ?? null;
294 try {
295 if (CaptchaConstants::isDisabled($type)) {
296 $this->enforceBehavioralBaseline($data, $form);
297 return [];
298 }
299 if ($type === CaptchaConstants::TYPE_BUILTIN) {
300 $this->builtInCaptchaValidator->validate($data);
301 $this->requireHumanSignals($data, $form);
302 }
303 if (CaptchaConstants::isReCaptcha($type)) {
304 $this->recaptchaValidator->validate($data);
305 }
306 if (CaptchaConstants::isTurnstile($type)) {
307 $this->turnstileValidator->validate($data);
308 }
309 } catch (ValidationError $error) {
310 return $error->getMeta();
311 }
312 return [];
313 }
314
315 /**
316 * Baseline protection when no CAPTCHA is configured: behavioral signals must
317 * look human, otherwise escalate to the built-in CAPTCHA inline challenge.
318 * isRequired()'s IP-history heuristic is intentionally bypassed here — the
319 * decision is made on per-submission signals, not on the IP's CAPTCHA history.
320 * On resubmit (after a previous escalation), signals are re-checked so that
321 * solving the CAPTCHA alone isn't enough to bypass the baseline.
322 */
323 private function enforceBehavioralBaseline(array $data, FormEntity $form): void {
324 if (!empty($data['captcha_session_id'])) {
325 $this->builtInCaptchaValidator->validateChallenge($data);
326 }
327 $this->requireHumanSignals($data, $form);
328 }
329
330 /**
331 * Throws a fresh CAPTCHA challenge unless behavioral signals look human.
332 * Admin/editor exempt. The suspect signals are dropped from the stash so the
333 * resubmit is evaluated on the current request's freshest counters (via
334 * initCaptcha's preserve step).
335 */
336 private function requireHumanSignals(array $data, FormEntity $form): void {
337 if ($this->builtInCaptchaValidator->isUserExemptFromCaptcha()) {
338 return;
339 }
340 if ($this->behavioralSignals->looksHuman($data)) {
341 return;
342 }
343 $stash = array_merge($data, ['form_id' => $form->getId()]);
344 unset($stash[BehavioralSignals::FIELD_NAME]);
345 $challenge = $this->builtInCaptchaValidator->getInlineCaptchaChallenge($stash);
346 throw new ValidationError(__('Please fill in the CAPTCHA.', 'mailpoet'), $challenge);
347 }
348
349 private function getSegmentIds(FormEntity $form, array $segmentIds): array {
350
351 // If form contains segment selection blocks allow only segments ids configured in those blocks
352 $segmentBlocksSegmentIds = $form->getSegmentBlocksSegmentIds();
353 if (!empty($segmentBlocksSegmentIds)) {
354 $segmentIds = array_intersect($segmentIds, $segmentBlocksSegmentIds);
355 } else {
356 $segmentIds = $form->getSettingsSegmentIds();
357 }
358
359 if (empty($segmentIds)) {
360 throw new UnexpectedValueException(__('Please select a list.', 'mailpoet'));
361 }
362
363 return $segmentIds;
364 }
365
366 private function getForm(array $data): FormEntity {
367 $formId = (isset($data['form_id']) ? (int)$data['form_id'] : false);
368 $form = $this->formsRepository->findOneById($formId);
369
370 if (!$form) {
371 throw new NotFoundException(__('Please specify a valid form ID.', 'mailpoet'));
372 }
373
374 return $form;
375 }
376
377 /**
378 * @param string[] $tagNames
379 */
380 private function addTagsToSubscriber(array $tagNames, SubscriberEntity $subscriber): void {
381 foreach ($tagNames as $tagName) {
382 $tag = $this->tagRepository->createOrUpdate(['name' => $tagName]);
383
384 $subscriberTag = $subscriber->getSubscriberTag($tag);
385 if (!$subscriberTag) {
386 $subscriberTag = new SubscriberTagEntity($tag, $subscriber);
387 $subscriber->getSubscriberTags()->add($subscriberTag);
388 $this->subscriberTagRepository->persist($subscriberTag);
389 $this->subscriberTagRepository->flush();
390 $this->wp->doAction('mailpoet_subscriber_tag_added', $subscriberTag);
391 }
392 }
393 }
394 }
395