PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.2.2
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.2.2
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.2.2, at back/Helpers/MailerHelper.php

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