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 / QueueHelper.php

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

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