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