PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.1.4
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.1.4
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
acymailing / back / Helpers / MailerHelper.php

MailerHelper.php in AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress 10.1.4, at back/Helpers/MailerHelper.php

1,694 lines 60.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace AcyMailing\Helpers;
4
5 use AcyMailing\Classes\CampaignClass;
6 use AcyMailing\Classes\ConfigurationClass;
7 use AcyMailing\Classes\FollowupClass;
8 use AcyMailing\Classes\MailArchiveClass;
9 use AcyMailing\Classes\MailClass;
10 use AcyMailing\Classes\OverrideClass;
11 use AcyMailing\Classes\UrlClass;
12 use AcyMailing\Classes\UserClass;
13 use AcyMailerPhp\Exception;
14 use AcyMailerPhp\OAuth;
15 use AcyMailerPhp\Mailer;
16 use Pelago\Emogrifier\HtmlProcessor\HtmlPruner;
17
18 class MailerHelper extends Mailer
19 {
20 const NEW_TRY_ERRORS = [1, 6];
21
22 public $to = [];
23 public $cc = [];
24 public $bcc = [];
25 public $ReplyTo = [];
26 public $attachment = [];
27 public $CustomHeader = [];
28
29 public string $preheader;
30 public string $replyname;
31 public string $replyemail;
32 public string $body;
33 public string $altbody;
34 public string $subject;
35 public string $from;
36 public string $fromName;
37 public array $replyto;
38 public bool $custom_view;
39
40 private EncodingHelper $encodingHelper;
41 private EditorHelper $editorHelper;
42 private UserClass $userClass;
43 private MailClass $mailClass;
44 private MailArchiveClass $mailArchiveClass;
45 public ConfigurationClass $config;
46
47 public bool $report = true;
48 public bool $alreadyCheckedAddresses = false;
49 public int $errorNumber = 0;
50 public bool $failedCounting = true;
51
52 public bool $autoAddUser = false;
53 public bool $userCreationTriggers = true;
54
55 public string $reportMessage = '';
56 public bool $dtextsFailed = false;
57
58 public bool $trackEmail = false;
59
60 public string $externalMailer;
61
62 public string $stylesheet = '';
63 public array $settings;
64
65 public array $parameters = [];
66
67 private string $userLanguage = '';
68 private string $receiverEmail;
69
70 private object $overrideEmailToSend;
71 public bool $isTest = false;
72 public bool $isSpamTest = false;
73 public bool $isForward = false;
74
75 public int $mailId = 0;
76 public int $creator_id;
77 public string $type;
78 public string $links_language;
79 public int $id = 0;
80 public ?object $mail = null;
81 private array $defaultMail = [];
82
83 public array $listsIds = [];
84 private string $currentSendingMethod = '';
85 private array $currentMethodSetting = [];
86
87 public bool $isAbTest = false;
88 public bool $isTransactional = false;
89 public bool $isOneTimeMail = false;
90 public bool $isSendingMethodByListActive = false;
91
92 public function __construct()
93 {
94 parent::__construct();
95
96 $this->XMailer = ' ';
97 $this->SMTPAutoTLS = false;
98
99 $this->encodingHelper = new EncodingHelper();
100 $this->editorHelper = new EditorHelper();
101 $this->userClass = new UserClass();
102 $this->mailClass = new MailClass();
103 $this->mailArchiveClass = new MailArchiveClass();
104 $this->config = acym_config();
105
106 $this->setFrom($this->getSendSettings('from_email'), $this->getSendSettings('from_name'));
107 $this->Sender = $this->cleanText($this->config->get('bounce_email'));
108 if (empty($this->Sender)) {
109 $this->Sender = '';
110 }
111 $maxLineLength = $this->config->get('mailer_wordwrap', 0);
112 if (!empty($maxLineLength) && self::MAX_LINE_LENGTH > $maxLineLength) {
113 $this->WordWrap = $maxLineLength;
114 }
115
116 $this->setSendingMethodSetting();
117
118 if ($this->config->get('dkim', 0) && $this->Mailer != 'elasticemail') {
119 $this->DKIM_domain = $this->config->get('dkim_domain');
120 $this->DKIM_selector = $this->config->get('dkim_selector', 'acy');
121 if (empty($this->DKIM_selector)) $this->DKIM_selector = 'acy';
122 $this->DKIM_passphrase = $this->config->get('dkim_passphrase');
123 $this->DKIM_identity = $this->config->get('dkim_identity');
124 $this->DKIM_private = trim($this->config->get('dkim_private'));
125 $this->DKIM_private_string = trim($this->config->get('dkim_private'));
126 }
127
128 $this->CharSet = strtolower($this->config->get('charset'));
129 if (empty($this->CharSet)) {
130 $this->CharSet = 'utf-8';
131 }
132
133 $this->clearAll();
134
135 $this->Encoding = $this->config->get('encoding_format');
136 if (empty($this->Encoding)) {
137 $this->Encoding = '8bit';
138 }
139
140 @ini_set('pcre.backtrack_limit', 1000000);
141
142 $this->SMTPOptions = ['ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true]];
143
144 $this->addParamInfo();
145 }
146
147 public function setSendingMethodSetting(): void
148 {
149 $externalSendingMethod = [];
150 acym_trigger('onAcymGetSendingMethods', [&$externalSendingMethod, true]);
151 $externalSendingMethod = array_keys($externalSendingMethod['sendingMethods']);
152
153 $mailerMethodConfig = $this->getSendingMethod();
154 $this->currentSendingMethod = $mailerMethodConfig;
155
156 if ($mailerMethodConfig === 'smtp') {
157 $this->isSMTP();
158 $this->Host = trim($this->getSendingMethodSettings('smtp_host'));
159 $port = $this->getSendingMethodSettings('smtp_port');
160 if (empty($port) && $this->getSendingMethodSettings('smtp_secured') === 'ssl') {
161 $port = 465;
162 }
163 if (!empty($port)) {
164 $this->Host .= ':'.$port;
165 }
166 $this->SMTPAuth = (bool)$this->getSendingMethodSettings('smtp_auth', '1');
167 $this->Username = trim($this->getSendingMethodSettings('smtp_username'));
168 $connectionType = $this->getSendingMethodSettings('smtp_type');
169 $hostName = explode(':', $this->Host)[0];
170
171 if (OAuth::hostRequireOauth($hostName, $connectionType)) {
172 $this->AuthType = 'XOAUTH2';
173
174 $clientSecret = trim($this->getSendingMethodSettings('smtp_secret'));
175 $clientId = trim($this->getSendingMethodSettings('smtp_clientId'));
176 $refreshToken = trim($this->getSendingMethodSettings('smtp_refresh_token'));
177 $oauthToken = trim($this->getSendingMethodSettings('smtp_token'));
178 $expiredIn = trim($this->getSendingMethodSettings('smtp_token_expireIn'));
179
180 $oauth = new OAuth(
181 [
182 'userName' => $this->Username,
183 'clientSecret' => $clientSecret,
184 'oauthToken' => $oauthToken,
185 'clientId' => $clientId,
186 'refreshToken' => $refreshToken,
187 'expiredIn' => $expiredIn,
188 'host' => $hostName,
189 ]
190 );
191 $this->setOAuth($oauth);
192 } else {
193 $authMethod = $this->getSendingMethodSettings('smtp_method');
194 if (in_array($authMethod, ['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'])) {
195 $this->AuthType = $authMethod;
196 }
197 if ($this->SMTPAuth) {
198 $this->Password = trim($this->getSendingMethodSettings('smtp_password'));
199 }
200 }
201
202 $this->SMTPSecure = trim((string)$this->getSendingMethodSettings('smtp_secured'));
203
204 if (empty($this->Sender)) {
205 $this->Sender = strpos($this->Username, '@') ? $this->Username : $this->getSendingMethodSettings('from_email');
206 }
207 } elseif ($mailerMethodConfig === 'sendmail') {
208 $this->isSendmail();
209 $this->Sendmail = trim($this->getSendingMethodSettings('sendmail_path'));
210 if (empty($this->Sendmail)) {
211 $this->Sendmail = '/usr/sbin/sendmail';
212 }
213 } elseif ($mailerMethodConfig === 'qmail') {
214 $this->isQmail();
215 } elseif ($mailerMethodConfig === 'amazon') {
216 $this->isSMTP();
217 $amazonCredentials = [];
218 acym_trigger('onAcymGetCredentialsSendingMethod', [&$amazonCredentials, 'amazon', $this->currentMethodSetting], 'plgAcymAmazon');
219 $this->Host = trim($amazonCredentials['amazon_host']).':587';
220 $this->Username = trim($amazonCredentials['amazon_username']);
221 $this->Password = trim($amazonCredentials['amazon_password']);
222 $this->SMTPAuth = true;
223 $this->SMTPSecure = 'tls';
224 } elseif ($mailerMethodConfig === 'brevo-smtp') {
225 $this->isSMTP();
226 $brevoSmtpCredentials = [];
227 acym_trigger('onAcymGetCredentialsSendingMethod', [&$brevoSmtpCredentials, 'brevo-smtp', $this->currentMethodSetting], 'plgAcymBrevo');
228 $this->Host = trim($brevoSmtpCredentials['brevo-smtp_host']).':587';
229 $this->Username = trim($brevoSmtpCredentials['brevo-smtp_username']);
230 $this->Password = trim($brevoSmtpCredentials['brevo-smtp_password']);
231 $this->SMTPAuth = true;
232 $this->SMTPSecure = 'tls';
233 } elseif (in_array($mailerMethodConfig, $externalSendingMethod)) {
234 $this->isExternal($mailerMethodConfig);
235 } else {
236 $this->isMail();
237 }
238 }
239
240 protected function externalSend(string $MIMEHeader, string $MIMEBody): bool
241 {
242 $fromName = empty($this->FromName) ? $this->config->get('from_name') : $this->FromName;
243 $reply_to = array_shift($this->ReplyTo);
244
245 $attachments = [];
246 if (!empty($this->attachment) && $this->config->get('embed_files')) {
247 foreach ($this->attachment as $i => $oneAttach) {
248 $encodedContent = $this->encodeFile($oneAttach[0], $oneAttach[3]);
249 $this->attachment[$i]['contentEncoded'] = $encodedContent;
250 }
251 $attachments = $this->attachment;
252 }
253
254 $response = [];
255 $data = [
256 &$response,
257 $this,
258 ['email' => $this->to[0][0], 'name' => $this->to[0][1]],
259 ['email' => $this->From, 'name' => $fromName],
260 ['email' => $reply_to[0], 'name' => $reply_to[1]],
261 !empty($this->bcc) ? $this->bcc : [],
262 $attachments,
263 $this->currentMethodSetting,
264 ];
265 acym_trigger('onAcymSendEmail', $data);
266
267 if (!empty($response['error'])) {
268 $this->setError($response['message']);
269
270 return false;
271 }
272
273 return true;
274 }
275
276 public function send(): bool
277 {
278 if (!file_exists(ACYM_LIBRARIES.'Mailer'.DS.'Mailer.php')) {
279 $this->reportMessage = acym_translationSprintf('ACYM_X_FILE_MISSING', 'Mailer', ACYM_LIBRARIES.'Mailer'.DS);
280 if ($this->report) {
281 acym_enqueueMessage($this->reportMessage, 'error');
282 }
283
284 return false;
285 }
286
287 if (empty($this->Subject) || empty($this->Body)) {
288 if ($this->isTest && empty($this->Subject)) {
289 $this->Subject = acym_translation('ACYM_EMAIL_SUBJECT');
290 } else {
291 $this->reportMessage = acym_translation('ACYM_SEND_EMPTY');
292 $this->errorNumber = 8;
293 if ($this->report) {
294 acym_enqueueMessage($this->reportMessage, 'error');
295 }
296
297 return false;
298 }
299 }
300
301 if (empty($this->ReplyTo) && empty($this->ReplyToQueue)) {
302 if (!empty($this->replyemail)) {
303 $replyToEmail = $this->replyemail;
304 } elseif ($this->config->get('from_as_replyto', 1) == 1) {
305 $replyToEmail = $this->getSendSettings('from_email');
306 } else {
307 $replyToEmail = $this->getSendSettings('replyto_email');
308 }
309
310 if (!empty($this->replyname)) {
311 $replyToName = $this->replyname;
312 } elseif ($this->config->get('from_as_replyto', 1) == 1) {
313 $replyToName = $this->getSendSettings('from_name');
314 } else {
315 $replyToName = $this->getSendSettings('replyto_name');
316 }
317
318 $this->acymAddReplyTo($replyToEmail, $replyToName);
319 }
320
321 $shouldEmbed = $this->config->get('embed_images', 0);
322 if (intval($shouldEmbed) === 1 && $this->Mailer !== 'elasticemail') {
323 $this->embedImages();
324 }
325
326 if (!$this->alreadyCheckedAddresses) {
327 $this->alreadyCheckedAddresses = true;
328
329 $replyToTmp = '';
330 if (!empty($this->ReplyTo)) {
331 $replyToTmp = reset($this->ReplyTo);
332 $replyToTmp = $replyToTmp[0];
333 } elseif (!empty($this->ReplyToQueue)) {
334 $replyToTmp = reset($this->ReplyToQueue);
335 $replyToTmp = $replyToTmp[1];
336 }
337
338 if (empty($replyToTmp) || !acym_isValidEmail($replyToTmp)) {
339 $this->reportMessage = acym_translation('ACYM_VALID_EMAIL').' ( '.acym_translation('ACYM_REPLYTO_EMAIL').' : '.(empty($this->ReplyTo) ? '' : $replyToTmp).' ) ';
340 $this->errorNumber = 9;
341 if ($this->report) {
342 acym_enqueueMessage($this->reportMessage, 'error');
343 }
344
345 return false;
346 }
347
348 if (empty($this->From) || !acym_isValidEmail($this->From)) {
349 $this->reportMessage = acym_translation('ACYM_VALID_EMAIL').' ( '.acym_translation('ACYM_FROM_EMAIL').' : '.$this->From.' ) ';
350 $this->errorNumber = 9;
351 if ($this->report) {
352 acym_enqueueMessage($this->reportMessage, 'error');
353 }
354
355 return false;
356 }
357
358 if (!empty($this->Sender) && !acym_isValidEmail($this->Sender)) {
359 $this->reportMessage = acym_translation('ACYM_VALID_EMAIL').' ( '.acym_translation('ACYM_BOUNCE_EMAIL').' : '.$this->Sender.' ) ';
360 $this->errorNumber = 9;
361 if ($this->report) {
362 acym_enqueueMessage($this->reportMessage, 'error');
363 }
364
365 return false;
366 }
367 }
368
369 if ($this->config->get('save_body') === '1') {
370 @file_put_contents(ACYM_ROOT.'acydebug_mail.html', $this->Body);
371 }
372
373 $this->Body = htmlentities($this->Body);
374 $this->Body = htmlspecialchars_decode($this->Body);
375 $this->Body = str_replace(['&amp;', '&sigmaf;'], ['&', 'ς'], $this->Body);
376
377 if ($this->CharSet !== 'utf-8') {
378 $this->Body = $this->encodingHelper->change($this->Body, 'UTF-8', $this->CharSet);
379 $this->Subject = $this->encodingHelper->change($this->Subject, 'UTF-8', $this->CharSet);
380 }
381
382 $this->Subject = str_replace(
383 ['’', '“', '”', '–'],
384 ["'", '"', '"', '-'],
385 $this->Subject
386 );
387
388 $this->Body = str_replace(" ", ' ', $this->Body);
389
390 $externalSending = false;
391
392 $this->isOneTimeMail = $this->isTransactional = $this->isForward || $this->isTest || $this->isSpamTest;
393 if (!empty($this->mailId) && !empty($this->defaultMail[$this->mailId])) {
394 if ($this->mailClass->isTransactionalMail($this->defaultMail[$this->mailId])) {
395 $this->isTransactional = true;
396 }
397
398 if ($this->mailClass->isOneTimeMail($this->defaultMail[$this->mailId])) {
399 $this->isOneTimeMail = true;
400 }
401 }
402
403 acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$externalSending, $this->isTransactional]);
404
405 $warnings = '';
406
407 if (ACYM_PRODUCTION) {
408 if ($externalSending) {
409 $result = false;
410 acym_trigger('onAcymRegisterReceiverContentAndList', [&$result, $this->Subject, $this->Body, $this->receiverEmail, $this->mailId, &$warnings]);
411 } else {
412 acym_trigger('onAcymBeforeEmailSend', [&$this]);
413 ob_start();
414 $result = parent::send();
415 $warnings = ob_get_clean();
416 }
417 }
418
419
420 if (!empty($warnings) && strpos($warnings, 'bloque')) {
421 $result = false;
422 }
423
424 $this->mailHeader = '';
425
426 $receivers = [];
427 foreach ($this->to as $oneReceiver) {
428 $receivers[] = $oneReceiver[0];
429 }
430
431 if (!$result) {
432 $this->reportMessage = acym_translationSprintf('ACYM_SEND_ERROR', '<b>'.$this->Subject.'</b>', '<b>'.implode(' , ', $receivers).'</b>');
433 if (!empty($this->ErrorInfo)) {
434 $this->reportMessage .= " \n\n ".$this->ErrorInfo;
435 }
436 if (!empty($warnings)) {
437 $this->reportMessage .= " \n\n ".$warnings;
438 }
439 $this->errorNumber = 1;
440 if ($this->report) {
441 $this->reportMessage = str_replace(
442 'Could not instantiate mail function',
443 '<a target="_blank" href="'.ACYM_DOCUMENTATION.'faq/could-not-instantiate-mail-function">'.acym_translation('ACYM_COUND_NOT_INSTANCIATE_MAIL_FUCNTION').'</a>',
444 $this->reportMessage
445 );
446 acym_enqueueMessage(nl2br($this->reportMessage), 'error');
447 }
448 } else {
449 if ($this->isSendingMethodByListActive) {
450 $this->reportMessage = acym_translationSprintf(
451 'ACYM_SEND_SUCCESS_WITH_SENDING_METHOD',
452 '<b>'.$this->Subject.'</b>',
453 '<b>'.implode(' , ', $receivers).'</b>',
454 '<b>'.$this->currentSendingMethod.'</b>'
455 );
456 } else {
457 $this->reportMessage = acym_translationSprintf('ACYM_SEND_SUCCESS', '<b>'.$this->Subject.'</b>', '<b>'.implode(' , ', $receivers).'</b>');
458 }
459
460 if (!empty($warnings)) {
461 $this->reportMessage .= " \n\n ".$warnings;
462 }
463
464 if ($this->report && acym_isAdmin()) {
465 acym_enqueueMessage(preg_replace('#(<br( ?/)?>){2}#', '<br />', nl2br($this->reportMessage)), 'info');
466 }
467 }
468
469 return $result;
470 }
471
472 public function clearAll(): void
473 {
474 $this->Subject = '';
475 $this->Body = '';
476 $this->AltBody = '';
477 $this->ClearAllRecipients();
478 $this->ClearAttachments();
479 $this->ClearCustomHeaders();
480 $this->ClearReplyTos();
481 $this->errorNumber = 0;
482 $this->MessageID = '';
483 $this->ErrorInfo = '';
484 $this->setFrom($this->getSendSettings('from_email'), $this->getSendSettings('from_name'));
485 }
486
487 public function load(int $mailId, ?object $user = null): ?object
488 {
489 if (isset($this->defaultMail[$mailId])) {
490 return $this->defaultMail[$mailId];
491 }
492
493 if (!empty($this->overrideEmailToSend)) {
494 $this->defaultMail[$mailId] = $this->overrideEmailToSend;
495 } else {
496 $this->defaultMail[$mailId] = $this->mailClass->getOneById($mailId, true);
497 }
498
499 global $acymLanguages;
500 if (!$this->isAbTest && empty($this->overrideEmailToSend) && acym_isMultilingual()) {
501 $defaultLanguage = $this->config->get('multilingual_default', ACYM_DEFAULT_LANGUAGE);
502 $mails = $this->mailClass->getMultilingualMails($mailId);
503
504 $this->userLanguage = !empty($user->language) ? $user->language : $defaultLanguage;
505
506 if (!empty($mails)) {
507 $languages = array_keys($mails);
508 if (count($languages) == 1) {
509 $key = $languages[0];
510 } elseif (empty($mails[$this->userLanguage])) {
511 $key = $defaultLanguage;
512 } else {
513 $key = $this->userLanguage;
514 }
515
516 if (isset($mails[$key])) {
517 $this->defaultMail[$mailId] = $mails[$key];
518 }
519 } else {
520 unset($this->defaultMail[$mailId]);
521
522 return null;
523 }
524
525 $acymLanguages['userLanguage'] = $this->userLanguage;
526 $this->setFrom($this->getSendSettings('from_email'), $this->getSendSettings('from_name'));
527 }
528
529 if (empty($this->defaultMail[$mailId]->id)) {
530 unset($this->defaultMail[$mailId]);
531
532 return null;
533 }
534
535 if (!empty($this->defaultMail[$mailId]->attachments)) {
536 $this->defaultMail[$mailId]->attach = [];
537
538 $attachments = json_decode($this->defaultMail[$mailId]->attachments);
539 foreach ($attachments as $oneAttach) {
540 $attach = new \stdClass();
541 $attach->name = basename($oneAttach->filename);
542 $attach->filename = str_replace(['/', '\\'], DS, ACYM_ROOT).$oneAttach->filename;
543 $attach->url = ACYM_LIVE.$oneAttach->filename;
544 $this->defaultMail[$mailId]->attach[] = $attach;
545 }
546 }
547
548
549 $this->mailId = $mailId;
550 $this->id = $this->mailId;
551 $this->mail = clone $this->defaultMail[$mailId];
552 acym_trigger('replaceContent', [&$this->defaultMail[$mailId], true]);
553
554 $this->prepareEmailContent($mailId);
555 if (!$this->isTest) {
556 $this->storeArchiveVersion($mailId);
557 }
558
559 return $this->defaultMail[$mailId];
560 }
561
562 public function sendOne($mailId, $user, array $options = []): bool
563 {
564 $this->clearAll();
565
566 $receiver = $this->loadUser($user);
567 $this->isTest = !empty($options['isTest']);
568 $this->dtextsFailed = false;
569
570 if (empty($receiver->email)) {
571 $this->errorNumber = 4;
572 $this->reportMessage = acym_translationSprintf('ACYM_SEND_ERROR_USER', '<b><i>'.acym_escape($user).'</i></b>');
573 if ($this->report) {
574 acym_enqueueMessage($this->reportMessage, 'error');
575 }
576
577 return false;
578 }
579
580 $mailId = intval($mailId);
581 $receiver->id = intval($receiver->id);
582
583 if (!$this->load($mailId, $receiver)) {
584 $this->reportMessage = 'Can not load the email with ID n°'.$mailId;
585 $this->errorNumber = 2;
586
587 if ($this->report) {
588 acym_enqueueMessage($this->reportMessage, 'error');
589 }
590
591 return false;
592 }
593
594 $this->prepareContent($mailId, $options);
595 $this->prepareHeaders($mailId, $receiver->id, $options);
596 $this->prepareAddresses($mailId, $receiver);
597 $this->prepareBcc($mailId, $options);
598 $this->prepareAttachments($mailId, $options);
599 $this->replaceDynamicContent($mailId);
600 $this->prepareTracking($mailId, $receiver);
601
602 if (!$this->handleDtexts($receiver)) {
603 $this->dtextsFailed = true;
604
605 return false;
606 }
607 $status = $this->send();
608
609 $this->initStatistics($status, $receiver->id);
610
611 return $status;
612 }
613
614 public function triggerFollowUpAgain(string $mailId, string $userId): void
615 {
616 if ($this->defaultMail[$mailId]->type !== MailClass::TYPE_FOLLOWUP) {
617 return;
618 }
619
620 $followUpClass = new FollowupClass();
621 $followUp = $followUpClass->getOneByMailId(intval($mailId));
622
623 if (empty($followUp)) {
624 return;
625 }
626
627 if (empty($followUp->loop)) {
628 return;
629 }
630
631 $lastEmail = $followUpClass->getLastEmail(intval($followUp->id));
632 if (empty($lastEmail) || $lastEmail->id != $mailId) {
633 return;
634 }
635
636 $delay = 0;
637 if (!empty($followUp->loop_delay)) {
638 $delay = $followUp->loop_delay;
639 }
640
641 $mailToSkip = [];
642 if (!empty($followUp->loop_mail_skip)) {
643 $mailToSkip = json_decode($followUp->loop_mail_skip);
644 }
645
646 $followUpClass->triggerFollowUp(intval($followUp->id), intval($userId), $delay, $mailToSkip);
647 }
648
649 public function statClick(int $mailId, int $userid, bool $fromStat = false): void
650 {
651 if (!$fromStat && !in_array($this->type, MailClass::TYPES_WITH_STATS)) {
652 return;
653 }
654
655 $urlClass = new UrlClass();
656 $urls = [];
657
658 $trackingSystemExternalWebsite = $this->config->get('trackingsystemexternalwebsite', 1);
659 $trackingSystem = $this->config->get('trackingsystem', 'acymailing');
660 if (false === strpos($trackingSystem, 'acymailing') && false === strpos($trackingSystem, 'google')) {
661 return;
662 }
663
664 if (strpos($trackingSystem, 'google') !== false) {
665 $mail = $this->mailClass->getOneById($mailId);
666 $campaignClass = new CampaignClass();
667 $campaign = $campaignClass->getOneCampaignByMailId($mailId);
668
669 $utmCampaign = acym_getAlias($mail->subject);
670 }
671
672 preg_match_all('#<[^>]* href[ ]*=[ ]*"(?!mailto:|\#|ymsgr:|callto:|file:|ftp:|webcal:|skype:|tel:)([^"]+)"#Ui', $this->body, $results);
673 if (empty($results)) {
674 return;
675 }
676
677 $countLinks = array_count_values($results[1]);
678 if (array_product($countLinks) != 1) {
679 $previousLinkHandled = '';
680 foreach ($results[1] as $key => $url) {
681 if ($countLinks[$url] === 1) {
682 continue;
683 }
684
685 $previousIsOutlook = false;
686 if (strpos($results[0][$key], '<v:roundrect') === 0) {
687 $previousLinkHandled = $results[0][$key];
688 if ($countLinks[$url] === 2) {
689 $countLinks[$url] = 1;
690 continue;
691 }
692 } elseif (strpos($previousLinkHandled, '<v:roundrect') === 0) {
693 $previousIsOutlook = true;
694 }
695 $previousLinkHandled = $results[0][$key];
696
697 if (!$previousIsOutlook) {
698 $countLinks[$url]--;
699 }
700
701 $toAddUrl = (strpos($url, '?') === false ? '?' : '&').'idU='.$countLinks[$url];
702
703 if ($previousIsOutlook) {
704 $countLinks[$url]--;
705 }
706
707 $posHash = strpos($url, '#');
708 if ($posHash !== false) {
709 $newURL = substr($url, 0, $posHash).$toAddUrl.substr($url, $posHash);
710 } else {
711 $newURL = $url.$toAddUrl;
712 }
713
714 $this->body = preg_replace('#href="('.preg_quote($url, '#').')"#Uis', 'href="'.$newURL.'"', $this->body, 1);
715
716 $results[0][$key] = 'href="'.$newURL.'"';
717 $results[1][$key] = $newURL;
718 }
719 }
720
721 foreach ($results[1] as $i => $url) {
722 $urlsNotToTrack = [
723 'task=unsub',
724 'fonts.googleapis.com',
725 ];
726
727 $track = true;
728
729 foreach ($urlsNotToTrack as $urlNotToTrack) {
730 if (strpos($url, $urlNotToTrack)) {
731 $track = false;
732 break;
733 }
734 }
735
736 if (isset($urls[$results[0][$i]]) || !$track) {
737 continue;
738 }
739
740 $simplifiedUrl = str_replace(['https://', 'http://', 'www.'], '', $url);
741 $simplifiedWebsite = str_replace(['https://', 'http://', 'www.'], '', ACYM_LIVE);
742 $internalUrl = strpos($simplifiedUrl, rtrim($simplifiedWebsite, '/')) === 0;
743
744 $subfolder = false;
745 if ($internalUrl) {
746 $urlWithoutBase = str_replace($simplifiedWebsite, '', $simplifiedUrl);
747 if (strpos($urlWithoutBase, '/') || strpos($urlWithoutBase, '?')) {
748 $slashPosition = strpos($urlWithoutBase, '/');
749 $folderName = substr($urlWithoutBase, 0, !$slashPosition ? strpos($urlWithoutBase, '?') : $slashPosition);
750 if (strpos($folderName, '.') === false) {
751 $subfolder = @is_dir(ACYM_ROOT.$folderName);
752 }
753 }
754 }
755
756 if ((!$internalUrl || $subfolder) && $trackingSystemExternalWebsite != 1) {
757 continue;
758 }
759
760 if (strpos($url, 'utm_source') === false && strpos($trackingSystem, 'google') !== false) {
761 $idToUse = empty($campaign) ? $mailId : $campaign->id;
762 $args = [];
763 $args[] = empty($campaign->sending_params['utm_source']) ? 'utm_source=newsletter_'.$idToUse : 'utm_source='.urlencode($campaign->sending_params['utm_source']);
764 $args[] = empty($campaign->sending_params['utm_medium']) ? 'utm_medium=email' : 'utm_medium='.urlencode($campaign->sending_params['utm_medium']);
765 $args[] = empty($campaign->sending_params['utm_campaign']) ? 'utm_campaign='.$utmCampaign : 'utm_campaign='.urlencode($campaign->sending_params['utm_campaign']);
766 $anchor = '';
767 if (strpos($url, '#') !== false) {
768 $anchor = substr($url, strpos($url, '#'));
769 $url = substr($url, 0, strpos($url, '#'));
770 }
771
772 if (strpos($url, '?')) {
773 $mytracker = $url.'&'.implode('&', $args);
774 } else {
775 $mytracker = $url.'?'.implode('&', $args);
776 }
777 $mytracker .= $anchor;
778 $urls[$results[0][$i]] = str_replace($results[1][$i], $mytracker, $results[0][$i]);
779
780 $url = $mytracker;
781 }
782
783 if (strpos($trackingSystem, 'acymailing') !== false) {
784 $isAutologin = false;
785 $autologinParams = 'autoSubId=%7Bsubscriber:id%7D&amp;subKey=%7Bsubscriber:key%7Curlencode%7D';
786 if (strpos($url, $autologinParams) !== false) {
787 $isAutologin = true;
788 $url = str_replace(
789 [
790 '?'.$autologinParams.'&amp;',
791 '?'.$autologinParams,
792 '&amp;'.$autologinParams,
793 ],
794 [
795 '?',
796 '',
797 '',
798 ],
799 $url
800 );
801 }
802
803 if (preg_match('#passw|modify|\{|%7B#i', $url)) {
804 continue;
805 }
806
807 if (!$fromStat) {
808 $mytracker = $urlClass->getUrl($url, $mailId, $userid);
809 }
810
811 if (empty($mytracker)) {
812 continue;
813 }
814
815 if ($isAutologin) {
816 $mytracker .= strpos($mytracker, '?') === false ? '?' : '&amp;';
817 $mytracker .= $autologinParams;
818 }
819
820 $urls[$results[0][$i]] = str_replace($results[1][$i], $mytracker, $results[0][$i]);
821 }
822 }
823
824 $this->body = str_replace(array_keys($urls), $urls, $this->body);
825 }
826
827 public function textVersion(string $html, bool $fullConvert = true): string
828 {
829 $html = acym_absoluteURL($html);
830
831 if ($fullConvert) {
832 $html = preg_replace('# +#', ' ', $html);
833 $html = str_replace(["\n", "\r", "\t"], '', $html);
834 }
835
836 $removepictureslinks = "#< *a[^>]*> *< *img[^>]*> *< *\/ *a *>#isU";
837 $removeScript = "#< *script(?:(?!< */ *script *>).)*< */ *script *>#isU";
838 $removeStyle = "#< *style(?:(?!< */ *style *>).)*< */ *style *>#isU";
839 $removeStrikeTags = '#< *strike(?:(?!< */ *strike *>).)*< */ *strike *>#iU';
840 $replaceByTwoReturnChar = '#< *(h1|h2)[^>]*>#Ui';
841 $replaceByStars = '#< *li[^>]*>#Ui';
842 $replaceByReturnChar1 = '#< */ *(li|td|dt|tr|div|p)[^>]*> *< *(li|td|dt|tr|div|p)[^>]*>#Ui';
843 $replaceByReturnChar = '#< */? *(br|p|h1|h2|legend|h3|li|ul|dd|dt|h4|h5|h6|tr|td|div)[^>]*>#Ui';
844 $replaceLinks = '/< *a[^>]*href *= *"([^#][^"]*)"[^>]*>(.+)< *\/ *a *>/Uis';
845
846 $text = preg_replace(
847 [
848 $removepictureslinks,
849 $removeScript,
850 $removeStyle,
851 $removeStrikeTags,
852 $replaceByTwoReturnChar,
853 $replaceByStars,
854 $replaceByReturnChar1,
855 $replaceByReturnChar,
856 $replaceLinks,
857 ],
858 ['', '', '', '', "\n\n", "\n* ", "\n", "\n", '${2} ( ${1} )'],
859 $html
860 );
861
862 $text = preg_replace('#(&lt;|&\#60;)([^ \n\r\t])#i', '&lt; ${2}', $text);
863
864 $text = str_replace([" ", "&nbsp;"], ' ', strip_tags($text));
865
866 $text = trim(@html_entity_decode($text, ENT_QUOTES, 'UTF-8'));
867
868 if ($fullConvert) {
869 $text = preg_replace('# +#', ' ', $text);
870 $text = preg_replace('#\n *\n\s+#', "\n\n", $text);
871 }
872
873 return $text;
874 }
875
876 protected function embedImages(): bool
877 {
878 preg_match_all('/(src|background)=[\'|"]([^"\']*)[\'|"]/Ui', $this->Body, $images);
879
880 if (empty($images[2])) {
881 return true;
882 }
883
884 $embedSuccess = true;
885
886 $mimetypes = [
887 'bmp' => 'image/bmp',
888 'gif' => 'image/gif',
889 'jpeg' => 'image/jpeg',
890 'jpg' => 'image/jpeg',
891 'jpe' => 'image/jpeg',
892 'png' => 'image/png',
893 'tiff' => 'image/tiff',
894 'tif' => 'image/tiff',
895 ];
896
897 $allImages = [];
898
899 foreach ($images[2] as $i => $url) {
900 if (isset($allImages[$url])) {
901 continue;
902 }
903
904 if (strpos($url, 'ctrl=') !== false) {
905 continue;
906 }
907
908 $allImages[$url] = 1;
909
910 $path = acym_internalUrlToPath($url);
911 $path = $this->removeAdditionalParams($path);
912
913 $filename = str_replace(['%', ' '], '_', basename($url));
914 $filename = $this->removeAdditionalParams($filename);
915
916 $md5 = md5($filename);
917 $cid = 'cid:'.$md5;
918 $fileParts = explode(".", $filename);
919 if (empty($fileParts[1])) {
920 continue;
921 }
922 $ext = strtolower($fileParts[1]);
923 if (!isset($mimetypes[$ext])) {
924 continue;
925 }
926
927 if ($this->addEmbeddedImage($path, $md5, $filename, 'base64', $mimetypes[$ext])) {
928 $this->Body = preg_replace('/'.preg_quote($images[0][$i], '/').'/Ui', $images[1][$i].'="'.$cid.'"', $this->Body);
929 } else {
930 $embedSuccess = false;
931 }
932 }
933
934 return $embedSuccess;
935 }
936
937 public function cleanText(?string $text): string
938 {
939 return trim(preg_replace('/(%0A|%0D|\n+|\r+)/i', '', (string)$text));
940 }
941
942 public function addParamInfo(): void
943 {
944 if (!empty($_SERVER)) {
945 $serverinfo = [];
946 foreach ($_SERVER as $oneKey => $oneInfo) {
947 $serverinfo[] = $oneKey.' => '.strip_tags(print_r($oneInfo, true));
948 }
949 $this->addParam('serverinfo', implode('<br />', $serverinfo));
950 }
951
952 if (!empty($_REQUEST)) {
953 $postinfo = [];
954 foreach ($_REQUEST as $oneKey => $oneInfo) {
955 $postinfo[] = $oneKey.' => '.strip_tags(print_r($oneInfo, true));
956 }
957 $this->addParam('postinfo', implode('<br />', $postinfo));
958 }
959 }
960
961 public function addParam(string $name, $value): void
962 {
963 $tagName = '{'.$name.'}';
964 $this->parameters[$tagName] = $value;
965 }
966
967 public function overrideEmail(array $options): bool
968 {
969 $overrideAllEmails = $this->config->get('send_website_emails', 0) == 1;
970
971 $overrideClass = new OverrideClass();
972 $override = $overrideClass->getMailByBaseContent($options['subject'], $options['message']);
973
974 if (empty($override) && !$overrideAllEmails) {
975 return false;
976 }
977
978 $this->autoAddUser = true;
979
980 if (empty($override)) {
981 $this->trackEmail = false;
982
983 return $this->overrideAllEmails($options);
984 } else {
985 $this->trackEmail = true;
986
987 return $this->sendOverride($override, $options);
988 }
989 }
990
991 private function getSendingMethodSettings(string $settingName, string $default = ''): string
992 {
993 if (empty($this->currentMethodSetting)) {
994 return $this->config->get($settingName, $default);
995 }
996
997 return $this->currentMethodSetting[$settingName] ?? $default;
998 }
999
1000 private function getSendingMethod(): string
1001 {
1002 $this->currentMethodSetting = [];
1003
1004 $sendingMethods = $this->config->get('sending_method_list', '{}');
1005 $sendingMethods = json_decode($sendingMethods, true);
1006 $sendingMethodsByList = $this->config->get('sending_method_list_by_list', '{}');
1007 $sendingMethodsByList = json_decode($sendingMethodsByList, true);
1008
1009 if (empty($this->listsIds) || empty($sendingMethods) || empty($sendingMethodsByList) || empty($sendingMethodsByList[$this->listsIds[0]])) {
1010 return $this->config->get('mailer_method', 'phpmail');
1011 }
1012
1013 $sendingMethodIds = [];
1014
1015 foreach ($this->listsIds as $listId) {
1016 if (!empty($sendingMethodsByList[$listId])) {
1017 $sendingMethodIds[] = $sendingMethodsByList[$listId];
1018 }
1019 }
1020
1021 if (count($sendingMethodIds) !== count($this->listsIds) || count(array_unique($sendingMethodIds)) !== 1) {
1022 return $this->config->get('mailer_method', 'phpmail');
1023 }
1024
1025 $sendingMethodId = $sendingMethodIds[0];
1026
1027 if (empty($sendingMethods[$sendingMethodId])) {
1028 return $this->config->get('mailer_method', 'phpmail');
1029 }
1030
1031 $this->currentMethodSetting = $sendingMethods[$sendingMethodId];
1032
1033 return $sendingMethods[$sendingMethodId]['mailer_method'];
1034 }
1035
1036 private function isExternal(string $method): void
1037 {
1038 $this->Mailer = 'external';
1039 $this->externalMailer = $method;
1040 }
1041
1042 private function storeArchiveVersion(int $mailId): void
1043 {
1044 if ($this->defaultMail[$mailId]->type !== MailClass::TYPE_STANDARD) {
1045 return;
1046 }
1047
1048 $mailArchive = new \stdClass();
1049
1050 $alreadyExisting = $this->mailArchiveClass->getOneByMailId($mailId);
1051 if (!empty($alreadyExisting)) {
1052 $mailArchive->id = $alreadyExisting->id;
1053 }
1054
1055 $mailArchive->mail_id = $mailId;
1056 $mailArchive->date = time();
1057 $mailArchive->body = $this->defaultMail[$mailId]->body;
1058 $mailArchive->subject = $this->defaultMail[$mailId]->subject;
1059 $mailArchive->settings = $this->defaultMail[$mailId]->settings;
1060 $mailArchive->stylesheet = $this->defaultMail[$mailId]->stylesheet;
1061 $mailArchive->attachments = $this->defaultMail[$mailId]->attachments;
1062 $this->mailArchiveClass->save($mailArchive);
1063 }
1064
1065 private function canTrack(int $mailId, object $user): bool
1066 {
1067 if ($this->isTest || empty($mailId) || empty($user) || !isset($user->tracking) || $user->tracking != 1) {
1068 return false;
1069 }
1070
1071 $mail = $this->mailClass->getOneById($mailId);
1072 if (!empty($mail) && $mail->tracking != 1) {
1073 return false;
1074 }
1075
1076 $lists = $this->mailClass->getAllListsByMailIdAndUserId($mailId, $user->id);
1077
1078 foreach ($lists as $list) {
1079 if ($list->tracking != 1) {
1080 return false;
1081 }
1082 }
1083
1084 return true;
1085 }
1086
1087 private function loadUser($user, bool $force = false): object
1088 {
1089 if (is_string($user) && strpos($user, '@')) {
1090 $receiver = $this->userClass->getOneByEmail($user);
1091
1092 if (empty($receiver)) {
1093 if (($force || $this->autoAddUser) && acym_isValidEmail($user)) {
1094 $newUser = new \stdClass();
1095 $newUser->email = $user;
1096 $this->userClass->checkVisitor = false;
1097 $this->userClass->sendConf = false;
1098 acym_setVar('acy_source', 'When sending a test');
1099 $this->userClass->triggers = $this->userCreationTriggers;
1100 $userId = $this->userClass->save($newUser);
1101 $receiver = $this->userClass->getOneById($userId);
1102 } else {
1103 throw new Exception(acym_translation('ACYM_USER_NOT_FOUND'));
1104 }
1105 }
1106 } elseif (is_object($user)) {
1107 $receiver = $user;
1108 } else {
1109 $receiver = $this->userClass->getOneById($user);
1110 }
1111
1112 $this->userLanguage = empty($receiver->language) ? acym_getLanguageTag() : $receiver->language;
1113 $this->receiverEmail = $receiver->email;
1114
1115 return $receiver;
1116 }
1117
1118 private function prepareContent(int $mailId, array $options): void
1119 {
1120 $this->isHTML();
1121
1122 if (!empty($this->defaultMail[$mailId]->stylesheet)) {
1123 $this->stylesheet = $this->defaultMail[$mailId]->stylesheet;
1124 }
1125 $this->settings = empty($this->defaultMail[$mailId]->settings) ? [] : json_decode($this->defaultMail[$mailId]->settings, true);
1126
1127 $this->Subject = $this->defaultMail[$mailId]->subject;
1128 $this->Body = $this->defaultMail[$mailId]->body;
1129 if ($this->isTest && !empty($options['testNote'])) {
1130 $this->Body = '<div style="text-align: center; padding: 25px; font-family: Poppins; font-size: 20px">'.$options['testNote'].'</div>'.$this->Body;
1131 }
1132
1133 if (!empty($this->introtext)) {
1134 $this->Body = $this->introtext.$this->Body;
1135 }
1136
1137 if (!empty($this->defaultMail[$mailId]->preheader)) {
1138 $preHeader = '<!--[if !mso 9]><!--><div style="visibility:hidden;mso-hide:all;font-size:0;color:transparent;height:0;line-height:0;max-height:0;max-width:0;opacity:0;overflow:hidden;">';
1139 $preHeader .= $this->defaultMail[$mailId]->preheader.str_repeat('&nbsp;&zwnj;', 100);
1140 $preHeader .= '</div><!--<![endif]-->';
1141
1142 preg_match('#(<(.*)<body(.*)>)#Uis', $this->Body, $matches);
1143 if (empty($matches) || empty($matches[1])) {
1144 $this->Body = $preHeader.$this->Body;
1145 } else {
1146 $this->Body = $matches[1].$preHeader.str_replace($matches[1], '', $this->Body);
1147 }
1148 }
1149
1150 if (ACYM_CMS === 'wordpress') {
1151 ob_start();
1152 $this->Body = do_shortcode($this->Body);
1153 ob_end_clean();
1154 }
1155 }
1156
1157 private function prepareHeaders(int $mailId, int $receiverId, array $options): void
1158 {
1159 $subject = base64_encode(rand(0, 9999999)).'AC'.$receiverId.'Y'.$this->defaultMail[$mailId]->id.'BA'.base64_encode(time().rand(0, 99999));
1160 $this->MessageID = '<'.preg_replace('|[^a-z0-9+_]|i', '', $subject).'@'.$this->serverHostname().'>';
1161 $this->addCustomHeader('Feedback-ID', $this->defaultMail[$mailId]->id.':'.$receiverId.':'.$this->defaultMail[$mailId]->type.':'.base64_encode(ACYM_ROOT));
1162
1163 if (!empty($this->defaultMail[$mailId]->headers)) {
1164 $this->mailHeader = $this->defaultMail[$mailId]->headers;
1165 }
1166
1167 if (!empty($options['headers'])) {
1168 foreach ($options['headers'] as $headerName => $headerValue) {
1169 $this->addCustomHeader(trim($headerName), trim($headerValue));
1170 }
1171 }
1172 }
1173
1174 private function prepareAddresses(int $mailId, object $receiver): void
1175 {
1176 $receiverName = '';
1177 if ($this->config->get('add_names', true)) {
1178 $receiverName = $this->cleanText($receiver->name);
1179 if ($receiverName == $this->cleanText($receiver->email)) {
1180 $receiverName = '';
1181 }
1182 }
1183 $this->addAddress($this->cleanText($receiver->email), $receiverName);
1184
1185 $this->setFrom($this->getSendSettings('from_email', $mailId), $this->getSendSettings('from_name', $mailId));
1186 $this->acymAddReplyTo($this->defaultMail[$mailId]->reply_to_email ?? '', $this->defaultMail[$mailId]->reply_to_name ?? '');
1187
1188 if (!empty($this->defaultMail[$mailId]->bounce_email)) {
1189 $this->Sender = $this->cleanText($this->defaultMail[$mailId]->bounce_email);
1190 } else {
1191 $this->Sender = $this->cleanText($this->config->get('bounce_email'));
1192 }
1193 }
1194
1195 private function prepareBcc(int $mailId, array $options): void
1196 {
1197 $bccAddresses = [];
1198
1199 if (!empty($options['bcc'])) {
1200 $bccAddresses = $options['bcc'];
1201 }
1202
1203 if (!empty($this->defaultMail[$mailId]->bcc)) {
1204 $emailBccAddresses = explode(';', trim(str_replace([',', ' '], ';', $this->defaultMail[$mailId]->bcc)));
1205 $bccAddresses = array_unique(array_merge($bccAddresses, $emailBccAddresses));
1206 }
1207
1208 if (!empty($bccAddresses)) {
1209 $bccAddresses = array_filter($bccAddresses);
1210 foreach ($bccAddresses as $oneBccAddress) {
1211 $this->loadUser($oneBccAddress, true);
1212 $this->addBCC($oneBccAddress);
1213 }
1214 }
1215 }
1216
1217 private function prepareAttachments(int $mailId, array $options): void
1218 {
1219 $attachments = [];
1220
1221 if (!empty($this->defaultMail[$mailId]->attach)) {
1222 $attachments = $this->defaultMail[$mailId]->attach;
1223 }
1224
1225 if (!empty($options['attachments'])) {
1226 $attachments = array_merge($attachments, $options['attachments']);
1227 }
1228
1229 if (empty($attachments)) {
1230 return;
1231 }
1232
1233 if ($this->config->get('embed_files')) {
1234 foreach ($attachments as $attachment) {
1235 $this->addAttachment($attachment->filename, $attachment->name);
1236 }
1237 } else {
1238 $attachStringHTML = '<fieldset><legend>'.acym_translation('ACYM_ATTACHMENTS').'</legend><table>';
1239 foreach ($attachments as $attachment) {
1240 $attachStringHTML .= '<tr><td><a href="'.$attachment->url.'" target="_blank">'.$attachment->name.'</a></td></tr>';
1241 }
1242 $attachStringHTML .= '</table></fieldset>';
1243
1244 if ($this->config->get('attachments_position', 'bottom') === 'top') {
1245 $this->Body = $attachStringHTML.'<br />'.$this->Body;
1246 } else {
1247 $this->Body .= '<br />'.$attachStringHTML;
1248 }
1249 }
1250 }
1251
1252 private function replaceDynamicContent(int $mailId): void
1253 {
1254 $this->replaceParams();
1255
1256 $this->body = &$this->Body;
1257 $this->altbody = &$this->AltBody;
1258 $this->subject = &$this->Subject;
1259 $this->from = &$this->From;
1260 $this->fromName = &$this->FromName;
1261 $this->replyto = &$this->ReplyTo;
1262 $this->replyname = $this->defaultMail[$mailId]->reply_to_name ?? '';
1263 $this->replyemail = $this->defaultMail[$mailId]->reply_to_email ?? '';
1264 $this->mailId = intval($this->defaultMail[$mailId]->id);
1265 $this->id = $this->mailId;
1266 $this->creator_id = intval($this->defaultMail[$mailId]->creator_id);
1267 $this->type = $this->defaultMail[$mailId]->type;
1268 $this->stylesheet = &$this->stylesheet;
1269 $this->links_language = $this->defaultMail[$mailId]->links_language;
1270 }
1271
1272 private function prepareTracking(int $mailId, object $receiver): void
1273 {
1274 if ($this->canTrack($mailId, $receiver)) {
1275 $receiver->id = intval($receiver->id);
1276 $this->statPicture($this->mailId, $receiver->id);
1277 $this->body = acym_absoluteURL($this->body);
1278 $this->statClick($this->mailId, $receiver->id);
1279 if (acym_isTrackingSalesActive()) {
1280 $this->trackingSales($this->mailId, $receiver->id);
1281 }
1282 }
1283 }
1284
1285 private function handleDtexts(object $receiver): bool
1286 {
1287 $this->replaceParams();
1288
1289 if (strpos($receiver->email, '@mt.acyba.com') !== false) {
1290 $currentUser = $this->userClass->getOneByEmail(acym_currentUserEmail());
1291 if (empty($currentUser)) {
1292 $currentUser = $receiver;
1293 }
1294 $result = acym_trigger('replaceUserInformation', [&$this, &$currentUser, true]);
1295 } else {
1296 $result = acym_trigger('replaceUserInformation', [&$this, &$receiver, true]);
1297 if (!empty($result)) {
1298 foreach ($result as $oneResult) {
1299 if (!empty($oneResult) && !$oneResult['send']) {
1300 $this->reportMessage = $oneResult['message'];
1301
1302 return false;
1303 }
1304 }
1305 }
1306 }
1307
1308 global $acymLanguages;
1309 if (!empty($acymLanguages['userLanguage'])) {
1310 unset($acymLanguages['userLanguage']);
1311 }
1312
1313 foreach ($result as $oneResult) {
1314 if (!empty($oneResult['emogrifier'])) {
1315 $this->prepareEmailContent();
1316 break;
1317 }
1318 }
1319
1320 if ($this->config->get('multiple_part', false)) {
1321 $this->altbody = $this->textVersion($this->Body);
1322 }
1323
1324 $this->replaceParams();
1325
1326 return true;
1327 }
1328
1329 private function initStatistics(bool $status, int $receiverId): void
1330 {
1331 if ($this->trackEmail) {
1332 $statsAdd = [];
1333 $statsAdd[$this->mailId][intval($status)][] = $receiverId;
1334
1335 $queueHelper = new QueueHelper();
1336 $queueHelper->statsAdd($statsAdd);
1337 $this->trackEmail = false;
1338 }
1339 }
1340
1341 private function trackingSales(int $mailId, int $userId): void
1342 {
1343 preg_match_all('#href[ ]*=[ ]*"(?!mailto:|\#|ymsgr:|callto:|file:|ftp:|webcal:|skype:|tel:)([^"]+)"#Ui', $this->body, $results);
1344 if (empty($results)) {
1345 return;
1346 }
1347
1348 foreach ($results[1] as $url) {
1349 $simplifiedUrl = str_replace(['https://', 'http://', 'www.'], '', $url);
1350 $simplifiedWebsite = str_replace(['https://', 'http://', 'www.'], '', ACYM_LIVE);
1351 if (strpos($simplifiedUrl, rtrim($simplifiedWebsite, '/')) === false || strpos($url, 'task=unsub')) continue;
1352
1353 $toAddUrl = (strpos($url, '?') === false ? '?' : '&').'linkReferal='.$mailId.'-'.$userId;
1354
1355 $posHash = strpos($url, '#');
1356 if ($posHash !== false) {
1357 $newURL = substr($url, 0, $posHash).$toAddUrl.substr($url, $posHash);
1358 } else {
1359 $newURL = $url.$toAddUrl;
1360 }
1361
1362 $this->body = preg_replace('#href="('.preg_quote($url, '#').')"#Uis', 'href="'.$newURL.'"', $this->body);
1363 }
1364 }
1365
1366 private function statPicture(int $mailId, int $userId): void
1367 {
1368 $pictureLink = acym_frontendLink('frontstats&task=openStats&id='.$mailId.'&userid='.$userId, true, false);
1369
1370 $statPicture = '<img class="spict" alt="Statistics image" src="'.$pictureLink.'" border="0" width="50" height="1" />';
1371
1372 if (strpos($this->body, '</body>')) {
1373 $this->body = str_replace('</body>', $statPicture.'</body>', $this->body);
1374 } else {
1375 $this->body .= $statPicture;
1376 }
1377 }
1378
1379 private function removeAdditionalParams(string $url): string
1380 {
1381 $additionalParamsPos = strpos($url, '?');
1382 if (!empty($additionalParamsPos)) {
1383 $url = substr($url, 0, $additionalParamsPos);
1384 }
1385
1386 return $url;
1387 }
1388
1389 private function replaceParams(): void
1390 {
1391 if (empty($this->parameters)) {
1392 return;
1393 }
1394
1395 $pluginHelper = new PluginHelper();
1396
1397 $this->generateAllParams();
1398
1399 $vars = [
1400 'Subject',
1401 'Body',
1402 'From',
1403 'FromName',
1404 'replyname',
1405 'replyemail',
1406 ];
1407
1408 foreach ($vars as $oneVar) {
1409 if (!empty($this->$oneVar)) {
1410 $this->$oneVar = $pluginHelper->replaceDText($this->$oneVar, $this->parameters);
1411 }
1412 }
1413
1414 if (!empty($this->ReplyTo)) {
1415 foreach ($this->ReplyTo as $i => $replyto) {
1416 foreach ($replyto as $a => $oneval) {
1417 $this->ReplyTo[$i][$a] = $pluginHelper->replaceDText($this->ReplyTo[$i][$a], $this->parameters);
1418 }
1419 }
1420 }
1421 }
1422
1423 private function generateAllParams(): void
1424 {
1425 $result = '<table style="border:1px solid;border-collapse:collapse;" border="1" cellpadding="10"><tr><td>Tag</td><td>Value</td></tr>';
1426 foreach ($this->parameters as $name => $value) {
1427 if (!is_string($value)) continue;
1428
1429 $result .= '<tr><td>'.trim($name, '{}').'</td><td>'.$value.'</td></tr>';
1430 }
1431 $result .= '</table>';
1432 $this->addParam('allshortcodes', $result);
1433 }
1434
1435 private function sendOverride(object $override, array $options): bool
1436 {
1437 for ($i = 1 ; $i < count($override->parameters) ; $i++) {
1438 $oneParam = $override->parameters[$i];
1439
1440 $unmodified = $oneParam;
1441 $oneParam = preg_replace(
1442 '/(http|https):\/\/(.*)/',
1443 '<a href="$1://$2" target="_blank">$1://$2</a>',
1444 $oneParam,
1445 -1,
1446 $count
1447 );
1448 if ($count > 0) {
1449 $this->addParam('link'.$i, $unmodified);
1450 }
1451 $this->addParam('param'.$i, $oneParam);
1452 }
1453
1454 $this->addParam('subject', $options['subject']);
1455
1456 $this->overrideEmailToSend = $override;
1457 $statusSend = $this->sendOne($override->id, $options['to'], $options);
1458 if (!$statusSend && !empty($this->reportMessage)) {
1459 $cronHelper = new CronHelper();
1460 $cronHelper->saveReport([$this->reportMessage]);
1461 }
1462
1463 return $statusSend;
1464 }
1465
1466 private function getSendSettings(string $type, int $mailId = 0): string
1467 {
1468 if (!in_array($type, ['from_name', 'from_email', 'replyto_name', 'replyto_email'])) {
1469 return '';
1470 }
1471
1472 $mailType = strpos($type, 'replyto') !== false ? str_replace('replyto', 'reply_to', $type) : $type;
1473
1474 if (!empty($mailId) && !empty($this->defaultMail[$mailId]) && !empty($this->defaultMail[$mailId]->$mailType)) {
1475 return $this->defaultMail[$mailId]->$mailType;
1476 }
1477
1478 $lang = empty($this->userLanguage) ? acym_getLanguageTag() : $this->userLanguage;
1479 $setting = $this->config->get($type);
1480 $translation = $this->config->get('sender_info_translation');
1481
1482 if (!empty($translation)) {
1483 $translation = json_decode($translation, true);
1484
1485 if (!empty($translation[$lang])) {
1486 $setting = $translation[$lang][$type];
1487 }
1488 }
1489
1490 return $setting;
1491 }
1492
1493 private function prepareEmailContent(int $mailId = 0): void
1494 {
1495 $this->handleRelativeURLs($mailId);
1496 $this->inlineCSS($mailId);
1497 $this->finalizeHtmlStructure($mailId);
1498 }
1499
1500 private function handleRelativeURLs(int $mailId): void
1501 {
1502 $mail = empty($mailId) ? $this : $this->defaultMail[$mailId];
1503 $mail->body = acym_absoluteURL($mail->body);
1504 }
1505
1506 private function inlineCSS(int $mailId): void
1507 {
1508 $mail = empty($mailId) ? $this : $this->defaultMail[$mailId];
1509
1510 global $emogrifiedMediaCSS;
1511 $emogrifiedMediaCSS = '';
1512
1513 $style = $this->getEmailStylesheet($mail);
1514 $cssInliner = CssInlinerHelper::fromHtml($mail->body)->inlineCss(implode('', $style));
1515 $domDocument = $cssInliner->getDomDocument();
1516
1517 $mail->body = preg_replace(
1518 [
1519 '# id="mce_\d+"#Ui',
1520 '#(style="[^"]+);"#Ui',
1521 ],
1522 [
1523 '',
1524 '$1"',
1525 ],
1526 HtmlPruner::fromDomDocument($domDocument)
1527 ->removeRedundantClassesAfterCssInlined($cssInliner)
1528 ->renderBodyContent()
1529 );
1530
1531 $mail->body = str_replace('&zwj;', '', $mail->body);
1532
1533 $mail->body = preg_replace_callback(
1534 '#style="([^"]+)"#Ui',
1535 function ($matches) {
1536 return 'style="'.str_replace(['; ', ': '], [';', ':'], $matches[1]).'"';
1537 },
1538 $mail->body
1539 );
1540 }
1541
1542 private function finalizeHtmlStructure(int $mailId): void
1543 {
1544 $mail = empty($mailId) ? $this : $this->defaultMail[$mailId];
1545
1546 $finalContent = '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">';
1547 $finalContent .= '<head>';
1548 $finalContent .= '<!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->';
1549 $finalContent .= '<meta http-equiv="Content-Type" content="text/html; charset='.strtolower($this->config->get('charset')).'" />'."\n";
1550 $finalContent .= '<meta name="viewport" content="width=device-width, initial-scale=1.0" />'."\n";
1551 $finalContent .= '<title>'.$mail->subject.'</title>'."\n";
1552
1553 global $emogrifiedMediaCSS;
1554 $finalContent .= '<style>'.$emogrifiedMediaCSS.'</style>';
1555 $finalContent .= '<!--[if mso]><style type="text/css">#acym__wysid__template center > table { width: 580px; }</style><![endif]-->';
1556 $finalContent .= '<!--[if !mso]><!--><style>#acym__wysid__template center > table { width: 100%; }</style><!--<![endif]-->';
1557
1558 if (!empty($mail->headers)) {
1559 $finalContent .= $mail->headers;
1560 }
1561 $finalContent .= '</head>';
1562
1563 preg_match('@<[^>"t]*body[^>]*>@', $mail->body, $matches);
1564 if (empty($matches[0])) {
1565 $mail->body = '<body>'.$mail->body.'</body>';
1566 } else {
1567 preg_match('@<[^>"t]*/body[^>]*>@', $mail->body, $matches);
1568 if (empty($matches[0])) {
1569 $mail->body .= '</body>';
1570 }
1571 }
1572
1573 $finalContent .= $mail->body;
1574 $finalContent .= '</html>';
1575
1576 $mail->body = $finalContent;
1577 }
1578
1579 private function getEmailStylesheet(object $mail): array
1580 {
1581 $style = [];
1582
1583 if (strpos($mail->body, 'acym__wysid__template') !== false) {
1584 static $foundationCSS = null;
1585 if (empty($foundationCSS)) {
1586 $foundationCSS = acym_fileGetContent(ACYM_MEDIA.'css'.DS.'libraries'.DS.'foundation_email.min.css');
1587 $foundationCSS = str_replace('#acym__wysid__template ', '', $foundationCSS);
1588 }
1589 $style['foundation'] = $foundationCSS;
1590 }
1591
1592 static $emailFixes = null;
1593 if (empty($emailFixes)) {
1594 $emailFixes = acym_getEmailCssFixes();
1595 }
1596 $style['fixes'] = $emailFixes;
1597
1598 if (!empty($mail->stylesheet)) {
1599 $style['custom_stylesheet'] = $mail->stylesheet;
1600 }
1601
1602 $settingsStyles = $this->editorHelper->getSettingsStyle($mail->settings);
1603 if (!empty($settingsStyles)) {
1604 $style['settings_stylesheet'] = $settingsStyles;
1605 }
1606
1607 return $style;
1608 }
1609
1610 private function overrideAllEmails(array $options): bool
1611 {
1612 $this->trackEmail = false;
1613
1614 $this->addParam('subject', $options['subject']);
1615
1616 if (isset($options['isHtml']) && !$options['isHtml']) {
1617 $options['message'] = nl2br($options['message']);
1618 }
1619 $this->addParam('body', $options['message']);
1620
1621 $mailClass = new MailClass();
1622 $this->overrideEmailToSend = $mailClass->getOneByName('acy_notification_cms');
1623
1624 if (empty($this->overrideEmailToSend)) {
1625 return false;
1626 }
1627
1628 $statusSend = $this->sendOne($this->overrideEmailToSend->id, $options['to'], $options);
1629 if (!$statusSend && !empty($this->reportMessage)) {
1630 $cronHelper = new CronHelper();
1631 $cronHelper->addMessage($this->reportMessage);
1632 $cronHelper->saveReport();
1633 }
1634
1635 return $statusSend;
1636 }
1637
1638
1639 private function acymAddReplyTo(string $email, string $name): void
1640 {
1641 if (empty($email)) {
1642 return;
1643 }
1644
1645 $replyToName = $this->config->get('add_names', true) ? $this->cleanText(trim($name)) : '';
1646 $replyToEmail = trim($email);
1647
1648 if (substr_count($replyToEmail, '@') > 1) {
1649 $replyToEmailArray = explode(';', str_replace([';', ','], ';', $replyToEmail));
1650 $replyToNameArray = explode(';', str_replace([';', ','], ';', $replyToName));
1651 foreach ($replyToEmailArray as $i => $oneReplyTo) {
1652 $this->addReplyTo($this->cleanText($oneReplyTo), @$replyToNameArray[$i]);
1653 }
1654 } else {
1655 $this->addReplyTo($this->cleanText($replyToEmail), $replyToName);
1656 }
1657 }
1658
1659 public function setFrom($email, $name = '', $auto = false)
1660 {
1661 if (!empty($email)) {
1662 $this->From = $this->cleanText($email);
1663 }
1664 if (!empty($name) && $this->config->get('add_names', true)) {
1665 $this->FromName = $this->cleanText($name);
1666 }
1667 }
1668
1669 protected function edebug($str)
1670 {
1671 if (strpos($this->ErrorInfo, $str) === false) {
1672 $this->ErrorInfo .= ' '.$str;
1673 }
1674 }
1675
1676 public function getMailMIME()
1677 {
1678 $result = parent::getMailMIME();
1679
1680 $result = rtrim($result, static::$LE);
1681
1682 if ($this->Mailer != 'mail') {
1683 $result .= static::$LE.static::$LE;
1684 }
1685
1686 return $result;
1687 }
1688
1689 public static function validateAddress($address, $patternselect = null)
1690 {
1691 return true;
1692 }
1693 }
1694