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 / ConfirmationEmailMailer.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
ConfirmationEmailMailer.php
429 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2
3 namespace MailPoet\Subscribers;
4
5 if (!defined('ABSPATH')) exit;
6
7
8 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\Html2Text;
9 use MailPoet\Cron\Workers\SendingQueue\Tasks\Shortcodes;
10 use MailPoet\Entities\NewsletterEntity;
11 use MailPoet\Entities\SegmentEntity;
12 use MailPoet\Entities\SubscriberEntity;
13 use MailPoet\Logging\LoggerFactory;
14 use MailPoet\Mailer\MailerError;
15 use MailPoet\Mailer\MailerFactory;
16 use MailPoet\Mailer\MailerLog;
17 use MailPoet\Mailer\MetaInfo;
18 use MailPoet\Newsletter\NewslettersRepository;
19 use MailPoet\Services\AuthorizedEmailsController;
20 use MailPoet\Services\Bridge;
21 use MailPoet\Settings\SettingsController;
22 use MailPoet\Subscription\SubscriptionUrlFactory;
23 use MailPoet\Util\Helpers;
24 use MailPoet\WP\Functions as WPFunctions;
25 use MailPoetVendor\Carbon\Carbon;
26
27 class ConfirmationEmailMailer {
28
29 const MAX_CONFIRMATION_EMAILS = 3;
30 const ADMIN_CONFIRMATION_RESEND_INTERVAL_DAYS = 7;
31 protected const WC_CONFIRMATION_UNAVAILABLE = 'unavailable';
32 protected const WC_CONFIRMATION_SENT = 'sent';
33 protected const WC_CONFIRMATION_FAILED = 'failed';
34
35 /** @var MailerFactory */
36 private $mailerFactory;
37
38 /** @var WPFunctions */
39 private $wp;
40
41 /** @var SettingsController */
42 private $settings;
43
44 /** @var MetaInfo */
45 private $mailerMetaInfo;
46
47 /** @var SubscribersRepository */
48 private $subscribersRepository;
49
50 /** @var SubscriptionUrlFactory */
51 private $subscriptionUrlFactory;
52
53 /** @var ConfirmationEmailCustomizer */
54 private $confirmationEmailCustomizer;
55
56 /** @var NewslettersRepository */
57 private $newslettersRepository;
58
59 /** @var LoggerFactory */
60 private $loggerFactory;
61
62 /** @var array Cache for confirmation emails sent within a request */
63 private $sentEmails = [];
64
65 public function __construct(
66 MailerFactory $mailerFactory,
67 SettingsController $settings,
68 SubscribersRepository $subscribersRepository,
69 SubscriptionUrlFactory $subscriptionUrlFactory,
70 ConfirmationEmailCustomizer $confirmationEmailCustomizer,
71 NewslettersRepository $newslettersRepository,
72 ?WPFunctions $wp = null
73 ) {
74 $this->mailerFactory = $mailerFactory;
75 $this->wp = $wp ?? new WPFunctions();
76 $this->settings = $settings;
77 $this->mailerMetaInfo = new MetaInfo;
78 $this->subscriptionUrlFactory = $subscriptionUrlFactory;
79 $this->subscribersRepository = $subscribersRepository;
80 $this->confirmationEmailCustomizer = $confirmationEmailCustomizer;
81 $this->newslettersRepository = $newslettersRepository;
82 $this->loggerFactory = LoggerFactory::getInstance();
83 }
84
85 /**
86 * Use this method if you want to make sure the confirmation email
87 * is not sent multiple times within a single request
88 * e.g. if sending confirmation emails from hooks
89 * @param SubscriberEntity $subscriber The subscriber to send the confirmation email to.
90 * @param int|null $confirmationEmailId Optional ID of a specific confirmation email newsletter to use.
91 * @param int|null $confirmationPageId Optional ID of a specific page to use for the confirmation link.
92 * @throws \Exception if unable to send the email.
93 */
94 public function sendConfirmationEmailOnce(SubscriberEntity $subscriber, ?int $confirmationEmailId = null, ?int $confirmationPageId = null, bool $isPublicFormSend = false): bool {
95 if (isset($this->sentEmails[$subscriber->getId()])) {
96 return true;
97 }
98 return $this->sendConfirmationEmail($subscriber, $confirmationEmailId, $confirmationPageId, $isPublicFormSend);
99 }
100
101 public function clearSentEmailsCache(): void {
102 $this->sentEmails = [];
103 }
104
105 /**
106 * Send confirmation email using WooCommerce email system.
107 *
108 * @return string
109 */
110 protected function sendWCConfirmationEmail(SubscriberEntity $subscriber, ?int $confirmationPageId = null): string {
111 try {
112 if (!function_exists('WC')) {
113 return self::WC_CONFIRMATION_UNAVAILABLE;
114 }
115
116 $wc = WC();
117 if (!$wc || !method_exists($wc, 'mailer')) {
118 return self::WC_CONFIRMATION_UNAVAILABLE;
119 }
120
121 $mailer = $wc->mailer();
122 $emails = $mailer->get_emails();
123
124 if (!isset($emails['mailpoet_marketing_confirmation'])) {
125 return self::WC_CONFIRMATION_UNAVAILABLE;
126 }
127
128 /** @var \MailPoet\WooCommerce\Emails\MarketingConfirmation $email */
129 $email = $emails['mailpoet_marketing_confirmation'];
130
131 $subscriber_email = $subscriber->getEmail();
132 $activation_link = $this->subscriptionUrlFactory->getConfirmationUrl($subscriber, $confirmationPageId);
133 $subscriber_firstname = $subscriber->getFirstName() ?: '';
134
135 if (!$email->trigger($subscriber_email, $activation_link, $subscriber_firstname)) {
136 return self::WC_CONFIRMATION_FAILED;
137 }
138
139 return self::WC_CONFIRMATION_SENT;
140
141 } catch (\Exception $e) {
142 $this->loggerFactory->getLogger(LoggerFactory::TOPIC_SENDING)->error(
143 'MailPoet WC Marketing Confirmation Email Error: ' . $e->getMessage(),
144 ['error' => $e, 'subscriber_id' => $subscriber->getId()]
145 );
146 return self::WC_CONFIRMATION_FAILED;
147 }
148 }
149
150 public function buildEmailData(string $subject, string $html, string $text): array {
151 return [
152 'subject' => $subject,
153 'body' => [
154 'html' => $html,
155 'text' => $text,
156 ],
157 ];
158 }
159
160 public function getMailBody(array $signupConfirmation, SubscriberEntity $subscriber, array $segmentNames, ?int $confirmationPageId = null): array {
161 $body = nl2br($signupConfirmation['body']);
162
163 // replace list of segments shortcode
164 $body = str_replace(
165 '[lists_to_confirm]',
166 '<strong>' . join(', ', $segmentNames) . '</strong>',
167 $body
168 );
169
170 // replace activation link
171 $body = Helpers::replaceLinkTags(
172 $body,
173 $this->subscriptionUrlFactory->getConfirmationUrl($subscriber, $confirmationPageId),
174 ['target' => '_blank'],
175 'activation_link'
176 );
177
178 $subject = Shortcodes::process($signupConfirmation['subject'], null, null, $subscriber, null);
179
180 $body = Shortcodes::process($body, null, null, $subscriber, null);
181
182 //create a text version. @ is important here, Html2Text throws warnings
183 $text = @Html2Text::convert(
184 (mb_detect_encoding($body, 'UTF-8', true)) ? $body : mb_convert_encoding($body, 'UTF-8', mb_list_encodings()),
185 true
186 );
187
188 return $this->buildEmailData($subject, $body, $text);
189 }
190
191 public function getMailBodyWithCustomizer(SubscriberEntity $subscriber, array $segmentNames, ?NewsletterEntity $newsletter = null, ?int $confirmationPageId = null): array {
192 if ($newsletter === null) {
193 $newsletter = $this->confirmationEmailCustomizer->getNewsletter();
194 }
195
196 $renderedNewsletter = $this->confirmationEmailCustomizer->render($newsletter);
197
198 $stringBody = Helpers::joinObject($renderedNewsletter);
199
200 // replace list of segments shortcode
201 $body = (string)str_replace(
202 '[lists_to_confirm]',
203 join(', ', $segmentNames),
204 $stringBody
205 );
206
207 // replace activation link
208 $body = (string)str_replace(
209 [
210 'http://[activation_link]', // See MAILPOET-5253
211 '[activation_link]',
212 ],
213 $this->subscriptionUrlFactory->getConfirmationUrl($subscriber, $confirmationPageId),
214 $body
215 );
216
217 [
218 $html,
219 $text,
220 $subject,
221 ] = Helpers::splitObject(Shortcodes::process($body, null, $newsletter, $subscriber, null));
222
223 // Fallback to newsletter subject if extracted subject is empty
224 if (empty($subject)) {
225 $subject = $newsletter->getSubject();
226 }
227 // Final fallback to default subject if still empty
228 if (empty($subject)) {
229 $subject = $this->settings->get('signup_confirmation.subject', __('Confirm your subscription', 'mailpoet'));
230 }
231
232 return $this->buildEmailData($subject, $html, $text);
233 }
234
235 /**
236 * @param SubscriberEntity $subscriber The subscriber to send the confirmation email to.
237 * @param int|null $confirmationEmailId Optional ID of a specific confirmation email newsletter to use.
238 * @param int|null $confirmationPageId Optional ID of a specific page to use for the confirmation link.
239 * @throws \Exception if unable to send the email.
240 */
241 public function sendConfirmationEmail(SubscriberEntity $subscriber, ?int $confirmationEmailId = null, ?int $confirmationPageId = null, bool $isPublicFormSend = false) {
242 $signupConfirmation = $this->settings->get('signup_confirmation');
243 if ((bool)$signupConfirmation['enabled'] === false) {
244 return false;
245 }
246
247 if ($isPublicFormSend) {
248 return $this->subscribersRepository->sendPublicConfirmationEmailWithCap(
249 $subscriber,
250 self::MAX_CONFIRMATION_EMAILS,
251 function() use ($subscriber, $signupConfirmation, $confirmationEmailId, $confirmationPageId): bool {
252 $sent = $this->sendConfirmationEmailMessage($subscriber, $signupConfirmation, $confirmationEmailId, $confirmationPageId);
253 if ($sent) {
254 $this->sentEmails[$subscriber->getId()] = true;
255 }
256 return $sent;
257 }
258 );
259 }
260
261 if (
262 !$this->wp->isUserLoggedIn()
263 && $subscriber->getConfirmationsCount() >= self::MAX_CONFIRMATION_EMAILS
264 ) {
265 return false;
266 }
267
268 $sent = $this->sendConfirmationEmailMessage($subscriber, $signupConfirmation, $confirmationEmailId, $confirmationPageId);
269 if (!$sent) {
270 return false;
271 }
272
273 $this->recordSuccessfulConfirmationSend($subscriber);
274 $this->sentEmails[$subscriber->getId()] = true;
275
276 return true;
277 }
278
279 /**
280 * @return array{status: 'sent'|'skipped'|'send_failed', reason?: string}
281 * @throws \Exception if unable to send the email.
282 */
283 public function sendAdminConfirmationEmail(SubscriberEntity $subscriber, ?\DateTimeInterface $oldestLifecycleDate = null): array {
284 $signupConfirmation = $this->settings->get('signup_confirmation');
285 if ((bool)$signupConfirmation['enabled'] === false) {
286 return ['status' => 'skipped', 'reason' => 'confirmation_disabled'];
287 }
288
289 $claim = $this->subscribersRepository->claimAdminConfirmationEmailResend(
290 $subscriber,
291 self::MAX_CONFIRMATION_EMAILS,
292 Carbon::now()->subDays(self::ADMIN_CONFIRMATION_RESEND_INTERVAL_DAYS)->millisecond(0),
293 $oldestLifecycleDate
294 );
295 if (!$claim['claimed']) {
296 return ['status' => 'skipped', 'reason' => $claim['reason'] ?? 'not_found'];
297 }
298
299 try {
300 $sent = $this->sendConfirmationEmailMessage($subscriber, $signupConfirmation);
301 } catch (\Throwable $throwable) {
302 $this->releaseAdminConfirmationEmailClaim($subscriber, $claim);
303 throw $throwable;
304 }
305
306 if (!$sent) {
307 $this->releaseAdminConfirmationEmailClaim($subscriber, $claim);
308 return ['status' => 'send_failed', 'reason' => 'sending_method'];
309 }
310
311 $this->completeAdminConfirmationEmailClaim($subscriber, $claim);
312 $this->sentEmails[$subscriber->getId()] = true;
313 return ['status' => 'sent'];
314 }
315
316 /** @param array{claim_time?: string, previous_last_confirmation_email_sent_at?: string|null, previous_count_confirmations?: int} $claim */
317 private function releaseAdminConfirmationEmailClaim(SubscriberEntity $subscriber, array $claim): void {
318 if (!isset($claim['claim_time'], $claim['previous_count_confirmations'])) {
319 return;
320 }
321 $this->subscribersRepository->releaseAdminConfirmationEmailResendClaim(
322 $subscriber,
323 (string)$claim['claim_time'],
324 $claim['previous_last_confirmation_email_sent_at'] ?? null,
325 (int)$claim['previous_count_confirmations']
326 );
327 }
328
329 /** @param array{claim_time?: string, previous_last_confirmation_email_sent_at?: string|null, previous_count_confirmations?: int} $claim */
330 private function completeAdminConfirmationEmailClaim(SubscriberEntity $subscriber, array $claim): void {
331 if (!isset($claim['claim_time'], $claim['previous_count_confirmations'])) {
332 return;
333 }
334 $this->subscribersRepository->completeAdminConfirmationEmailResendClaim(
335 $subscriber,
336 (string)$claim['claim_time'],
337 $claim['previous_last_confirmation_email_sent_at'] ?? null,
338 (int)$claim['previous_count_confirmations']
339 );
340 }
341
342 /**
343 * @throws \Exception if unable to send the email.
344 */
345 private function sendConfirmationEmailMessage(SubscriberEntity $subscriber, array $signupConfirmation, ?int $confirmationEmailId = null, ?int $confirmationPageId = null): bool {
346 $authorizationEmailsValidation = $this->settings->get(AuthorizedEmailsController::AUTHORIZED_EMAIL_ADDRESSES_ERROR_SETTING);
347 $unauthorizedSenderEmail = isset($authorizationEmailsValidation['invalid_sender_address']);
348 if (Bridge::isMPSendingServiceEnabled() && $unauthorizedSenderEmail) {
349 return false;
350 }
351
352 // Try to send using WooCommerce email first. Only available in Garden environment.
353 // Skip WC path when a per-list confirmation email is set, since WC doesn't support custom templates.
354 if ($confirmationEmailId === null) {
355 $wcConfirmationEmailResult = $this->sendWCConfirmationEmail($subscriber, $confirmationPageId);
356 if ($wcConfirmationEmailResult === self::WC_CONFIRMATION_SENT) {
357 return true;
358 }
359 }
360
361 $segments = $subscriber->getSegments()->toArray();
362 $segmentNames = array_map(function(SegmentEntity $segment) {
363 return $segment->getName();
364 }, $segments);
365
366 $email = $this->getConfirmationEmailBody($signupConfirmation, $subscriber, $segmentNames, $confirmationEmailId, $confirmationPageId);
367
368 // send email
369 $extraParams = [
370 'meta' => $this->mailerMetaInfo->getConfirmationMetaInfo($subscriber),
371 ];
372
373 // Don't attempt to send confirmation email when sending is paused
374 $confirmationEmailErrorMessage = __('There was an error when sending a confirmation email for your subscription. Please contact the website owner.', 'mailpoet');
375 if (MailerLog::isSendingPaused()) {
376 throw new \Exception($confirmationEmailErrorMessage);
377 }
378
379 try {
380 $defaultMailer = $this->mailerFactory->getDefaultMailer();
381 $result = $defaultMailer->send($email, $subscriber, $extraParams);
382 } catch (\Exception $e) {
383 MailerLog::processTransactionalEmailError(MailerError::OPERATION_CONNECT, $e->getMessage(), $e->getCode());
384 throw new \Exception($confirmationEmailErrorMessage);
385 }
386
387 if ($result['response'] === false) {
388 if ($result['error'] instanceof MailerError && $result['error']->getLevel() === MailerError::LEVEL_HARD) {
389 MailerLog::processTransactionalEmailError($result['error']->getOperation(), (string)$result['error']->getMessage());
390 }
391 throw new \Exception($confirmationEmailErrorMessage);
392 };
393
394 // E-mail was successfully sent we need to update the MailerLog
395 MailerLog::incrementSentCount();
396
397 return true;
398 }
399
400 private function recordSuccessfulConfirmationSend(SubscriberEntity $subscriber): void {
401 if ($this->wp->isUserLoggedIn()) {
402 return;
403 }
404 $subscriber->setConfirmationsCount($subscriber->getConfirmationsCount() + 1);
405 $this->subscribersRepository->persist($subscriber);
406 $this->subscribersRepository->flush();
407 }
408
409 /**
410 * Determines which confirmation email body to use based on settings and optional override.
411 */
412 private function getConfirmationEmailBody(array $signupConfirmation, SubscriberEntity $subscriber, array $segmentNames, ?int $confirmationEmailId = null, ?int $confirmationPageId = null): array {
413 // If a specific confirmation email ID is provided, try to use it
414 if ($confirmationEmailId !== null) {
415 $newsletter = $this->newslettersRepository->findOneById($confirmationEmailId);
416 if ($newsletter !== null && $newsletter->getType() === NewsletterEntity::TYPE_CONFIRMATION_EMAIL_CUSTOMIZER) {
417 return $this->getMailBodyWithCustomizer($subscriber, $segmentNames, $newsletter, $confirmationPageId);
418 }
419 }
420
421 // Fall back to global settings
422 $IsConfirmationEmailCustomizerEnabled = (bool)$this->settings->get(ConfirmationEmailCustomizer::SETTING_ENABLE_EMAIL_CUSTOMIZER, false);
423
424 return $IsConfirmationEmailCustomizerEnabled ?
425 $this->getMailBodyWithCustomizer($subscriber, $segmentNames, null, $confirmationPageId) :
426 $this->getMailBody($signupConfirmation, $subscriber, $segmentNames, $confirmationPageId);
427 }
428 }
429