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

578 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 $start = 0;
24 public int $stoptime = 0;
25 public int $successSend = 0;
26 public int $errorSend = 0;
27 public int $pause = 0;
28 public int $total;
29 public array $messages = [];
30 public array $emailTypes = [];
31
32 private int $consecutiveError = 0;
33 private bool $mod_security2 = false;
34 private int $obend = 0;
35
36 private UserClass $userClass;
37 private QueueClass $queueClass;
38
39 public function __construct()
40 {
41 parent::__construct();
42
43 $this->queueClass = new QueueClass();
44 $this->userClass = new UserClass();
45
46 $this->send_limit = (int)$this->config->get('queue_nbmail', 40);
47
48 acym_increasePerf();
49
50 @ini_set('default_socket_timeout', 10);
51
52 @ignore_user_abort(true);
53
54 $timelimit = intval(ini_get('max_execution_time'));
55 if (empty($timelimit)) {
56 $timelimit = 600;
57 }
58
59 if (!empty($timelimit)) {
60 $this->stoptime = time() + $timelimit - 4;
61 }
62 }
63
64 public function process(): bool
65 {
66 $creditsLeft = 10000;
67 acym_trigger('onAcymCreditsLeft', [&$creditsLeft]);
68
69 if (empty($creditsLeft)) {
70 $this->finish = true;
71 if ($this->report) {
72 acym_display(acym_translation('ACYM_NOT_ENOUGH_CREDITS'), 'info');
73 }
74
75 return false;
76 }
77 if ($this->send_limit > $creditsLeft) {
78 $this->send_limit = $creditsLeft;
79 }
80
81 $queueClass = new QueueClass();
82 $queueClass->emailtypes = $this->emailTypes;
83 $queueElements = $queueClass->getReady($this->send_limit, $this->id);
84
85 if (empty($queueElements)) {
86 $this->finish = true;
87 if ($this->report) {
88 acym_display(acym_translation('ACYM_NO_PROCESS'), 'info');
89 }
90
91 return true;
92 }
93
94 if ($this->report) {
95 if (function_exists('apache_get_modules')) {
96 $modules = apache_get_modules();
97 $this->mod_security2 = in_array('mod_security2', $modules);
98 }
99
100 @ini_set('output_buffering', 'off');
101 @ini_set('zlib.output_compression', 0);
102
103 if (!headers_sent()) {
104 while (ob_get_level() > 0 && $this->obend++ < 3) {
105 @ob_end_flush();
106 }
107 }
108
109 $disp = '<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" />';
110 $disp .= '<title>'.acym_translation('ACYM_SEND_PROCESS').'</title>';
111 $disp .= '<style>body{font-size:12px;font-family: Arial,Helvetica,sans-serif;}</style></head><body>';
112 $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(
113 'ACYM_DONT_CLOSE'
114 ).'</p></div>';
115 $disp .= "<div style='display: inline;background-color : white;border : 1px solid grey; padding : 3px;font-size:14px'>";
116 $disp .= "<span id='divpauseinfo' style='padding:10px;margin:5px;font-size:16px;font-weight:bold;display:none;background-color:black;color:white;'> </span>";
117 $disp .= acym_translation('ACYM_SEND_PROCESS').': <span id="counter" >'.$this->start.'</span> / '.$this->total;
118 $disp .= '</div>';
119 $disp .= "<div id='divinfo' style='display:none; position:fixed; bottom:3px;left:3px;background-color : white; border : 1px solid grey; padding : 3px;'> </div>";
120 $disp .= '<br /><br />';
121 $url = acym_completeLink('queue&task=continuesend&id='.$this->id.'&totalsend='.$this->total, true, true).'&alreadysent=';
122 $disp .= '<script type="text/javascript" language="javascript">';
123 $disp .= 'var mycounter = document.getElementById("counter");';
124 $disp .= 'var divinfo = document.getElementById("divinfo");
125 var divpauseinfo = document.getElementById("divpauseinfo");
126 function setInfo(message){ divinfo.style.display = \'block\';divinfo.innerHTML=message; }
127 function setPauseInfo(nbpause){ divpauseinfo.style.display = \'\';divpauseinfo.innerHTML=nbpause;}
128 function setCounter(val){ mycounter.innerHTML=val;}
129 var scriptpause = '.$this->pause.';
130 function handlePause(){
131 setPauseInfo(scriptpause);
132 if(scriptpause > 0){
133 scriptpause = scriptpause - 1;
134 setTimeout(\'handlePause()\',1000);
135 }else{
136 document.location.href=\''.$url.'\'+mycounter.innerHTML;
137 }
138 }
139 </script>';
140 echo $disp;
141 if (function_exists('ob_flush')) {
142 @ob_flush();
143 }
144 if (!$this->mod_security2) {
145 @flush();
146 }
147 }
148
149 $mailerHelper = new MailerHelper();
150 $mailerHelper->report = false;
151 if ($this->config->get('smtp_keepalive', 1) || in_array($this->config->get('mailer_method'), ['elasticemail'])) {
152 $mailerHelper->SMTPKeepAlive = true;
153 }
154
155 $queueDelete = [];
156 $queueUpdate = [];
157 $statsAdd = [];
158
159 $maxTry = (int)$this->config->get('queue_try', 0);
160
161 $currentMail = $this->start;
162 $this->nbprocess = 0;
163
164 if (count($queueElements) < $this->send_limit) {
165 $this->finish = true;
166 }
167
168 $externalSending = false;
169 acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$externalSending]);
170
171 if ($externalSending) {
172 $listExternalSending = [];
173 if (!empty($this->id)) {
174 acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $this->id]);
175 } else {
176 $mailIds = [];
177 foreach ($queueElements as $oneElement) {
178 if (!in_array($oneElement->mail_id, $mailIds)) {
179 $mailIds[] = $oneElement->mail_id;
180 acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $oneElement->mail_id]);
181 }
182 }
183 }
184 }
185
186 $sentEmails = [];
187 $emailFrequency = $this->fromManual ? 0 : intval($this->config->get('email_frequency', 0));
188 if ($emailFrequency < 0) {
189 $emailFrequency = 0;
190 }
191
192 $userLists = [];
193 $userClass = new UserClass();
194 $isSendingMethodByListActive = false;
195 acym_trigger('sendingMethodByListActive', [&$isSendingMethodByListActive]);
196 $mailerHelper->isSendingMethodByListActive = $isSendingMethodByListActive;
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->triggerSentHook((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 ($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');
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
392 $oneSubscriber = intval($oneSubscriber);
393
394 $userStat = [];
395 $userStat['user_id'] = $oneSubscriber;
396 $userStat['mail_id'] = $mailId;
397 $userStat['send_date'] = $currentDate;
398 $userStat['fail'] = $status ? 0 : 1;
399 $userStat['sent'] = $status ? 1 : 0;
400 $userStat['statusSending'] = $status;
401
402 $userStatClass->save($userStat, true);
403
404 if ($status) {
405 $subscriberIds[] = $oneSubscriber;
406 }
407 }
408 }
409
410 $nbSent = empty($infos[1]) ? 0 : count($infos[1]);
411 $nbFail = empty($infos[0]) ? 0 : count($infos[0]);
412
413 $mailStat = [
414 'mail_id' => $mailId,
415 'sent' => $nbSent,
416 'fail' => $nbFail,
417 ];
418
419 $mailStatClass->save($mailStat);
420 }
421
422 if (!empty($subscriberIds)) {
423 acym_query('UPDATE `#__acym_user` SET `last_sent_date` = '.acym_escapeDB($currentDate).' WHERE `id` IN ('.implode(',', $subscriberIds).')');
424 }
425 }
426
427 private function triggerSentHook(int $mailId): void
428 {
429 static $triggered = [];
430 if (!empty($triggered[$mailId])) {
431 return;
432 }
433
434 $triggered[$mailId] = true;
435 acym_triggerCmsHook('onAcymSendMail', [$mailId]);
436 }
437
438 private function deleteQueue(array $queueDelete): bool
439 {
440 if (empty($queueDelete)) {
441 return true;
442 }
443
444 $status = true;
445
446 foreach ($queueDelete as $mailid => $subscribers) {
447 $nbsub = count($subscribers);
448 $res = $this->queueClass->deleteOne($subscribers, $mailid);
449 if ($res === false || !empty($this->queueClass->errors)) {
450 $status = false;
451 $this->displayMessage($this->queueClass->errors, self::MESSAGE_TYPE_ERROR);
452 } else {
453 $nbdeleted = $res;
454 if ($nbdeleted != $nbsub) {
455 $status = false;
456 $this->displayMessage(
457 [$nbdeleted < $nbsub ? acym_translation('ACYM_QUEUE_DOUBLE') : $nbdeleted.' emails deleted from the queue whereas we only have '.$nbsub.' subscribers'],
458 self::MESSAGE_TYPE_ERROR
459 );
460 }
461 }
462 }
463
464 return $status;
465 }
466
467 private function queueUpdate(array $queueUpdate): void
468 {
469 if (empty($queueUpdate)) {
470 return;
471 }
472
473 foreach ($queueUpdate as $mailid => $subscribers) {
474 $this->queueClass->delayFailed($mailid, $subscribers);
475 }
476 }
477
478 private function handleError(): void
479 {
480 $this->finish = true;
481 $message = acym_translation('ACYM_SEND_STOPED');
482 $message .= '<br />';
483 $message .= acym_translation('ACYM_SEND_KEPT_ALL');
484 $message .= '<br />';
485 if ($this->report) {
486 if (empty($this->successSend) && empty($this->start)) {
487 $message .= acym_translation('ACYM_SEND_CHECKONE');
488 $message .= '<br />';
489 $message .= acym_translation('ACYM_SEND_ADVISE_LIMITATION');
490 } else {
491 $message .= acym_translation('ACYM_SEND_REFUSE');
492 $message .= '<br />';
493 if (!acym_level(ACYM_ESSENTIAL)) {
494 $message .= acym_translation('ACYM_SEND_CONTINUE_COMMERCIAL');
495 } else {
496 $message .= acym_translation('ACYM_SEND_CONTINUE_AUTO');
497 }
498 }
499 }
500
501 $this->displayMessage([$message], self::MESSAGE_TYPE_WARNING);
502 }
503
504 private function displayMessage(array $messages, int $status, int $num = 0): void
505 {
506 foreach ($messages as $message) {
507 $this->messages[] = strip_tags($message);
508 }
509
510 if (!$this->report) {
511 return;
512 }
513
514 $color = $status === self::MESSAGE_TYPE_SUCCESS ? 'green' : ($status === self::MESSAGE_TYPE_WARNING ? 'orange' : 'red');
515 foreach ($messages as $message) {
516 if (!empty($num)) {
517 echo '<br />'.$num.' : <span style="color:'.$color.';">'.$message.'</span>';
518 } else {
519 echo '<script type="text/javascript" language="javascript">setInfo(\''.addslashes($message).'\')</script>';
520 }
521 }
522
523 if (function_exists('ob_flush')) {
524 @ob_flush();
525 }
526 if (!$this->mod_security2) {
527 @flush();
528 }
529 }
530
531 private function failedActions(int $userId): string
532 {
533 $listId = 0;
534 if (in_array($this->config->get('bounce_action_maxtry'), ['sub', 'remove', 'unsub'])) {
535 $subscriptions = $this->userClass->getUserSubscriptionById($userId);
536 }
537
538 $message = '';
539 switch ($this->config->get('bounce_action_maxtry')) {
540 case 'sub' :
541 $listId = $this->config->get('bounce_action_lists_maxtry');
542 if (!empty($listId)) {
543 $message .= ' user '.$userId.' subscribed to list n°'.$listId;
544 $this->userClass->subscribe($userId, [$listId]);
545 }
546 case 'remove' :
547 $unsubLists = array_diff(array_keys($subscriptions), [$listId]);
548 if (!empty($unsubLists)) {
549 $message .= ' user '.$userId.' removed from lists '.implode(',', $unsubLists);
550 $this->userClass->removeSubscription($userId, $unsubLists);
551 } else {
552 $message .= ' user '.$userId.' not subscribed';
553 }
554 break;
555 case 'unsub' :
556 $unsubLists = array_diff(array_keys($subscriptions), [$listId]);
557 if (!empty($unsubLists)) {
558 $message .= ' user '.$userId.' unsubscribed from lists '.implode(',', $unsubLists);
559 $this->userClass->unsubscribe($userId, $unsubLists);
560 } else {
561 $message .= ' user '.$userId.' not unsubscribed';
562 }
563 break;
564 case 'delete' :
565 $message .= ' user '.$userId.' deleted';
566 $this->userClass->delete($userId);
567 break;
568 case 'block' :
569 $message .= ' user '.$userId.' blocked';
570 $this->userClass->deactivate($userId);
571 $this->queueClass->deleteOne($userId);
572 break;
573 }
574
575 return $message;
576 }
577 }
578