PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 10.11.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v10.11.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 / QueueHelper.php

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

577 lines 20.8 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\MailStatClass;
6 use AcyMailing\Classes\QueueClass;
7 use AcyMailing\Classes\UserClass;
8 use AcyMailing\Classes\UserStatClass;
9 use AcyMailing\Core\AcymObject;
10
11 class QueueHelper extends AcymObject
12 {
13 const MESSAGE_TYPE_SUCCESS = 1;
14 const MESSAGE_TYPE_WARNING = 2;
15 const MESSAGE_TYPE_ERROR = 3;
16
17 public bool $report = true;
18 public bool $finish = false;
19 public bool $fromManual = false;
20 public int $id = 0;
21 public int $send_limit = 0;
22 public int $nbprocess = 0;
23 public int $startSendingFrom = 0;
24 public int $start = 0;
25 public int $stoptime = 0;
26 public int $successSend = 0;
27 public int $errorSend = 0;
28 public int $pause = 0;
29 public int $total;
30 public array $messages = [];
31 public array $emailTypes = [];
32
33 private int $consecutiveError = 0;
34 private bool $mod_security2 = false;
35 private int $obend = 0;
36
37 private UserClass $userClass;
38 private QueueClass $queueClass;
39
40 public function __construct()
41 {
42 parent::__construct();
43
44 $this->queueClass = new QueueClass();
45 $this->userClass = new UserClass();
46
47 $this->send_limit = (int)$this->config->get('queue_nbmail', 40);
48
49 @ini_set('default_socket_timeout', 10);
50
51 @ignore_user_abort(true);
52
53 $timeLimit = acym_increasePerf();
54 if (empty($timeLimit)) {
55 $timeLimit = 600;
56 }
57
58 $this->stoptime = time() + $timeLimit - 4;
59 }
60
61 public function process(): bool
62 {
63 $creditsLeft = 10000;
64 acym_trigger('onAcymCreditsLeft', [&$creditsLeft]);
65
66 if (empty($creditsLeft)) {
67 $this->finish = true;
68 if ($this->report) {
69 acym_display(acym_translation('ACYM_NOT_ENOUGH_CREDITS'), 'info');
70 }
71
72 return false;
73 }
74
75 if ($this->send_limit > $creditsLeft) {
76 $this->send_limit = $creditsLeft;
77 }
78
79 $queueClass = new QueueClass();
80 $queueClass->emailtypes = $this->emailTypes;
81 $queueElements = $queueClass->getReady($this->startSendingFrom, $this->send_limit, $this->id);
82
83 if (empty($queueElements)) {
84 $this->finish = true;
85 if ($this->report) {
86 acym_display(acym_translation('ACYM_NO_PROCESS'), 'info');
87 }
88
89 return true;
90 }
91
92 if ($this->report) {
93 if (function_exists('apache_get_modules')) {
94 $modules = apache_get_modules();
95 $this->mod_security2 = in_array('mod_security2', $modules);
96 }
97
98 @ini_set('output_buffering', 'off');
99 @ini_set('zlib.output_compression', 0);
100
101 if (!headers_sent()) {
102 while (ob_get_level() > 0 && $this->obend++ < 3) {
103 @ob_end_flush();
104 }
105 }
106
107 $disp = '<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />';
108 $disp .= '<title>'.acym_translation('ACYM_SEND_PROCESS').'</title>';
109 $disp .= '<style>body{font-size:12px;font-family: Arial,Helvetica,sans-serif;}</style></head><body>';
110 $disp .= '<div style="margin-bottom: 18px;padding: 8px !important; background-color: #fcf8e3; border: 1px solid #fbeed5; border-radius: 4px;"><p style="margin:0;">'.acym_translation(
111 'ACYM_DONT_CLOSE'
112 ).'</p></div>';
113 $disp .= "<div style='display: inline;background-color : white;border : 1px solid grey; padding : 3px;font-size:14px'>";
114 $disp .= "<span id='divpauseinfo' style='padding:10px;margin:5px;font-size:16px;font-weight:bold;display:none;background-color:black;color:white;'> </span>";
115 $disp .= acym_translation('ACYM_SEND_PROCESS').': <span id="counter" >'.$this->start.'</span> / '.$this->total;
116 $disp .= '</div>';
117 $disp .= "<div id='divinfo' style='display:none; position:fixed; bottom:3px;left:3px;background-color : white; border : 1px solid grey; padding : 3px;'> </div>";
118 $disp .= '<br /><br />';
119 $url = acym_completeLink('queue&task=continuesend&id='.$this->id.'&totalsend='.$this->total, true, true).'&alreadysent=';
120 $disp .= '<script type="text/javascript" language="javascript">';
121 $disp .= 'var mycounter = document.getElementById("counter");';
122 $disp .= 'var divinfo = document.getElementById("divinfo");
123 var divpauseinfo = document.getElementById("divpauseinfo");
124 function setInfo(message){ divinfo.style.display = \'block\';divinfo.innerHTML=message; }
125 function setPauseInfo(nbpause){ divpauseinfo.style.display = \'\';divpauseinfo.innerHTML=nbpause;}
126 function setCounter(val){ mycounter.innerHTML=val;}
127 var scriptpause = '.$this->pause.';
128 function handlePause(){
129 setPauseInfo(scriptpause);
130 if(scriptpause > 0){
131 scriptpause = scriptpause - 1;
132 setTimeout(\'handlePause()\',1000);
133 }else{
134 document.location.href=\''.$url.'\'+mycounter.innerHTML;
135 }
136 }
137 </script>';
138 echo $disp;
139 if (function_exists('ob_flush')) {
140 @ob_flush();
141 }
142 if (!$this->mod_security2) {
143 @flush();
144 }
145 }
146
147 $mailerHelper = new MailerHelper();
148 $mailerHelper->report = false;
149 if ($this->config->get('smtp_keepalive', 1) || $this->config->get('mailer_method') === 'elasticemail') {
150 $mailerHelper->SMTPKeepAlive = true;
151 }
152
153 $queueDelete = [];
154 $queueUpdate = [];
155 $statsAdd = [];
156
157 $maxTry = (int)$this->config->get('queue_try', 0);
158
159 $currentMail = $this->start;
160 $this->nbprocess = 0;
161
162 if (count($queueElements) < $this->send_limit) {
163 $this->finish = true;
164 }
165
166 $externalSending = false;
167 acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$externalSending]);
168
169 if ($externalSending) {
170 $listExternalSending = [];
171 if (!empty($this->id)) {
172 acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $this->id]);
173 } else {
174 $mailIds = [];
175 foreach ($queueElements as $oneElement) {
176 if (!in_array($oneElement->mail_id, $mailIds)) {
177 $mailIds[] = $oneElement->mail_id;
178 acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $oneElement->mail_id]);
179 }
180 }
181 }
182 }
183
184 $sentEmails = [];
185 $emailFrequency = $this->fromManual ? 0 : intval($this->config->get('email_frequency', 0));
186 if ($emailFrequency < 0) {
187 $emailFrequency = 0;
188 }
189
190 $userLists = [];
191 $userClass = new UserClass();
192 $isSendingMethodByListActive = false;
193 acym_trigger('sendingMethodByListActive', [&$isSendingMethodByListActive]);
194 $mailerHelper->isSendingMethodByListActive = $isSendingMethodByListActive;
195
196 $statisticsByBatch = $this->config->get('queue_statistics_by_batch', 1) == 1;
197
198 foreach ($queueElements as $oneQueue) {
199 if ($isSendingMethodByListActive && empty($userLists[$oneQueue->user_id])) {
200 $userSubscriptions = $userClass->getUserStandardListIdById($oneQueue->user_id);
201 $userLists[$oneQueue->user_id] = empty($userSubscriptions) ? [] : $userSubscriptions;
202 }
203 if (!empty($emailFrequency)) {
204 sleep($emailFrequency);
205 }
206
207 $currentMail++;
208 $this->nbprocess++;
209 if ($this->report) {
210 echo '<script type="text/javascript">setCounter('.$currentMail.')</script>';
211 if (function_exists('ob_flush')) {
212 @ob_flush();
213 }
214 if (!$this->mod_security2) {
215 @flush();
216 }
217 }
218
219 $isAbTesting = false;
220
221 if (!empty($oneQueue->sending_params)) {
222 $sendingParams = json_decode($oneQueue->sending_params, true);
223
224 $isAbTesting = isset($sendingParams['abtest']);
225 }
226
227 $this->triggerSendHook((int)$oneQueue->mail_id);
228 try {
229 $mailerHelper->isAbTest = $isAbTesting;
230 $mailerHelper->listsIds = empty($userLists[$oneQueue->user_id]) ? [] : $userLists[$oneQueue->user_id];
231 $mailerHelper->setSendingMethodSetting();
232 $result = $mailerHelper->sendOne($oneQueue->mail_id, $oneQueue->user_id);
233 } catch (\Exception $e) {
234 $result = false;
235 $this->displayMessage([$e->getMessage()], self::MESSAGE_TYPE_ERROR, $e->getCode());
236 }
237
238 if (empty($sentEmails[$oneQueue->mail_id])) {
239 $sentEmails[$oneQueue->mail_id] = $mailerHelper->Body;
240 }
241
242 $queueDeleteOk = true;
243 $otherMessage = '';
244
245 if ($result === true) {
246 $this->successSend++;
247 $this->consecutiveError = 0;
248 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
249 $statsAdd[$oneQueue->mail_id][1][] = $oneQueue->user_id;
250
251 $queueDeleteOk = $this->deleteQueue($queueDelete);
252 $mailerHelper->triggerFollowUpAgain($oneQueue->mail_id, $oneQueue->user_id);
253 $queueDelete = [];
254
255 if (!$statisticsByBatch || $this->nbprocess % 10 == 0) {
256 $this->statsAdd($statsAdd);
257 $this->queueUpdate($queueUpdate);
258 $statsAdd = [];
259 $queueUpdate = [];
260 }
261 } elseif ($mailerHelper->dtextsFailed) {
262 $this->consecutiveError = 0;
263 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
264 $queueDeleteOk = $this->deleteQueue($queueDelete);
265 $queueDelete = [];
266 } else {
267 acym_trigger('onAcymFailedSendingEmail', [$oneQueue->mail_id, $oneQueue->user_id, $mailerHelper->errorNumber]);
268 $this->errorSend++;
269
270 $shouldTrySendingLater = false;
271 if (in_array($mailerHelper->errorNumber, MailerHelper::NEW_TRY_ERRORS)) {
272 if (empty($maxTry) || $oneQueue->try < $maxTry - 1) {
273 $shouldTrySendingLater = true;
274 if ($mailerHelper->failedCounting) {
275 $otherMessage = acym_translationSprintf('ACYM_QUEUE_NEXT_TRY', 60);
276 }
277 }
278
279 if ($mailerHelper->errorNumber === 1) {
280 $this->consecutiveError++;
281 }
282
283 if ($this->consecutiveError === 2) {
284 sleep(1);
285 }
286 }
287
288 if (!$shouldTrySendingLater) {
289 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
290 $statsAdd[$oneQueue->mail_id][0][] = $oneQueue->user_id;
291 if ($mailerHelper->errorNumber === 1 && $this->config->get('bounce_action_maxtry')) {
292 $queueDeleteOk = $this->deleteQueue($queueDelete);
293 $queueDelete = [];
294 $otherMessage .= $this->failedActions((int)$oneQueue->user_id);
295 }
296 } elseif ($mailerHelper->failedCounting) {
297 $queueUpdate[$oneQueue->mail_id][] = $oneQueue->user_id;
298 }
299
300 $mailerHelper->failedCounting = true;
301 }
302
303 $messageOnScreen = '[ID '.$oneQueue->mail_id.'] '.$mailerHelper->reportMessage;
304 if (!empty($otherMessage)) {
305 $messageOnScreen .= ' => '.$otherMessage;
306 }
307 $this->displayMessage([$messageOnScreen], $result ? self::MESSAGE_TYPE_SUCCESS : self::MESSAGE_TYPE_ERROR, $currentMail);
308
309 if (!$queueDeleteOk) {
310 $this->finish = true;
311 break;
312 }
313
314 if (!empty($this->stoptime) && $this->stoptime < time()) {
315 $this->displayMessage([acym_translation('ACYM_SEND_REFRESH_TIMEOUT')], self::MESSAGE_TYPE_WARNING);
316 if ($this->nbprocess < count($queueElements)) {
317 $this->finish = false;
318 }
319 break;
320 }
321
322 if ($this->consecutiveError > 3 && $this->successSend > 3) {
323 $this->displayMessage([acym_translation('ACYM_SEND_REFRESH_CONNECTION')], self::MESSAGE_TYPE_WARNING);
324 break;
325 }
326
327 if ($this->consecutiveError > 5 || ($this->report && connection_aborted())) {
328 $this->finish = true;
329 break;
330 }
331 }
332
333 if ($externalSending) {
334 foreach ($sentEmails as $mailId => $content) {
335 if (!$queueClass->isSendingFinished($mailId)) continue;
336
337 acym_trigger('onAcymSendCampaignOnExternalSendingMethod', [$mailId, $content]);
338 }
339 }
340
341 $this->deleteQueue($queueDelete);
342 $this->statsAdd($statsAdd);
343 $this->queueUpdate($queueUpdate);
344
345 if ($mailerHelper->SMTPKeepAlive) {
346 $mailerHelper->smtpClose();
347 }
348
349 if (!empty($this->total) && $currentMail >= $this->total) {
350 $this->finish = true;
351 }
352
353 if ($this->consecutiveError > 5) {
354 $this->handleError();
355
356 return false;
357 }
358
359 if ($this->report && !$this->finish) {
360 echo '<script type="text/javascript" language="javascript">handlePause();</script>';
361 }
362
363 if ($this->report) {
364 echo '</body></html>';
365 while ($this->obend-- > 0) {
366 ob_start();
367 }
368 exit;
369 }
370
371 return true;
372 }
373
374 public function statsAdd(array $statsAdd): void
375 {
376 if (empty($statsAdd)) {
377 return;
378 }
379
380 $userStatClass = new UserStatClass();
381 $mailStatClass = new MailStatClass();
382
383 $currentDate = acym_date('now', 'Y-m-d H:i:s', false);
384 $subscriberIds = [];
385
386 foreach ($statsAdd as $mailId => $infos) {
387 $mailId = intval($mailId);
388
389 foreach ($infos as $status => $subscribers) {
390 foreach ($subscribers as $oneSubscriber) {
391 $oneSubscriber = intval($oneSubscriber);
392
393 $userStat = new \stdClass();
394 $userStat->user_id = $oneSubscriber;
395 $userStat->mail_id = $mailId;
396 $userStat->send_date = $currentDate;
397 $userStat->fail = $status ? 0 : 1;
398 $userStat->sent = $status ? 1 : 0;
399 $userStat->statusSending = $status;
400
401 $userStatClass->save($userStat, true);
402
403 if ($status) {
404 $subscriberIds[] = $oneSubscriber;
405 }
406 }
407 }
408
409 $nbSent = empty($infos[1]) ? 0 : count($infos[1]);
410 $nbFail = empty($infos[0]) ? 0 : count($infos[0]);
411
412 $mailStat = new \stdClass();
413 $mailStat->mail_id = $mailId;
414 $mailStat->sent = $nbSent;
415 $mailStat->fail = $nbFail;
416
417 $mailStatClass->save($mailStat);
418 }
419
420 if (!empty($subscriberIds)) {
421 acym_query('UPDATE `#__acym_user` SET `last_sent_date` = '.acym_escapeDB($currentDate).' WHERE `id` IN ('.implode(',', $subscriberIds).')');
422 }
423 }
424
425 private function triggerSendHook(int $mailId): void
426 {
427 static $triggered = [];
428 if (!empty($triggered[$mailId])) {
429 return;
430 }
431
432 $triggered[$mailId] = true;
433 acym_triggerCmsHook('onAcymSendMail', [$mailId]);
434 }
435
436 private function deleteQueue(array $queueDelete): bool
437 {
438 if (empty($queueDelete)) {
439 return true;
440 }
441
442 $status = true;
443
444 foreach ($queueDelete as $mailId => $userIds) {
445 $nbUsers = count($userIds);
446 $nbDeleted = $this->queueClass->deleteQueuedByUserIds($userIds, $mailId);
447
448 if (!empty($this->queueClass->errors)) {
449 $status = false;
450 $this->displayMessage($this->queueClass->errors, self::MESSAGE_TYPE_ERROR);
451 } else {
452 if ($nbDeleted !== $nbUsers) {
453 $status = false;
454 $this->displayMessage(
455 [$nbDeleted < $nbUsers ? acym_translation('ACYM_QUEUE_DOUBLE') : $nbDeleted.' emails deleted from the queue whereas we only have '.$nbUsers.' subscribers'],
456 self::MESSAGE_TYPE_ERROR
457 );
458 }
459 }
460 }
461
462 return $status;
463 }
464
465 private function queueUpdate(array $queueUpdate): void
466 {
467 if (empty($queueUpdate)) {
468 return;
469 }
470
471 foreach ($queueUpdate as $mailid => $subscribers) {
472 $this->queueClass->delayFailed($mailid, $subscribers);
473 }
474 }
475
476 private function handleError(): void
477 {
478 $this->finish = true;
479 $message = acym_translation('ACYM_SEND_STOPED');
480 $message .= '<br />';
481 $message .= acym_translation('ACYM_SEND_KEPT_ALL');
482 $message .= '<br />';
483 if ($this->report) {
484 if (empty($this->successSend) && empty($this->start)) {
485 $message .= acym_translation('ACYM_SEND_CHECKONE');
486 $message .= '<br />';
487 $message .= acym_translation('ACYM_SEND_ADVISE_LIMITATION');
488 } else {
489 $message .= acym_translation('ACYM_SEND_REFUSE');
490 $message .= '<br />';
491 if (!acym_level(ACYM_ESSENTIAL)) {
492 $message .= acym_translation('ACYM_SEND_CONTINUE_COMMERCIAL');
493 } else {
494 $message .= acym_translation('ACYM_SEND_CONTINUE_AUTO');
495 }
496 }
497 }
498
499 $this->displayMessage([$message], self::MESSAGE_TYPE_WARNING);
500 }
501
502 private function displayMessage(array $messages, int $status, int $num = 0): void
503 {
504 foreach ($messages as $message) {
505 $this->messages[] = strip_tags($message);
506 }
507
508 if (!$this->report) {
509 return;
510 }
511
512 $color = $status === self::MESSAGE_TYPE_SUCCESS ? 'green' : ($status === self::MESSAGE_TYPE_WARNING ? 'orange' : 'red');
513 foreach ($messages as $message) {
514 if (!empty($num)) {
515 echo '<br />'.$num.' : <span style="color:'.$color.';">'.$message.'</span>';
516 } else {
517 echo '<script type="text/javascript" language="javascript">setInfo(\''.addslashes($message).'\')</script>';
518 }
519 }
520
521 if (function_exists('ob_flush')) {
522 @ob_flush();
523 }
524 if (!$this->mod_security2) {
525 @flush();
526 }
527 }
528
529 private function failedActions(int $userId): string
530 {
531 $actionMaxTries = $this->config->get('bounce_action_maxtry');
532 if (in_array($actionMaxTries, ['sub', 'remove', 'unsub'])) {
533 $subscriptions = $this->userClass->getUserSubscriptionById($userId);
534 }
535
536 $listId = 0;
537 $message = '';
538 switch ($actionMaxTries) {
539 case 'sub':
540 $listId = $this->config->get('bounce_action_lists_maxtry');
541 if (!empty($listId)) {
542 $message .= ' user '.$userId.' subscribed to list n°'.$listId;
543 $this->userClass->subscribe([$userId], [$listId]);
544 }
545 case 'remove':
546 $unsubLists = array_diff(array_keys($subscriptions), [$listId]);
547 if (!empty($unsubLists)) {
548 $message .= ' user '.$userId.' removed from lists '.implode(',', $unsubLists);
549 $this->userClass->removeSubscription([$userId], $unsubLists);
550 } else {
551 $message .= ' user '.$userId.' not subscribed';
552 }
553 break;
554 case 'unsub':
555 $unsubLists = array_diff(array_keys($subscriptions), [$listId]);
556 if (!empty($unsubLists)) {
557 $message .= ' user '.$userId.' unsubscribed from lists '.implode(',', $unsubLists);
558 $this->userClass->unsubscribe([$userId], $unsubLists);
559 } else {
560 $message .= ' user '.$userId.' not unsubscribed';
561 }
562 break;
563 case 'delete':
564 $message .= ' user '.$userId.' deleted';
565 $this->userClass->delete([$userId]);
566 break;
567 case 'block':
568 $message .= ' user '.$userId.' blocked';
569 $this->userClass->deactivate([$userId]);
570 $this->queueClass->deleteQueuedByUserIds([$userId]);
571 break;
572 }
573
574 return $message;
575 }
576 }
577