PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.0
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.0
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Helpers/QueueHelper.php +122 -36 10.11.1 → 11.1.0 View file →
@@ -30,9 +30,11 @@
30 30 public array $messages = [];
31 31 public array $emailTypes = [];
32 32
33 33 private int $consecutiveError = 0;
34 + // Check apache module "mod_security" to avoid flush issue
34 35 private bool $mod_security2 = false;
36 + // Number of ob_end_flush used in the process
35 37 private int $obend = 0;
36 38
37 39 private UserClass $userClass;
38 40 private QueueClass $queueClass;
@@ -45,22 +47,27 @@
45 47 $this->userClass = new UserClass();
46 48
47 49 $this->send_limit = (int)$this->config->get('queue_nbmail', 40);
48 50
51 + // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- The default is 60s, which is far too much for email sending.
49 52 @ini_set('default_socket_timeout', 10);
50 53
54 + // We call cron URLs but don't wait for the result, we still want the script to execute
51 55 @ignore_user_abort(true);
52 56
57 + // Increase the limit to have enough time to send, and prevent infinite process
53 58 $timeLimit = acym_increasePerf();
54 59 if (empty($timeLimit)) {
55 60 $timeLimit = 600;
56 61 }
57 62
63 + // We keep 4 seconds to finish the process properly
58 64 $this->stoptime = time() + $timeLimit - 4;
59 65 }
60 66
61 67 public function process(): bool
62 68 {
69 + // Check if the current sending method has credits left and load the correct number of emails to send for this batch
63 70 $creditsLeft = 10000;
64 71 acym_trigger('onAcymCreditsLeft', [&$creditsLeft]);
65 72
66 73 if (empty($creditsLeft)) {
@@ -88,8 +95,9 @@
88 95
89 96 return true;
90 97 }
91 98
99 + //We stop the flush
92 100 if ($this->report) {
93 101 if (function_exists('apache_get_modules')) {
94 102 $modules = apache_get_modules();
95 103 $this->mod_security2 = in_array('mod_security2', $modules);
@@ -94,9 +102,12 @@
94 102 $modules = apache_get_modules();
95 103 $this->mod_security2 = in_array('mod_security2', $modules);
96 104 }
97 105
106 + /*This is to avoid the blank page... and it apparently works! ;) */
107 + // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Disabled to prevent blank output pages.
98 108 @ini_set('output_buffering', 'off');
109 + // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Disabled to prevent blank output pages.
99 110 @ini_set('zlib.output_compression', 0);
100 111
101 112 if (!headers_sent()) {
102 113 while (ob_get_level() > 0 && $this->obend++ < 3) {
@@ -103,40 +114,61 @@
103 114 @ob_end_flush();
104 115 }
105 116 }
106 117
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;
118 + ?>
119 + <html>
120 + <head>
121 + <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
122 + <title><?php echo esc_html(acym_translation('ACYM_SEND_PROCESS')); ?></title>
123 + <style>body{
124 + font-size: 12px;
125 + font-family: Arial, Helvetica, sans-serif;
126 + }</style>
127 + </head>
128 + <body>
129 + <div style="margin-bottom: 18px;padding: 8px !important; background-color: #fcf8e3; border: 1px solid #fbeed5; border-radius: 4px;">
130 + <p style="margin:0;"><?php echo esc_html(acym_translation('ACYM_DONT_CLOSE')); ?></p>
131 + </div>
132 + <div style="display: inline;background-color : white;border : 1px solid grey; padding : 3px;font-size:14px">
133 + <span id="divpauseinfo" style="padding:10px;margin:5px;font-size:16px;font-weight:bold;display:none;background-color:black;color:white;"> </span>
134 + <?php echo esc_html(acym_translation('ACYM_SEND_PROCESS')); ?>:
135 + <span id="counter"><?php echo intval($this->start); ?></span> / <?php echo intval($this->total); ?>
136 + </div>
137 + <div id="divinfo" style="display:none; position:fixed; bottom:3px;left:3px;background-color : white; border : 1px solid grey; padding : 3px;"></div>
138 + <br /><br />
139 + <script type="text/javascript" language="javascript">
140 + function setInfo(message) {
141 + const divinfo = document.getElementById('divinfo');
142 + divinfo.style.display = 'block';
143 + divinfo.innerHTML = message;
144 + }
145 +
146 + function setPauseInfo(nbpause) {
147 + const divpauseinfo = document.getElementById('divpauseinfo');
148 + divpauseinfo.style.display = '';
149 + divpauseinfo.innerHTML = nbpause;
150 + }
151 +
152 + function setCounter(val) {
153 + document.getElementById('counter').innerHTML = val;
154 + }
155 +
156 + let acymailingScriptPause = <?php echo intval($this->pause); ?>;
157 +
158 + function handlePause() {
159 + setPauseInfo(acymailingScriptPause);
160 + if (acymailingScriptPause > 0) {
161 + acymailingScriptPause = acymailingScriptPause - 1;
162 + setTimeout(handlePause, 1000);
163 + } else {
164 + document.location.href = <?php echo json_encode(
165 + acym_completeLink('queue&task=continuesend&id='.$this->id.'&totalsend='.$this->total, true, true).'&'.acym_getFormToken().'&alreadysent='
166 + ); ?> +document.getElementById('counter').innerHTML;
167 + }
168 + }
169 + </script>
170 + <?php
139 171 if (function_exists('ob_flush')) {
140 172 @ob_flush();
141 173 }
142 174 if (!$this->mod_security2) {
@@ -149,12 +181,19 @@
149 181 if ($this->config->get('smtp_keepalive', 1) || $this->config->get('mailer_method') === 'elasticemail') {
150 182 $mailerHelper->SMTPKeepAlive = true;
151 183 }
152 184
185 + //To delete some entries from the queue
186 + //$queueDelete[mailing][] = subid;
153 187 $queueDelete = [];
188 + //Update some entries with a new try and a new delay
189 + //$queueUpdate[mailing][] = subid;
154 190 $queueUpdate = [];
191 + //Add some stats because we deleted the Newsletter
192 + //$statsAdd[mailing][status (1 for success/0 for fail)][] = subid;
155 193 $statsAdd = [];
156 194
195 + //Maximum number of try...
157 196 $maxTry = (int)$this->config->get('queue_try', 0);
158 197
159 198 $currentMail = $this->start;
160 199 $this->nbprocess = 0;
@@ -206,9 +245,9 @@
206 245
207 246 $currentMail++;
208 247 $this->nbprocess++;
209 248 if ($this->report) {
210 - echo '<script type="text/javascript">setCounter('.$currentMail.')</script>';
249 + echo '<script type="text/javascript">setCounter('.esc_html($currentMail).')</script>';
211 250 if (function_exists('ob_flush')) {
212 251 @ob_flush();
213 252 }
214 253 if (!$this->mod_security2) {
@@ -247,12 +286,14 @@
247 286 $this->consecutiveError = 0;
248 287 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
249 288 $statsAdd[$oneQueue->mail_id][1][] = $oneQueue->user_id;
250 289
290 + //In case of the e-mail has been sent now, we immediately process the update/delete and stats
251 291 $queueDeleteOk = $this->deleteQueue($queueDelete);
252 292 $mailerHelper->triggerFollowUpAgain($oneQueue->mail_id, $oneQueue->user_id);
253 293 $queueDelete = [];
254 294
295 + //We only update the queue and add the stats every 10 emails so that we can group things and avoid queries
255 296 if (!$statisticsByBatch || $this->nbprocess % 10 == 0) {
256 297 $this->statsAdd($statsAdd);
257 298 $this->queueUpdate($queueUpdate);
258 299 $statsAdd = [];
@@ -260,8 +301,9 @@
260 301 }
261 302 } elseif ($mailerHelper->dtextsFailed) {
262 303 $this->consecutiveError = 0;
263 304 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
305 + //In case of the e-mail has been sent now, we immediately process the update/delete and stats
264 306 $queueDeleteOk = $this->deleteQueue($queueDelete);
265 307 $queueDelete = [];
266 308 } else {
267 309 acym_trigger('onAcymFailedSendingEmail', [$oneQueue->mail_id, $oneQueue->user_id, $mailerHelper->errorNumber]);
@@ -279,19 +321,23 @@
279 321 if ($mailerHelper->errorNumber === 1) {
280 322 $this->consecutiveError++;
281 323 }
282 324
325 + //If we have 2 consecutive errors, we pause the process a little bit to avoid possible other issues.
283 326 if ($this->consecutiveError === 2) {
284 327 sleep(1);
285 328 }
286 329 }
287 330
331 + //We delete the queue entry if it's more than number of try allowed
288 332 if (!$shouldTrySendingLater) {
289 333 $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id;
290 334 $statsAdd[$oneQueue->mail_id][0][] = $oneQueue->user_id;
291 335 if ($mailerHelper->errorNumber === 1 && $this->config->get('bounce_action_maxtry')) {
336 + //We have to delete the queue now otherwise we may have problems!
292 337 $queueDeleteOk = $this->deleteQueue($queueDelete);
293 338 $queueDelete = [];
339 + //Let's execute an action on this subscriber if we have something to do...
294 340 $otherMessage .= $this->failedActions((int)$oneQueue->user_id);
295 341 }
296 342 } elseif ($mailerHelper->failedCounting) {
297 343 $queueUpdate[$oneQueue->mail_id][] = $oneQueue->user_id;
@@ -305,8 +351,9 @@
305 351 $messageOnScreen .= ' => '.$otherMessage;
306 352 }
307 353 $this->displayMessage([$messageOnScreen], $result ? self::MESSAGE_TYPE_SUCCESS : self::MESSAGE_TYPE_ERROR, $currentMail);
308 354
355 + //We didn't delete the right number of elements so to avoid a double send process, we stop immediately
309 356 if (!$queueDeleteOk) {
310 357 $this->finish = true;
311 358 break;
312 359 }
@@ -319,12 +366,15 @@
319 366 break;
320 367 }
321 368
322 369 if ($this->consecutiveError > 3 && $this->successSend > 3) {
370 + //We refresh the page in case of we used to send e-mails but now we can not, that may be due to a mysql close connection
323 371 $this->displayMessage([acym_translation('ACYM_SEND_REFRESH_CONNECTION')], self::MESSAGE_TYPE_WARNING);
324 372 break;
325 373 }
326 374
375 + //After 5 consecutive send error, we stop the process and display a message
376 + //We stop the send process if the user closed the windows so that he can stop the send process at any time he wants
327 377 if ($this->consecutiveError > 5 || ($this->report && connection_aborted())) {
328 378 $this->finish = true;
329 379 break;
330 380 }
@@ -337,8 +387,9 @@
337 387 acym_trigger('onAcymSendCampaignOnExternalSendingMethod', [$mailId, $content]);
338 388 }
339 389 }
340 390
391 + //Update all values if it's not already done (we don't do it for each error so that's why we still have some
341 392 $this->deleteQueue($queueDelete);
342 393 $this->statsAdd($statsAdd);
343 394 $this->queueUpdate($queueUpdate);
344 395
@@ -345,8 +396,9 @@
345 396 if ($mailerHelper->SMTPKeepAlive) {
346 397 $mailerHelper->smtpClose();
347 398 }
348 399
400 + //We finished the send process
349 401 if (!empty($this->total) && $currentMail >= $this->total) {
350 402 $this->finish = true;
351 403 }
352 404
@@ -356,12 +408,14 @@
356 408 return false;
357 409 }
358 410
359 411 if ($this->report && !$this->finish) {
412 + //We handle the pause in JS.
360 413 echo '<script type="text/javascript" language="javascript">handlePause();</script>';
361 414 }
362 415
363 416 if ($this->report) {
417 + //We need to finish the current page properly
364 418 echo '</body></html>';
365 419 while ($this->obend-- > 0) {
366 420 ob_start();
367 421 }
@@ -370,8 +424,13 @@
370 424
371 425 return true;
372 426 }
373 427
428 + /**
429 + * Function to add/update elements in the stats
430 + * $statsAdd[mailing][1 for success or 0 for fail][] = subscriber id;
431 + * $statsAdd[mailing][1][] = 123;
432 + */
374 433 public function statsAdd(array $statsAdd): void
375 434 {
376 435 if (empty($statsAdd)) {
377 436 return;
@@ -432,8 +491,12 @@
432 491 $triggered[$mailId] = true;
433 492 acym_triggerCmsHook('onAcymSendMail', [$mailId]);
434 493 }
435 494
495 + /**
496 + * Function to delete elements from the queue
497 + * $queueDelete[mailId][] = subscriberId;
498 + */
436 499 private function deleteQueue(array $queueDelete): bool
437 500 {
438 501 if (empty($queueDelete)) {
439 502 return true;
@@ -448,8 +511,9 @@
448 511 if (!empty($this->queueClass->errors)) {
449 512 $status = false;
450 513 $this->displayMessage($this->queueClass->errors, self::MESSAGE_TYPE_ERROR);
451 514 } else {
515 + // If we didn't delete the entry, it may have been done by another send process so we stop it immediately to avoid double send
452 516 if ($nbDeleted !== $nbUsers) {
453 517 $status = false;
454 518 $this->displayMessage(
455 519 [$nbDeleted < $nbUsers ? acym_translation('ACYM_QUEUE_DOUBLE') : $nbDeleted.' emails deleted from the queue whereas we only have '.$nbUsers.' subscribers'],
@@ -461,8 +525,12 @@
461 525
462 526 return $status;
463 527 }
464 528
529 + /**
530 + * Function to update elements in the queue
531 + * $queueUpdate[mailing][] = subid;
532 + */
465 533 private function queueUpdate(array $queueUpdate): void
466 534 {
467 535 if (empty($queueUpdate)) {
468 536 return;
@@ -467,8 +535,9 @@
467 535 if (empty($queueUpdate)) {
468 536 return;
469 537 }
470 538
539 + //Delay to requeue the e-mail in seconds
471 540 foreach ($queueUpdate as $mailid => $subscribers) {
472 541 $this->queueClass->delayFailed($mailid, $subscribers);
473 542 }
474 543 }
@@ -479,8 +548,9 @@
479 548 $message = acym_translation('ACYM_SEND_STOPED');
480 549 $message .= '<br />';
481 550 $message .= acym_translation('ACYM_SEND_KEPT_ALL');
482 551 $message .= '<br />';
552 + //ERROR 1 : no e-mail sent at all...
483 553 if ($this->report) {
484 554 if (empty($this->successSend) && empty($this->start)) {
485 555 $message .= acym_translation('ACYM_SEND_CHECKONE');
486 556 $message .= '<br />';
@@ -485,8 +555,9 @@
485 555 $message .= acym_translation('ACYM_SEND_CHECKONE');
486 556 $message .= '<br />';
487 557 $message .= acym_translation('ACYM_SEND_ADVISE_LIMITATION');
488 558 } else {
559 + //We used to send e-mails but now we can not any more... sounds like a server limitation!
489 560 $message .= acym_translation('ACYM_SEND_REFUSE');
490 561 $message .= '<br />';
491 562 if (!acym_level(ACYM_ESSENTIAL)) {
492 563 $message .= acym_translation('ACYM_SEND_CONTINUE_COMMERCIAL');
@@ -498,12 +569,15 @@
498 569
499 570 $this->displayMessage([$message], self::MESSAGE_TYPE_WARNING);
500 571 }
501 572
573 + /**
574 + * If num is empty then it's a message otherwise it's a send status
575 + */
502 576 private function displayMessage(array $messages, int $status, int $num = 0): void
503 577 {
504 578 foreach ($messages as $message) {
505 - $this->messages[] = strip_tags($message);
579 + $this->messages[] = acym_stripTags($message);
506 580 }
507 581
508 582 if (!$this->report) {
509 583 return;
@@ -511,11 +585,21 @@
511 585
512 586 $color = $status === self::MESSAGE_TYPE_SUCCESS ? 'green' : ($status === self::MESSAGE_TYPE_WARNING ? 'orange' : 'red');
513 587 foreach ($messages as $message) {
514 588 if (!empty($num)) {
515 - echo '<br />'.$num.' : <span style="color:'.$color.';">'.$message.'</span>';
589 + echo '<br />'.esc_html($num).' : <span style="color:'.esc_attr($color).';">'.wp_kses(
590 + $message,
591 + [
592 + 'b' => [],
593 + 'i' => [],
594 + 'a' => [
595 + 'href' => true,
596 + 'target' => true,
597 + ],
598 + ]
599 + ).'</span>';
516 600 } else {
517 - echo '<script type="text/javascript" language="javascript">setInfo(\''.addslashes($message).'\')</script>';
601 + echo '<script type="text/javascript" language="javascript">setInfo('.json_encode($message).')</script>';
518 602 }
519 603 }
520 604
521 605 if (function_exists('ob_flush')) {
@@ -541,8 +625,9 @@
541 625 if (!empty($listId)) {
542 626 $message .= ' user '.$userId.' subscribed to list n°'.$listId;
543 627 $this->userClass->subscribe([$userId], [$listId]);
544 628 }
629 + // There is no break here as we will remove the user from the other lists
545 630 case 'remove':
546 631 $unsubLists = array_diff(array_keys($subscriptions), [$listId]);
547 632 if (!empty($unsubLists)) {
548 633 $message .= ' user '.$userId.' removed from lists '.implode(',', $unsubLists);
@@ -566,8 +651,9 @@
566 651 break;
567 652 case 'block':
568 653 $message .= ' user '.$userId.' blocked';
569 654 $this->userClass->deactivate([$userId]);
655 + // We delete any other e-mail from the queue as well
570 656 $this->queueClass->deleteQueuedByUserIds([$userId]);
571 657 break;
572 658 }
573 659