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

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