PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.0.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.0.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
acymailing / back / Helpers / BounceHelper.php

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

1,463 lines 57.3 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\HistoryClass;
6 use AcyMailing\Classes\ListClass;
7 use AcyMailing\Classes\MailClass;
8 use AcyMailing\Classes\RuleClass;
9 use AcyMailing\Classes\UserClass;
10 use AcyMailing\Core\AcymObject;
11 use AcyMailing\Libraries\Pear\Pop3;
12 use AcyMailing\Types\CharsetType;
13
14 class BounceHelper extends AcymObject
15 {
16 private $server;
17 private $username;
18 private $password;
19 private $port;
20 private $connectMethod;
21 private $secureMethod;
22 private $selfSigned;
23 private $timeout;
24 private $oAuthToken;
25 private $imapConnectionMethod;
26
27 private $allowed_extensions = [];
28 public $nbMessages = 0;
29 public $report = false;
30 private $mailer;
31 private $mailbox;
32 public $_message;
33 private $userClass;
34 private $blockedUsers = [];
35 var $deletedUsers = [];
36 private $bounceMessages = [];
37 private $usePear = false;
38 private $detectEmail;
39 private $detectEmail2 = '/(([a-z0-9\-]+\.)+[a-z0-9]{2,8})\/([a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*)/i';
40 public $messages = [];
41 public $stoptime = 0;
42 public $mod_security2 = false;
43 public $obend = 0;
44 public $action;
45 public $attachments = [];
46 private $inlineImages = [];
47
48 private $allCharsets;
49
50 private $ruleClass;
51 private $mailClass;
52 private $historyClass;
53 private $encodingHelper;
54
55 public $allLists;
56
57 public function __construct()
58 {
59 parent::__construct();
60
61 $this->mailer = new MailerHelper();
62 $this->ruleClass = new RuleClass();
63 $this->userClass = new UserClass();
64 $this->mailClass = new MailClass();
65 $this->historyClass = new HistoryClass();
66 $this->encodingHelper = new EncodingHelper();
67
68 $this->mailer->report = false;
69 $this->mailer->alreadyCheckedAddresses = true;
70
71 $charsetType = new CharsetType();
72 $this->allCharsets = $charsetType->charsets;
73
74 $this->allowed_extensions = explode(',', $this->config->get('allowed_files', ''));
75 $this->detectEmail = '/'.acym_getEmailRegex(false, true).'/i';
76 }
77
78 public function init($config = [])
79 {
80 if (empty($config)) {
81 $config = [
82 'server' => $this->config->get('bounce_server'),
83 'username' => $this->config->get('bounce_username'),
84 'password' => $this->config->get('bounce_password'),
85 'port' => $this->config->get('bounce_port', ''),
86 'connect_method' => $this->config->get('bounce_connection'),
87 'secure_method' => $this->config->get('bounce_secured', ''),
88 'self_signed' => $this->config->get('bounce_certif', false),
89 'timeout' => $this->config->get('bounce_timeout'),
90 'bounce_token' => $this->config->get('bounce_token', ''),
91 'imap_connection_method' => $this->config->get('imap_connection_method', 'classic'),
92 ];
93 }
94
95 $this->server = $config['server'];
96 $this->username = $config['username'];
97 $this->password = $config['password'];
98 $this->port = $config['port'];
99 $this->connectMethod = $config['connect_method'];
100 $this->secureMethod = $config['secure_method'];
101 $this->selfSigned = $config['self_signed'];
102 $this->timeout = $config['timeout'];
103 $this->oAuthToken = str_replace('Bearer ', '', $config['bounce_token']);
104 $this->imapConnectionMethod = $config['imap_connection_method'];
105
106 if ($this->connectMethod == 'pear') {
107 $this->usePear = true;
108 include_once ACYM_LIBRARIES.'Pear'.DS.'Pop3.php';
109
110 return true;
111 }
112
113 if (extension_loaded('imap') || function_exists('imap_open')) {
114 return true;
115 }
116
117 $prefix = PHP_SHLIB_SUFFIX === 'dll' ? 'php_' : '';
118 $EXTENSION = $prefix.'imap.'.PHP_SHLIB_SUFFIX;
119
120 if (function_exists('dl')) {
121 $fatalMessage = 'The system tried to load dynamically the '.$EXTENSION.' extension';
122 $fatalMessage .= '<br />If you see this message, that means the system could not load this PHP extension';
123 $fatalMessage .= '<br />Please enable the PHP Extension '.$EXTENSION;
124 ob_start();
125 echo $fatalMessage;
126 dl($EXTENSION);
127 $warnings = str_replace($fatalMessage, '', ob_get_clean());
128 if (extension_loaded('imap') || function_exists('imap_open')) {
129 return true;
130 }
131 }
132
133 if ($this->report) {
134 acym_display(
135 'The extension "'.$EXTENSION.'" could not be loaded, please change your PHP configuration to enable it or use the pop3 method without imap extension',
136 'error'
137 );
138 if (!empty($warnings)) {
139 acym_display($warnings, 'warning');
140 }
141 }
142
143 return false;
144 }
145
146 public function connect()
147 {
148 if ($this->usePear) {
149 return $this->connectPear();
150 }
151
152 return $this->connectImap();
153 }
154
155 private function connectPear()
156 {
157 ob_start();
158
159 $this->mailbox = new Pop3();
160
161 $timeout = $this->timeout;
162 if (!empty($timeout)) {
163 $this->mailbox->_timeout = $timeout;
164 }
165
166 $port = intval($this->port);
167 $secure = $this->secureMethod;
168 if (empty($port)) {
169 if ($secure == 'ssl') {
170 $port = '995';
171 } else {
172 $port = '110/pop3/notls';
173 }
174 }
175
176 $serverName = trim($this->server);
177
178 if (!empty($secure) && !strpos($serverName, '://')) {
179 $serverName = $secure.'://'.$serverName;
180 }
181
182 if (!$this->mailbox->connect($serverName, $port)) {
183 $warnings = ob_get_clean();
184 if ($this->report) {
185 acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_CONNECTING', $this->server.' : '.$port), 'error');
186 }
187 if (!empty($warnings) && $this->report) {
188 acym_display($warnings, 'warning');
189 }
190
191 return false;
192 }
193
194 $login = $this->mailbox->login(trim($this->username), trim($this->password), 'USER');
195 if (empty($login) || isset($login->code)) {
196 $warnings = ob_get_clean();
197 if ($this->report) {
198 acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_LOGIN', $this->username.':'.$this->password), 'error');
199 }
200 if (!empty($warnings) && $this->report) {
201 acym_display($warnings, 'warning');
202 }
203
204 return false;
205 }
206
207 ob_end_clean();
208
209 return true;
210 }
211
212 private function connectImap()
213 {
214 if (empty($this->server)) {
215 acym_enqueueMessage(acym_translation('ACYM_CONFIGURE_BOUNCE'), 'warning');
216
217 return false;
218 }
219
220 ob_start();
221 $buff = $this->callImapFunction('imap_alerts', []);
222 $buff = $this->callImapFunction('imap_errors', []);
223
224 $timeout = intval($this->timeout);
225 if (!empty($timeout)) {
226 $this->callImapFunction('imap_timeout', [IMAP_OPENTIMEOUT, $timeout]);
227 }
228
229 $port = intval($this->port);
230 $secure = $this->secureMethod;
231 $protocol = $this->connectMethod;
232 $serverName = '{'.trim($this->server);
233 if (empty($port)) {
234 if ($protocol === 'imap') {
235 $port = $secure === 'ssl' ? '993' : '143';
236 } elseif ($protocol === 'pop3') {
237 $port = $secure === 'ssl' ? '995' : '110';
238 }
239 }
240
241 if (!empty($port)) {
242 $serverName .= ':'.$port;
243 }
244
245 if ($serverName === '{outlook.office365.com:993') {
246 $serverName .= '/imap';
247 }
248
249 if (!empty($secure)) {
250 $serverName .= '/'.$secure;
251 }
252
253 $email = trim($this->username);
254 if (strpos($email, '\\') !== false) {
255 [$user, $authuser] = explode('\\', $email);
256 [$x, $domain] = explode('@', $user);
257 $serverName .= '/authuser='.$user.'/user='.$authuser.'@'.$domain;
258 }
259
260 if ($this->selfSigned) {
261 $serverName .= '/novalidate-cert';
262 }
263
264 if (!empty($protocol)) {
265 $serverName .= '/service='.$protocol;
266 }
267 $serverName .= '}';
268 if ($this->imapConnectionMethod === 'oauth') {
269 $this->refreshToken();
270 $this->mailbox = imap2_open($serverName, trim($this->username), $this->oAuthToken, OP_XOAUTH2);
271 } else {
272 $this->mailbox = imap_open($serverName, trim($this->username), trim($this->password), OP_SILENT);
273 }
274 $warnings = ob_get_clean();
275
276 if ($this->report) {
277 if (!$this->mailbox) {
278 acym_enqueueMessage(acym_translationSprintf('ACYM_ERROR_CONNECTING', $serverName), 'error');
279 }
280 if (!empty($warnings)) {
281 acym_enqueueMessage($warnings, 'warning');
282 }
283 }
284
285 return (bool)$this->mailbox;
286 }
287
288 public function getNBMessages()
289 {
290 if ($this->usePear) {
291 $this->nbMessages = $this->mailbox->numMsg();
292 } else {
293 $this->nbMessages = $this->callImapFunction('imap_num_msg', [$this->mailbox]);
294 }
295
296 return $this->nbMessages;
297 }
298
299 public function getMessage($msgNB)
300 {
301 if ($this->usePear) {
302 $message = new \stdClass();
303 $message->headerString = $this->mailbox->getRawHeaders($msgNB);
304 if (empty($message->headerString)) {
305 return false;
306 }
307 } else {
308 $message = $this->callImapFunction('imap_headerinfo', [$this->mailbox, $msgNB]);
309 }
310
311 return $message;
312 }
313
314 public function deleteMessage($msgNB)
315 {
316 if ($this->usePear) {
317 $this->mailbox->deleteMsg($msgNB);
318 } else {
319 $this->callImapFunction('imap_delete', [$this->mailbox, $msgNB]);
320 $this->callImapFunction('imap_expunge', [$this->mailbox]);
321 }
322 }
323
324 public function close()
325 {
326 if ($this->usePear) {
327 $this->mailbox->disconnect();
328 } else {
329 $this->callImapFunction('imap_close', [$this->mailbox]);
330 }
331 }
332
333 public function decodeMessage($attachments = false)
334 {
335 if ($this->usePear) {
336 return $this->decodeMessagePear($attachments);
337 } else {
338 return $this->decodeMessageImap($attachments);
339 }
340 }
341
342 private function decodeMessagePear($attachments)
343 {
344 $this->_message->headerinfo = $this->mailbox->getParsedHeaders($this->_message->messageNB);
345 if (empty($this->_message->headerinfo['subject'])) {
346 return false;
347 }
348 $this->_message->text = '';
349 $this->_message->html = $this->mailbox->getBody($this->_message->messageNB);
350
351 if (!empty($this->_message->headerinfo['content-type']) && strpos($this->_message->headerinfo['content-type'], 'boundary') !== false) {
352 $matches = [];
353 preg_match('#boundary="([^"]+)"#i', $this->_message->headerinfo['content-type'], $matches);
354
355 if (!empty($matches[1]) && strpos($this->_message->html, $matches[1]) !== false) {
356 $inlineImages = [];
357
358 $segments = explode('--'.$matches[1], $this->_message->html);
359 foreach ($segments as $segment) {
360 if (strpos($segment, 'Content-Type: text/plain') !== false) {
361 $matches = [];
362 preg_match('#boundary="([^"]+)"#i', $segment, $matches);
363
364 if (!empty($matches[1])) {
365 $parts = explode('--'.$matches[1], $segment);
366
367 foreach ($parts as $onePart) {
368 $content = trim(preg_replace('#(charset=".*?\r\n)|Content-(Type|ID|Disposition|Transfer-Encoding):.*?\r\n#is', "", $onePart));
369
370 if (strpos($onePart, 'Content-Transfer-Encoding') !== false) {
371 preg_match('#Content-Transfer-Encoding: (.+)#i', $onePart, $encoding);
372 $encoding = trim($encoding[1]);
373 if (!empty($encoding) && $encoding !== 'Content-Type:') {
374 $content = mb_convert_encoding($content, 'UTF-8', $encoding);
375 }
376 }
377
378 if (strpos($onePart, "Content-Type: text/plain") !== false) {
379 $this->_message->text .= ' '.$content;
380 }
381 if (strpos($onePart, "Content-Type: text/html") !== false) {
382 $this->_message->html .= ' '.$content;
383 }
384 }
385 } else {
386 $this->_message->html .= ' '.trim(preg_replace('#(charset=".*?\r\n)|Content-(Type|ID|Disposition|Transfer-Encoding):.*?\r\n#is', "", $segment));
387 }
388 } elseif (preg_match("#Content-Type: .*?/(png|jpg|jpeg|gif)#i", $segment) !== false) {
389 preg_match('#name="([^"]+)"#i', $segment, $filename);
390 if (empty($filename) || empty($filename[1])) {
391 continue;
392 }
393 $name = trim($filename[1]);
394 $filename = $name;
395 $extensionPos = strrpos($filename, '.');
396
397 if ($extensionPos === false) {
398 continue;
399 }
400
401 $extension = substr($filename, $extensionPos + 1);
402
403 $filename = preg_replace('#[^a-zA-Z0-9]#Uis', '_', substr($filename, 0, $extensionPos));
404
405 $uploadFolder = str_replace(['/', '\\'], DS, acym_getFilesFolder());
406 $pathToUpload = ACYM_ROOT.trim($uploadFolder, DS).DS;
407
408 if (file_exists($pathToUpload.$filename.'.'.$extension)) {
409 $fileNumber = 1;
410 while (file_exists($pathToUpload.$filename.'_('.$fileNumber.').'.$extension)) {
411 $fileNumber++;
412 }
413 $filename = $filename.'_('.$fileNumber.')';
414 }
415
416 if ($attachments && preg_match("#Content-Disposition: attachment;#i", $segment) !== false) {
417 $data = trim(substr($segment, strpos($segment, "\r\n\r\n")));
418 if (strpos($segment, 'Content-Transfer-Encoding: base64') !== false) {
419 $data = base64_decode($data);
420 }
421
422 try {
423 if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
424 $attachment = new \stdClass();
425 $attachment->filename = str_replace(DS, '/', trim($uploadFolder, '\\/')).'/'.$filename.'.'.$extension;
426 $attachment->size = filesize(ACYM_ROOT.$attachment->filename);
427 $this->attachments[] = $attachment;
428 }
429 } catch (\Exception $e) {
430 $completeFilename = $filename.'.'.$extension;
431 $this->display(acym_translationSprintf('ACYM_ERROR_UPLOADING_ATTACHMENT_X_TO_X', $completeFilename, $e->getMessage()), false);
432 }
433 } else {
434 preg_match('#Content-ID: <([^>]+)>#i', $segment, $contentID);
435 if (empty($contentID) || empty($contentID[1])) {
436 continue;
437 }
438 $contentID = trim($contentID[1]);
439
440 $data = trim(substr($segment, strpos($segment, "\r\n\r\n")));
441 if (strpos($segment, 'Content-Transfer-Encoding: base64') !== false) {
442 $data = base64_decode($data);
443 }
444
445
446 try {
447 if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
448 $inlineImages['cid:'.$contentID] = acym_rootURI().$uploadFolder.'/'.$filename.'.'.$extension;
449 }
450 } catch (\Exception $e) {
451 $this->display(acym_translationSprintf('ACYM_ERROR_UPLOAD_ATTACHMENT', $filename.'.'.$extension, $e->getMessage()), false);
452 }
453 }
454 }
455 }
456
457 if (!empty($inlineImages)) {
458 $this->_message->html = str_replace(array_keys($inlineImages), $inlineImages, $this->_message->html);
459 }
460 }
461 }
462
463 $this->_message->subject = $this->decodeHeader($this->_message->headerinfo['subject']);
464 if (empty($this->_message->header)) {
465 $this->_message->header = new \stdClass();
466 }
467 $this->_message->header->sender_email = @$this->_message->headerinfo['return-path'];
468 if (is_array($this->_message->header->sender_email)) {
469 $this->_message->header->sender_email = reset($this->_message->header->sender_email);
470 }
471 if (preg_match($this->detectEmail, $this->_message->header->sender_email, $results)) {
472 $this->_message->header->sender_email = $results[0];
473 }
474 $this->_message->header->sender_name = strip_tags(@$this->_message->headerinfo['from']);
475 $this->_message->header->reply_to_email = $this->_message->header->sender_email;
476 $this->_message->header->reply_to_name = $this->_message->header->sender_name;
477 $this->_message->header->from_email = $this->_message->header->sender_email;
478 $this->_message->header->from_name = $this->_message->header->sender_name;
479
480 return true;
481 }
482
483 private function decodeMessageImap($attachments)
484 {
485
486 $this->_message->structure = $this->callImapFunction('imap_fetchstructure', [$this->mailbox, $this->_message->messageNB]);
487
488 if (empty($this->_message->structure)) {
489 return false;
490 }
491
492 $this->_message->headerinfo = $this->callImapFunction('imap_fetchheader', [$this->mailbox, $this->_message->messageNB]);
493
494 $this->_message->html = '';
495 $this->_message->text = '';
496
497 if ($this->_message->structure->type == 1) {
498 $this->_message->contentType = 2;
499 if ($this->_message->structure->subtype == "MIXED") {
500 $allParts = $this->explodeBodyMixed($this->_message->structure);
501 } else {
502 $allParts = $this->explodeBody($this->_message->structure);
503 }
504
505 $text = '';
506 foreach ($allParts as $num => $onePart) {
507 $decodedContent = $this->decodeContent($this->callImapFunction('imap_fetchbody', [$this->mailbox, $this->_message->messageNB, $num]), $onePart);
508 if ($onePart->subtype == 'HTML') {
509 $this->_message->html .= $decodedContent;
510 } else {
511 if ($onePart->subtype == 'PLAIN') {
512 $text .= $decodedContent."\n";
513 }
514 $this->_message->text .= $decodedContent."\n\n- - -\n\n";
515 }
516
517 if (!empty($this->action) && $attachments) {
518 if ((isset($onePart->parameters) && is_array($onePart->parameters)) || (isset($onePart->dparameters) && is_array($onePart->dparameters))) {
519 $this->uploadAttachment($onePart, $num);
520 } elseif (isset($onePart->parts) && is_array($onePart->parts)) {
521 foreach ($onePart->parts as $num2 => $innerPart) {
522 $this->uploadAttachment($innerPart, $num2);
523 }
524 }
525 }
526 }
527
528 if (!empty($this->action) && empty($this->_message->html)) {
529 $this->_message->html = $text."\n";
530 }
531 if (!empty($this->inlineImages)) {
532 $this->_message->html = str_replace(array_keys($this->inlineImages), $this->inlineImages, $this->_message->html);
533 }
534 } else {
535 if ($this->_message->structure->subtype == 'HTML') {
536 $this->_message->contentType = 1;
537 $this->_message->html = $this->decodeContent($this->callImapFunction('imap_body', [$this->mailbox, $this->_message->messageNB]), $this->_message->structure);
538 } else {
539 $this->_message->contentType = 0;
540 $this->_message->text = $this->decodeContent($this->callImapFunction('imap_body', [$this->mailbox, $this->_message->messageNB]), $this->_message->structure);
541 }
542 }
543
544 $this->_message->subject = $this->decodeHeader($this->_message->subject);
545
546 $this->decodeAddressImap('sender');
547 $this->decodeAddressImap('from');
548 $this->decodeAddressImap('reply_to');
549 $this->decodeAddressImap('to');
550
551 return true;
552 }
553
554 private function uploadAttachment($attachment, $num)
555 {
556 if (!isset($attachment->disposition) || (strtolower($attachment->disposition) !== 'attachment' && strtolower($attachment->disposition) !== 'inline')) {
557 return;
558 }
559
560 $name = empty($attachment->dparameters[0]->value) ? $attachment->parameters[0]->value : $attachment->dparameters[0]->value;
561 $filename = $this->decodeHeader($name);
562 $extensionPos = strrpos($filename, '.');
563
564 if ($extensionPos === false) {
565 foreach ($attachment->parameters as $key => $oneParam) {
566 if (strtolower($oneParam->attribute) == 'name') {
567 $filename = $this->decodeHeader($attachment->parameters[$key]->value);
568 $extensionPos = strrpos($filename, '.');
569 break;
570 }
571 }
572 }
573
574 if ($extensionPos === false) {
575 return;
576 }
577
578 $extension = substr($filename, $extensionPos + 1);
579 if (!in_array(strtolower($extension), $this->allowed_extensions)) {
580 return;
581 }
582
583 $filename = preg_replace('#[^a-zA-Z0-9]#Uis', '_', substr($filename, 0, $extensionPos));
584
585 $uploadFolder = str_replace(['/', '\\'], DS, acym_getFilesFolder());
586 $pathToUpload = ACYM_ROOT.trim($uploadFolder, DS).DS;
587
588 if (file_exists($pathToUpload.$filename.'.'.$extension)) {
589 $fileNumber = 1;
590 while (file_exists($pathToUpload.$filename.'_('.$fileNumber.').'.$extension)) {
591 $fileNumber++;
592 }
593 $filename = $filename.'_('.$fileNumber.')';
594 }
595
596 $data = $this->decodeContent($this->callImapFunction('imap_fetchbody', [$this->mailbox, $this->_message->messageNB, $num]), $attachment);
597
598 try {
599 if (acym_writeFile($pathToUpload.$filename.'.'.$extension, $data)) {
600 if (strtolower($attachment->disposition) !== 'attachment' && !empty($attachment->id) && in_array(
601 strtolower($attachment->subtype),
602 acym_getImageFileExtensions()
603 )) {
604 $this->inlineImages['cid:'.trim($attachment->id, '<>')] = acym_rootURI().$uploadFolder.'/'.$filename.'.'.$extension;
605 } else {
606 $attachment = new \stdClass();
607 $attachment->filename = str_replace(DS, '/', trim($uploadFolder, '\\/')).'/'.$filename.'.'.$extension;
608 $attachment->size = filesize(ACYM_ROOT.$attachment->filename);
609 $this->attachments[] = $attachment;
610 }
611 }
612 } catch (\Exception $e) {
613 $completeFilename = $filename.'.'.$extension;
614 $this->display(acym_translationSprintf('ACYM_ERROR_UPLOADING_ATTACHMENT_X_TO_X', $completeFilename, $e->getMessage()), false);
615 }
616 }
617
618 public function handleMessages()
619 {
620 $maxMessages = min($this->nbMessages, $this->config->get('bounce_max', 0));
621 if (empty($maxMessages)) {
622 $maxMessages = min($this->nbMessages, 500);
623 }
624
625 if ($this->report) {
626 if (function_exists('apache_get_modules')) {
627 $modules = apache_get_modules();
628 $this->mod_security2 = in_array('mod_security2', $modules);
629 }
630
631 @ini_set('output_buffering', 'off');
632 @ini_set('zlib.output_compression', 0);
633
634
635 if (!headers_sent()) {
636 while (ob_get_level() > 0 && $this->obend++ < 3) {
637 @ob_end_flush();
638 }
639 }
640
641 $disp = "<div style='position:fixed; top:3px;left:3px;background-color : white;border : 1px solid grey; padding : 3px;font-size:14px'>";
642 $disp .= acym_translation('ACYM_BOUNCE_HANDLING');
643 $disp .= ': <span id="counter">0</span> / '.$maxMessages;
644 $disp .= '</div>';
645 $disp .= '<script type="text/javascript" language="javascript">';
646 $disp .= 'var mycounter = document.getElementById("counter");';
647 $disp .= 'function setCounter(val){ mycounter.innerHTML=val;}';
648 $disp .= '</script>';
649 echo $disp;
650 if (function_exists('ob_flush')) {
651 @ob_flush();
652 }
653 if (!$this->mod_security2) {
654 @flush();
655 }
656 }
657
658 $rules = $this->ruleClass->getAll(null, true);
659
660 $msgNB = $maxMessages;
661 $listClass = new ListClass();
662 $this->allLists = $listClass->getAll('id');
663
664 $replyemail = $this->config->get('reply_email');
665 $fromemail = $this->config->get('from_email');
666 $bouncemail = $this->config->get('bounce_email');
667 $removeEmails = '#('.str_replace(['%'], ['@'], $this->config->get('bounce_username'));
668 if (!empty($bouncemail)) {
669 $removeEmails .= '|'.$bouncemail;
670 }
671 if (!empty($fromemail)) {
672 $removeEmails .= '|'.$fromemail;
673 }
674 if (!empty($replyemail)) {
675 $removeEmails .= '|'.$replyemail;
676 }
677 $removeEmails .= ')#i';
678
679 while (($msgNB > 0) && ($this->_message = $this->getMessage($msgNB))) {
680 if ($this->report) {
681 echo '<script type="text/javascript" language="javascript">setCounter('.($maxMessages - $msgNB + 1).')</script>';
682 if (function_exists('ob_flush')) {
683 @ob_flush();
684 }
685 if (!$this->mod_security2) {
686 @flush();
687 }
688 }
689 $this->_message->messageNB = $msgNB;
690 $msgNB--;
691
692 if (!$this->decodeMessage()) {
693 $this->display(acym_translation('ACYM_ERROR_RETRIEVING_MESSAGE'), false, $maxMessages - $this->_message->messageNB + 1);
694 continue;
695 }
696
697 if (empty($this->_message->subject)) {
698 $this->_message->subject = '';
699 }
700
701 $this->_message->analyseText = $this->_message->html.' '.$this->_message->text;
702 if (!empty($this->_message->header->from_email)) {
703 $this->_message->analyseText .= ' '.$this->_message->header->from_email;
704 }
705 $this->display('<b>'.acym_translation('ACYM_EMAIL_SUBJECT').' : '.strip_tags($this->_message->subject).'</b>', false, $maxMessages - $this->_message->messageNB + 1);
706
707 preg_match('#AC([0-9]+)Y([0-9]+)BA#i', $this->_message->analyseText, $resultsVars);
708 if (!empty($resultsVars[1])) {
709 $this->_message->userid = $resultsVars[1];
710 }
711 if (!empty($resultsVars[2])) {
712 $this->_message->mailid = $resultsVars[2];
713 }
714
715 if (empty($this->_message->userid)) {
716 preg_match_all($this->detectEmail, $this->_message->analyseText, $results);
717
718 if (empty($results[0])) {
719 preg_match_all($this->detectEmail2, $this->_message->analyseText, $results2);
720 for ($i = 0 ; $i < count($results2[0]) ; $i++) {
721 $results[0][] = $results2[3][$i].'@'.$results2[1][$i];
722 }
723 }
724
725 if (!empty($results[0])) {
726 $alreadyChecked = [];
727 foreach ($results[0] as $oneEmail) {
728 if (!preg_match($removeEmails, $oneEmail)) {
729 $this->_message->subemail = strtolower($oneEmail);
730 if (!empty($alreadyChecked[$this->_message->subemail])) {
731 continue;
732 }
733
734 $user = $this->userClass->getOneByEmail($this->_message->subemail);
735 if (!empty($user)) $this->_message->userid = $user->id;
736
737 if (empty($this->_message->userid) && preg_match('#(&|\?)id=([0-9]+)&#ims', $this->_message->subemail, $subid)) {
738 $user = $this->userClass->getOneById($subid[2]);
739 $this->_message->userid = $user->id;
740 $this->_message->subemail = $user->email;
741 }
742
743 $alreadyChecked[$this->_message->subemail] = true;
744 if (!empty($this->_message->userid)) {
745 break;
746 }
747 }
748 }
749 }
750 }
751
752 if (empty($this->_message->mailid) && !empty($this->_message->userid)) {
753 $this->_message->mailid = acym_loadResult(
754 'SELECT `mail_id` FROM #__acym_user_stat WHERE `user_id` = '.intval($this->_message->userid).' ORDER BY `send_date` DESC'
755 );
756 }
757
758
759 foreach ($rules as $oneRule) {
760 if ($this->handleRule($oneRule)) {
761 break;
762 }
763 }
764
765 if ($msgNB % 50 == 0) {
766 $this->userActions();
767 }
768
769 if (!empty($this->stoptime) && time() > $this->stoptime) {
770 break;
771 }
772 }
773
774
775 $this->userActions();
776
777 $this->close();
778 }
779
780 private function userActions()
781 {
782 if (!empty($this->deletedUsers)) {
783 acym_arrayToInteger($this->deletedUsers);
784 $this->userClass->delete($this->deletedUsers);
785 $this->deletedUsers = [];
786 }
787
788 if (!empty($this->blockedUsers)) {
789 acym_arrayToInteger($this->blockedUsers);
790 $allUsersId = implode(',', $this->blockedUsers);
791 acym_query('UPDATE `#__acym_user` SET `active` = 0 WHERE `id` IN ('.$allUsersId.')');
792 acym_query('DELETE FROM `#__acym_queue` WHERE `user_id` IN ('.$allUsersId.')');
793 $this->blockedUsers = [];
794 }
795
796 if (!empty($this->bounceMessages)) {
797 foreach ($this->bounceMessages as $mailid => $bouncedata) {
798
799 $updateBounceDetails = '';
800 if (!empty($bouncedata['bouncedetails'])) {
801 $bouncedetails = acym_loadResult('SELECT `bounce_details` FROM #__acym_mail_stat WHERE mail_id = '.intval($mailid));
802 if (!empty($bouncedetails)) {
803 $bouncedetails = unserialize($bouncedetails);
804 } else {
805 $bouncedetails = [];
806 }
807
808 foreach ($bouncedata['bouncedetails'] as $ruleName => $nbTimes) {
809 if (empty($bouncedetails[$ruleName])) {
810 $bouncedetails[$ruleName] = 0;
811 }
812 $bouncedetails[$ruleName] += $nbTimes;
813 }
814
815 $updateBounceDetails = ' , `bounce_details` = '.acym_escapeDB(serialize($bouncedetails));
816 }
817
818 if (!empty($bouncedata['userids'])) {
819 $valueInsert = [];
820 foreach ($bouncedata['ruletriggered'] as $userid => $rulename) {
821 $valueInsert[] = '('.intval($mailid).','.intval($userid).',1,'.acym_escapeDB($rulename).')';
822 }
823 acym_query(
824 'INSERT INTO #__acym_user_stat (mail_id,user_id,bounce,bounce_rule) VALUES '.implode(
825 ',',
826 $valueInsert
827 ).' ON DUPLICATE KEY UPDATE `bounce` = `bounce` + 1, bounce_rule=VALUES(bounce_rule)'
828 );
829 acym_arrayToInteger($bouncedata['userids']);
830 $realUniqueBounces = acym_loadResult(
831 'SELECT COUNT(*)
832 FROM #__acym_user_stat
833 WHERE `bounce` = 1
834 AND `user_id` IN ('.implode(',', $bouncedata['userids']).')
835 AND `mail_id` = '.intval($mailid)
836 );
837 $bouncedata['nbbounces'] = $bouncedata['nbbounces'] - count($bouncedata['userids']) + $realUniqueBounces;
838 }
839
840 acym_query(
841 'UPDATE #__acym_mail_stat SET `bounce_unique` = `bounce_unique` + '.intval($bouncedata['nbbounces']).$updateBounceDetails.'
842 WHERE `mail_id` = '.intval($mailid).'
843 LIMIT 1'
844 );
845 }
846 $this->bounceMessages = [];
847 }
848 }
849
850 private function handleRule(&$oneRule)
851 {
852 $regex = $oneRule->regex;
853 if (empty($regex)) {
854 return false;
855 }
856
857
858 $analyseText = '';
859 if (in_array('senderInfo', $oneRule->executed_on)) {
860 $analyseText .= ' ';
861 if (isset($this->_message->header->sender_name)) {
862 $analyseText .= $this->_message->header->sender_name;
863 }
864 if (isset($this->_message->header->sender_email)) {
865 $analyseText .= $this->_message->header->sender_email;
866 }
867 }
868 if (in_array('subject', $oneRule->executed_on)) {
869 $analyseText .= ' '.$this->_message->subject;
870 }
871 if (in_array('body', $oneRule->executed_on)) {
872 if (!empty($this->_message->html)) {
873 $analyseText .= ' '.$this->_message->html;
874 }
875 if (!empty($this->_message->text)) {
876 $analyseText .= ' '.$this->_message->text;
877 }
878 }
879
880 $analyseText = str_replace(["\n", "\r", "\t"], ' ', $analyseText);
881
882 if (!preg_match('#'.$regex.'#ims', $analyseText)) {
883 return false;
884 }
885
886 $message = acym_translation('ACYM_BOUNCE_RULE').' ['.acym_translation('ACYM_ID').' '.$oneRule->id.'] '.acym_translation($oneRule->name).' : ';
887 $message .= $this->actionUser($oneRule);
888 $message .= $this->actionMessage($oneRule);
889
890 $this->display($message, true);
891
892 return true;
893 }
894
895 private function actionUser(&$oneRule)
896 {
897 $commonListsMailUser = [];
898 $message = '';
899
900 if (empty($this->_message->userid)) {
901 $message .= 'user not identified';
902 if (!empty($this->_message->subemail)) {
903 $message .= ' ( '.$this->_message->subemail.' ) ';
904 }
905
906 return $message;
907 }
908
909 if (in_array('delete_user_subscription', $oneRule->action_user) || in_array('unsubscribe_user', $oneRule->action_user) || in_array(
910 'subscribe_user',
911 $oneRule->action_user
912 )) {
913 if (empty($this->_message->subemail)) {
914 $currentUser = $this->userClass->getOneById($this->_message->userid);
915 if (!empty($currentUser->email)) {
916 $this->_message->subemail = $currentUser->email;
917 }
918 }
919
920 $userSubscription = $this->userClass->getSubscriptionStatus($this->_message->userid);
921
922 $bounceContent = '';
923 if (!empty($this->_message->html)) {
924 $bounceContent .= ' '.$this->_message->html;
925 }
926 if (!empty($this->_message->text)) {
927 $bounceContent .= ' '.$this->_message->text;
928 }
929
930 $emailLists = [];
931 preg_match('#Please unsubscribe user ID \d+ from list\(s\) ([^.]+)\.#Uis', $bounceContent, $emailLists);
932 if (!empty($emailLists[1])) {
933 $currentEmailLists = explode(',', $emailLists[1]);
934 foreach ($currentEmailLists as $oneListId) {
935 if (!empty($userSubscription[$oneListId]) && $userSubscription[$oneListId]->status == 1) {
936 $commonListsMailUser[] = $oneListId;
937 }
938 }
939 }
940
941 if (empty($commonListsMailUser)) {
942 $commonListsMailUser = array_keys($userSubscription);
943 }
944 }
945
946 if (empty($this->_message->subemail)) {
947 $this->_message->subemail = $this->_message->userid;
948 }
949
950
951 $mail = $this->mailClass->getOneById($this->_message->mailid);
952 if ($oneRule->increment_stats && !empty($this->_message->mailid) && !empty($mail)) {
953
954 if (empty($this->bounceMessages[$this->_message->mailid])) {
955 $this->bounceMessages[$this->_message->mailid] = [];
956 $this->bounceMessages[$this->_message->mailid]['nbbounces'] = 0;
957 $this->bounceMessages[$this->_message->mailid]['userids'] = [];
958 $this->bounceMessages[$this->_message->mailid]['bouncedetails'] = [];
959 $this->bounceMessages[$this->_message->mailid]['ruletriggered'] = [];
960 }
961
962 $this->bounceMessages[$this->_message->mailid]['nbbounces']++;
963
964 $ruleName = $oneRule->name.' [ID '.$oneRule->id.'] ';
965 if (empty($this->bounceMessages[$this->_message->mailid]['bouncedetails'][$ruleName])) {
966 $this->bounceMessages[$this->_message->mailid]['bouncedetails'][$ruleName] = 1;
967 } else {
968 $this->bounceMessages[$this->_message->mailid]['bouncedetails'][$ruleName]++;
969 }
970
971 $user = $this->userClass->getOneById($this->_message->userid);
972 if (!empty($this->_message->userid) && !in_array('delete_user', $oneRule->action_user) && !empty($user)) {
973 $this->bounceMessages[$this->_message->mailid]['userids'][] = intval($this->_message->userid);
974 $this->bounceMessages[$this->_message->mailid]['ruletriggered'][intval($this->_message->userid)] = $oneRule->name.' ['.acym_translation(
975 'ACYM_ID'
976 ).' '.$oneRule->id.']';
977 }
978 }
979
980 if (!empty($oneRule->execute_action_after) && $oneRule->execute_action_after > 1) {
981 if (empty($this->_message->mailid)) {
982 $this->_message->mailid = 0;
983 }
984 $nb = intval(
985 acym_loadResult(
986 'SELECT COUNT(mail_id) FROM #__acym_user_stat WHERE bounce > 0 AND user_id = '.intval($this->_message->userid).' AND mail_id != '.intval(
987 $this->_message->mailid
988 )
989 )
990 ) + 1;
991
992 if ($nb < $oneRule->execute_action_after) {
993 $message .= ' | '.acym_translationSprintf('ACYM_BOUNCE_RECEIVED', $nb, $this->_message->subemail).' | '.acym_translationSprintf(
994 'ACYM_BOUNCE_MIN_EXEC',
995 $oneRule->execute_action_after
996 );
997
998 return $message;
999 }
1000 }
1001
1002 if (in_array('delete_user', $oneRule->action_user)) {
1003 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_DELETED', $this->_message->subemail);
1004 $this->deletedUsers[] = intval($this->_message->userid);
1005
1006 return $message;
1007 }
1008
1009 $listId = 0;
1010 if (in_array('subscribe_user', $oneRule->action_user) && !empty($oneRule->action_user['subscribe_user_list'])) {
1011 $listId = $oneRule->action_user['subscribe_user_list'];
1012
1013 $listName = empty($this->allLists[$listId]->name) ? $listId : $this->allLists[$listId]->name;
1014 $message .= ' | ';
1015 if (isset($userSubscription[$listId])) {
1016 $message .= acym_translationSprintf('ACYM_USER_X_NOT_SUBSCRIBED_TO', $this->_message->subemail, $listName);
1017 if ($userSubscription[$listId]->status == 1) {
1018 $message .= acym_translation('ACYM_USER_ALREADY_SUBSCRIBED');
1019 } elseif ($userSubscription[$listId]->status == 0) {
1020 $message .= acym_translation('ACYM_USER_ALREADY_UNSUBSCRIBED');
1021 }
1022 } else {
1023 $this->userClass->subscribe($this->_message->userid, $listId);
1024 $message .= acym_translationSprintf('ACYM_USER_X_SUBSCRIBED_TO', $this->_message->subemail, $listName);
1025 }
1026 }
1027
1028 if (in_array('delete_user_subscription', $oneRule->action_user)) {
1029 $removeLists = array_diff($commonListsMailUser, [$listId]);
1030 if (!empty($removeLists)) {
1031 $listNames = [];
1032 foreach ($removeLists as $oneListId) {
1033 if (!empty($this->allLists[$oneListId]->name)) {
1034 $listNames[] = $this->allLists[$oneListId]->name;
1035 } else {
1036 $listNames[] = $oneListId;
1037 }
1038 }
1039 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_REMOVED_FROM', $this->_message->subemail, implode(', ', $listNames));
1040 $this->userClass->removeSubscription($this->_message->userid, $removeLists);
1041 } else {
1042 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_NOT_SUBSCRIBED', $this->_message->subemail);
1043 }
1044 }
1045
1046 if (in_array('unsubscribe_user', $oneRule->action_user)) {
1047 $unsubLists = array_diff($commonListsMailUser, [$listId]);
1048 if (!empty($unsubLists)) {
1049 $listNames = [];
1050 foreach ($unsubLists as $oneListId) {
1051 if (!empty($this->allLists[$oneListId]->name)) {
1052 $listNames[] = $this->allLists[$oneListId]->name;
1053 } else {
1054 $listNames[] = $oneListId;
1055 }
1056 }
1057 $this->userClass->unsubscribe($this->_message->userid, $unsubLists);
1058 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_UNSUBSCRIBED_FROM', $this->_message->subemail, implode(', ', $listNames));
1059 } else {
1060 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_NOT_SUBSCRIBED', $this->_message->subemail);
1061 }
1062 }
1063
1064 if (in_array('block_user', $oneRule->action_user)) {
1065 $message .= ' | '.acym_translationSprintf('ACYM_USER_X_BLOCKED', $this->_message->subemail.' ( '.acym_translation('ACYM_ID').' '.intval($this->_message->userid).' )');
1066 $this->blockedUsers[] = intval($this->_message->userid);
1067 }
1068
1069 if (in_array('empty_queue_user', $oneRule->action_user)) {
1070 $affected = acym_query('DELETE FROM #__acym_queue WHERE user_id = '.intval($this->_message->userid));
1071 $message .= ' | '.acym_translationSprintf(
1072 'ACYM_USER_X_QUEUE',
1073 $this->_message->subemail.' ( '.acym_translation('ACYM_ID').' '.intval($this->_message->userid).' )',
1074 acym_translationSprintf('ACYM_SUCC_DELETE_ELEMENTS', $affected)
1075 );
1076 }
1077
1078 return $message;
1079 }
1080
1081 private function actionMessage(&$oneRule)
1082 {
1083 $message = '';
1084
1085 if (!empty($oneRule->action_message['forward_to'])) {
1086 if (strtolower($oneRule->action_message['forward_to']) == strtolower($this->config->get('bounce_username')) || strtolower(
1087 $oneRule->action_message['forward_to']
1088 ) == strtolower($this->config->get('bounce_email'))) {
1089 $oneRule->action_message['forward_to'] = '';
1090 unset($oneRule->action_message['delete_message']);
1091 $message .= ' | '.acym_translation('ACYM_BOUNCE_NOT_FORWARD');
1092 }
1093 }
1094
1095
1096 $donotdelete = false;
1097
1098 if (in_array('save_message', $oneRule->action_message) && !empty($this->_message->userid) && !in_array('delete_user', $oneRule->action_user)) {
1099 $data = [];
1100 $data[] = 'SUBJECT::'.@htmlentities($this->_message->subject, ENT_COMPAT, 'UTF-8');
1101 $data[] = 'ACY_RULE::'.$oneRule->id.' '.$oneRule->name;
1102 $data[] = 'REPLYTO_ADDRESS::'.$this->_message->header->reply_to_name.' ( '.$this->_message->header->reply_to_email.' )';
1103 $data[] = 'FROM_ADDRESS::'.$this->_message->header->from_name.' ( '.$this->_message->header->from_email.' )';
1104 $data[] = @htmlentities(print_r($this->_message->headerinfo, true), ENT_COMPAT, 'UTF-8');
1105 if (empty($this->_message->mailid)) {
1106 $this->_message->mailid = 0;
1107 }
1108 try {
1109 $this->historyClass->insert($this->_message->userid, 'bounce', $data, $this->_message->mailid);
1110 $message .= ' | '.acym_translationSprintf('ACYM_BOUNCE_MESSAGE_SAVED', $this->_message->userid);
1111 } catch (\Exception $e) {
1112 $message .= ' | '.acym_translationSprintf(
1113 'ACYM_BOUNCE_SAVE_ERROR_USER_MAIL_RULE',
1114 $this->_message->userid,
1115 $this->_message->mailid,
1116 $oneRule->id,
1117 $e->getMessage()
1118 );
1119 $donotdelete = true;
1120 }
1121 }
1122
1123
1124 if (!empty($oneRule->action_message['forward_to'])) {
1125 $this->mailer->clearAll();
1126 $this->mailer->Subject = 'BOUNCE FORWARD : '.$this->_message->subject;
1127
1128 if (substr_count($oneRule->action_message['forward_to'], '@') > 1) {
1129 $forwardAddresses = explode(';', str_replace([';', ','], ';', $oneRule->action_message['forward_to']));
1130 } else {
1131 $forwardAddresses = [$oneRule->action_message['forward_to']];
1132 }
1133
1134 foreach ($forwardAddresses as $oneForwardAddress) {
1135 $this->mailer->AddAddress($this->mailer->cleanText($oneForwardAddress));
1136 }
1137
1138 $info = acym_translation('ACYM_BOUNCE_RULE').' ['.acym_translation('ACYM_ID').' '.$oneRule->id.'] '.acym_translation($oneRule->name);
1139 if (!empty($this->_message->html)) {
1140 $this->mailer->isHTML();
1141 if (!empty($this->_message->userid)) {
1142 $info .= ' ('.acym_translation('ACYM_USER_ID').': '.$this->_message->userid.')';
1143 } elseif (!empty($this->_message->subemail)) {
1144 $info .= ' ('.acym_translation('ACYM_USER_EMAIL').': '.$this->_message->subemail.')';
1145 }
1146
1147 $this->mailer->Body = $info.'<br />'.$this->_message->html;
1148 if (!empty($this->_message->text)) {
1149 $this->mailer->Body .= '<br /><br />-------<br />'.nl2br($this->_message->text);
1150 }
1151 } else {
1152 $this->mailer->isHTML(false);
1153 $this->mailer->Body = $info."\n".$this->_message->text;
1154 }
1155
1156 $this->mailer->Body .= print_r($this->_message->headerinfo, true);
1157 $replyAddress = trim(@$this->_message->header->reply_to_email, '<> ');
1158 if (!empty($replyAddress)) {
1159 if ($this->config->get('mailer_method') === \plgAcymAcymailer::SENDING_METHOD_ID) {
1160 $this->mailer->AddReplyTo($this->config->get('replyto_email'), $this->config->get('replyto_name'));
1161 } else {
1162 $this->mailer->AddReplyTo(trim($this->_message->header->reply_to_email, '<> '), $this->_message->header->reply_to_name);
1163 }
1164 }
1165
1166 $this->mailer->isForward = true;
1167 if ($this->mailer->send()) {
1168 $message .= ' | '.acym_translationSprintf('ACYM_FORWARDED_TO_X', $oneRule->action_message['forward_to']);
1169 } else {
1170 $message .= ' | '.acym_translationSprintf('ACYM_NOT_FORWARDED_TO_X', $oneRule->action_message['forward_to'], $this->mailer->reportMessage);
1171 $donotdelete = true;
1172 }
1173 }
1174
1175 if (in_array('delete_message', $oneRule->action_message) && !$donotdelete) {
1176 $message .= ' | '.acym_translation('ACYM_MESSAGE_DELETED');
1177 $this->deleteMessage($this->_message->messageNB);
1178 }
1179
1180 return $message;
1181 }
1182
1183 private function decodeAddressImap($type)
1184 {
1185 $address = $type.'address';
1186 $name = $type.'_name';
1187 $email = $type.'_email';
1188 if (empty($this->_message->$type)) {
1189 return false;
1190 }
1191
1192 $var = $this->_message->$type;
1193
1194 if (empty($this->_message->header)) {
1195 $this->_message->header = new \stdClass();
1196 }
1197
1198 if (!empty($this->_message->$address)) {
1199 $this->_message->header->$name = $this->_message->$address;
1200 } else {
1201 $this->_message->header->$name = $var[0]->personal;
1202 }
1203
1204 $this->_message->header->$email = $var[0]->mailbox.'@'.@$var[0]->host;
1205
1206 return true;
1207 }
1208
1209
1210 protected function display($message, $success = true, $num = '')
1211 {
1212 $this->messages[] = $message;
1213
1214 if (!$this->report) return;
1215
1216 $color = $success ? 'green' : 'blue';
1217 if (!empty($num)) {
1218 echo '<br />'.$num.' : ';
1219 } else {
1220 echo '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
1221 }
1222
1223 echo '<span style="color: '.$color.'">'.$message.'</span>';
1224
1225 if (function_exists('ob_flush')) {
1226 @ob_flush();
1227 }
1228 if (!$this->mod_security2) {
1229 @flush();
1230 }
1231 }
1232
1233 public function decodeHeader($input)
1234 {
1235 $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
1236 $currentCharset = false;
1237
1238 while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
1239
1240 $encoded = $matches[1];
1241 $charset = $matches[2];
1242 $encoding = $matches[3];
1243 $text = $matches[4];
1244
1245 switch (strtolower($encoding)) {
1246 case 'b':
1247 $text = base64_decode($text);
1248 break;
1249
1250 case 'q':
1251 $text = str_replace('_', ' ', $text);
1252 preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
1253 foreach ($matches[1] as $value) {
1254 $text = str_replace('='.$value, chr(hexdec($value)), $text);
1255 }
1256 break;
1257 }
1258 $currentCharset = $charset;
1259 $input = str_replace($encoded, $text, $input);
1260 }
1261
1262 if (!empty($currentCharset) && in_array($currentCharset, $this->allCharsets)) {
1263 $input = $this->encodingHelper->change($input, $currentCharset, 'UTF-8');
1264 }
1265
1266 return $input;
1267 }
1268
1269 private function explodeBodyMixed($struct, $path = "1")
1270 {
1271 $allParts = [];
1272
1273 if (empty($struct->parts)) {
1274 return $allParts;
1275 }
1276
1277 $pathPrefix = ($struct->subtype == "MIXED" && $path == '1') ? '' : $path.'.';
1278 foreach ($struct->parts as $i => $part) {
1279 $partPath = $pathPrefix.($i + 1);
1280 if ($part->type == 1) {
1281 $allParts = array_merge($this->explodeBodyMixed($part, $partPath), $allParts);
1282 } else {
1283 $allParts[$partPath] = $part;
1284 }
1285 }
1286
1287 return $allParts;
1288 }
1289
1290 private function explodeBody($struct, $path = "0", $inline = 0)
1291 {
1292 $allParts = [];
1293
1294 if (empty($struct->parts)) {
1295 return $allParts;
1296 }
1297
1298 $c = 0; //counts real content
1299 foreach ($struct->parts as $part) {
1300 if ($part->type == 1) {
1301 if ($part->subtype == "MIXED") { //Mixed:
1302 $path = $this->incPath($path, 1); //refreshing current path
1303 $newpath = $path.".0"; //create a new path-id (ex.:2.0)
1304 $allParts = array_merge($this->explodeBody($part, $newpath), $allParts); //fetch new parts
1305 } else { //Alternativ / rfc / signed
1306 $newpath = $this->incPath($path, 1);
1307 $path = $this->incPath($path, 1);
1308 $allParts = array_merge($this->explodeBody($part, $newpath, 1), $allParts);
1309 }
1310 } else {
1311 $c++;
1312 if ($c == 1 && $inline) {
1313 $path = $path.".0";
1314 }
1315 $path = $this->incPath($path, 1);
1316 $allParts[$path] = $part;
1317 }
1318 }
1319
1320 return $allParts;
1321 }
1322
1323 private function incPath($path, $inc)
1324 {
1325 $newPath = '';
1326 $path_elements = explode(".", $path);
1327 $limit = count($path_elements);
1328 for ($i = 0 ; $i < $limit ; $i++) {
1329 if ($i == $limit - 1) { //last element
1330 $newPath .= $path_elements[$i] + $inc; // new Part-Number
1331 } else {
1332 $newPath .= $path_elements[$i]."."; //rebuild "1.2.2"-Chronology
1333 }
1334 }
1335
1336 return $newPath;
1337 }
1338
1339 private function decodeContent($content, $structure)
1340 {
1341 $encoding = $structure->encoding;
1342
1343 if ($encoding == 2) {
1344 $content = imap_binary($content);
1345 } elseif ($encoding == 3) {
1346 $content = imap_base64($content);
1347 } elseif ($encoding == 4) {
1348 $content = imap_qprint($content);
1349 }
1350
1351 if (!empty($this->action)) {
1352 $charset = $this->getMailParam($structure, 'charset');
1353 if (!empty($charset) && strtoupper($charset) != 'UTF-8') {
1354 $content = $this->encodingHelper->change($content, $charset, 'UTF-8');
1355 }
1356
1357 return $content;
1358 }
1359
1360 return substr($content, 0, 100000);
1361 }
1362
1363 private function getMailParam($params, $name)
1364 {
1365 $searchIn = [];
1366
1367 if ($params->ifparameters) {
1368 $searchIn = array_merge($searchIn, $params->parameters);
1369 }
1370 if ($params->ifdparameters) {
1371 $searchIn = array_merge($searchIn, $params->dparameters);
1372 }
1373
1374 if (empty($searchIn)) {
1375 return false;
1376 }
1377
1378 foreach ($searchIn as $num => $values) {
1379 if (strtolower($values->attribute) == $name) {
1380 return $values->value;
1381 }
1382 }
1383 }
1384
1385 public function getErrors()
1386 {
1387 $return = [];
1388 if (!$this->usePear) {
1389 if (!function_exists('imap_alerts')) {
1390 $return[] = 'The IMAP extension could not be loaded, please change your PHP configuration to enable it or use the pop3 method without imap extension';
1391
1392 return $return;
1393 }
1394 $alerts = $this->callImapFunction('imap_alerts', []);
1395 $errors = $this->callImapFunction('imap_errors', []);
1396 if (!empty($alerts)) {
1397 $return = array_merge($return, $alerts);
1398 }
1399 if (!empty($errors)) {
1400 $return = array_merge($return, $errors);
1401 }
1402 }
1403
1404 return $return;
1405 }
1406
1407 private function refreshToken(): void
1408 {
1409 if ((int)$this->config->get('bounce_token_expireIn') > time()) {
1410 return;
1411 }
1412
1413 if ($this->config->get('bounce_server') === 'imap.gmail.com') {
1414 $url = 'https://oauth2.googleapis.com/token';
1415 } else {
1416 $tenant = $this->config->get('bounce_tenant');
1417 if (empty($tenant)) {
1418 acym_enqueueMessage(acym_translation('ACYM_TENANT_FIELD_IS_MISSING'), 'error');
1419 }
1420 $url = 'https://login.microsoftonline.com/'.$tenant.'/oauth2/v2.0/token';
1421 }
1422
1423 $requestOptions = [
1424 'method' => 'POST',
1425 'data' => [
1426 'client_id' => $this->config->get('bounce_client_id'),
1427 'grant_type' => 'refresh_token',
1428 'refresh_token' => $this->config->get('bounce_refresh_token'),
1429 'client_secret' => $this->config->get('bounce_client_secret'),
1430 ],
1431 ];
1432 $response = acym_makeCurlCall(
1433 $url,
1434 $requestOptions
1435 );
1436
1437 acym_logError('Response from OAuth refresh token call: '.json_encode($response), 'imap_oauth', 100);
1438
1439 if (empty($response['error'])) {
1440 $token = $response['token_type'].' '.$response['access_token'];
1441 $expireIn = time() + (int)$response['expires_in'];
1442 $this->config->save(['bounce_token' => $token, 'bounce_token_expireIn' => $expireIn]);
1443
1444 $this->oAuthToken = $token;
1445 } else {
1446 acym_enqueueMessage(acym_translationSprintf('ACYM_OAUTH_REFRESH_TOKEN_ERROR', $response['error']), 'error');
1447 }
1448 }
1449
1450 private function callImapFunction($functionName, $params = [])
1451 {
1452 if ($this->imapConnectionMethod === 'oauth') {
1453 $functionName = str_replace('imap_', 'imap2_', $functionName);
1454 }
1455
1456 if (empty($params)) {
1457 return $functionName();
1458 }
1459
1460 return $functionName(...$params);
1461 }
1462 }
1463