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/CronHelper.php +219 -113 10.1.4 → 11.1.0 View file →
@@ -46,8 +46,13 @@
46 46 self::STEP_ABTEST,
47 47 self::STEP_SCENARIO,
48 48 ];
49 49
50 + const SEND_REPORT_NO = 0;
51 + const SEND_REPORT_EACH_TIME = 1;
52 + const SEND_REPORT_ONLY_ON_ACTION = 2;
53 + const SEND_REPORT_ONLY_ON_ERROR = 3;
54 +
50 55 private array $messages = [];
51 56 private array $detailMessages = [];
52 57 private array $emailTypes = [];
53 58 private array $skip = [];
@@ -55,28 +60,36 @@
55 60 private int $cronTimeLimit = 0;
56 61 private bool $cronTimeLimitReached = false;
57 62 private int $startQueue;
58 63
64 + // Save the main message
59 65 private string $mainMessage = '';
60 66
67 + // Did the cron process anything?
61 68 private bool $processed = false;
62 69
70 + // Is there any error in the process
63 71 private bool $errorDetected = false;
64 72
73 + // If we call the cron just to send a batch of emails
65 74 private bool $externalSendingActivated = false;
66 75 private bool $externalSendingRepeat;
67 76 private bool $externalSendingNotFinished = false;
77 + private bool $isSendingCall = false;
68 78
69 79 public function __construct()
70 80 {
71 81 parent::__construct();
72 - $this->startQueue = acym_getVar('int', 'startqueue', 0);
73 82
74 83 acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$this->externalSendingActivated]);
75 84
85 + $callType = acym_getVar('string', 'type', '');
86 + $this->startQueue = acym_getVar('int', 'startqueue', 0);
76 87 $this->externalSendingRepeat = !empty(acym_getVar('int', 'external_sending_repeat', 0));
77 - if (!empty($this->startQueue) || !empty($this->externalSendingRepeat)) {
88 +
89 + if ($callType === 'sending' || !empty($this->startQueue) || !empty($this->externalSendingRepeat)) {
78 90 $this->skip = array_diff(self::ALL_STEPS, [self::STEP_SEND]);
91 + $this->isSendingCall = true;
79 92 }
80 93 }
81 94
82 95 public function addSkipFromString(string $skipVar): void
@@ -102,25 +115,17 @@
102 115 if (!$this->checkCronFrequency()) {
103 116 return;
104 117 }
105 118
106 - $this->freeUpCall();
107 119 $this->queueScheduledCampaigns();
108 120 $this->cleanQueue();
109 - $this->sendQueuedEmails();
110 - $this->checkTimeRemaining();
121 + $this->handleQueue();
111 122 $this->handleBounceMessages();
112 - $this->checkTimeRemaining();
113 123 $this->handleAutomations();
114 - $this->checkTimeRemaining();
115 124 $this->handleAutomaticCampaigns();
116 - $this->checkTimeRemaining();
117 125 $this->handleSpecificEmails();
118 - $this->checkTimeRemaining();
119 126 $this->handleFollowups();
120 - $this->checkTimeRemaining();
121 127 $this->handleMailboxActions();
122 - $this->checkTimeRemaining();
123 128 $this->cleanData();
124 129 $this->handleABTestCampaigns();
125 130 $this->handleScenario();
126 131 $this->continueCronCall();
@@ -147,13 +152,15 @@
147 152 if (!empty($detailMessages)) {
148 153 $this->detailMessages = $detailMessages;
149 154 }
150 155
151 - $reportPath = str_replace(['{year}', '{month}'], [date('Y'), date('m')], $reportPath);
156 + // Prepare the cron file path
157 + $reportPath = str_replace(['{year}', '{month}'], [gmdate('Y'), gmdate('m')], $reportPath);
152 158 $reportPath = acym_cleanPath(ACYM_ROOT.trim(html_entity_decode($reportPath)));
153 159 acym_createDir(dirname($reportPath), true, true);
154 160
155 161 $lr = "\r\n";
162 + // Catch warnings
156 163 ob_start();
157 164 file_put_contents(
158 165 $reportPath,
159 166 $lr.$lr.'******************** '.acym_getDate(time()).' UTC ********************'.$lr.implode($lr, $this->messages),
@@ -173,8 +180,11 @@
173 180 $this->messages[] = $potentialWarnings;
174 181 }
175 182 }
176 183
184 + /**
185 + * Sets the types of emails that will be sent by the cron
186 + */
177 187 public function setEmailTypes(array $emailTypes): void
178 188 {
179 189 $this->emailTypes = $emailTypes;
180 190 }
@@ -185,11 +195,15 @@
185 195 }
186 196
187 197 public function handleCronReport(): void
188 198 {
189 - $sendReport = $this->config->get('cron_sendreport');
199 + $sendReport = (int)$this->config->get('cron_sendreport');
190 200
191 - if (($sendReport == 2 && $this->processed) || $sendReport == 1 || ($sendReport == 3 && $this->errorDetected)) {
201 + if (
202 + ($sendReport === self::SEND_REPORT_ONLY_ON_ACTION && $this->processed)
203 + || $sendReport === self::SEND_REPORT_EACH_TIME
204 + || ($sendReport === self::SEND_REPORT_ONLY_ON_ERROR && $this->errorDetected)
205 + ) {
192 206 $mailerHelper = new MailerHelper();
193 207 $mailerHelper->report = false;
194 208 $mailerHelper->autoAddUser = true;
195 209 $mailerHelper->addParam('report', implode('<br />', $this->messages));
@@ -224,18 +238,22 @@
224 238 }
225 239
226 240 $this->saveReport();
227 241
228 - $newConfig = new \stdClass();
229 - $newConfig->cron_report = implode("\n", $this->messages);
242 + $newConfig = [
243 + 'cron_report' => implode("\n", $this->messages),
244 + ];
230 245
231 - if (strlen($newConfig->cron_report) > 800) {
232 - $newConfig->cron_report = substr($newConfig->cron_report, 0, 795).'...';
246 + if (strlen($newConfig['cron_report']) > 800) {
247 + $newConfig['cron_report'] = substr($newConfig['cron_report'], 0, 795).'...';
233 248 }
234 249
235 - $this->config->save($newConfig);
250 + $this->config->saveConfig($newConfig);
236 251 }
237 252
253 + /**
254 + * Call made by the API to remove the API key when the user unlinks the website from their account page on our website
255 + */
238 256 private function handleUnlinkLicenseCalls(): void
239 257 {
240 258 if (acym_getVar('int', 'unlink', 0) !== 1) {
241 259 return;
@@ -244,14 +262,17 @@
244 262 $callLicenseKey = acym_getVar('string', 'licenseKey');
245 263 $configLicenseKey = $this->config->get('license_key');
246 264
247 265 if (!empty($configLicenseKey) && $configLicenseKey === $callLicenseKey) {
248 - $this->config->save(['license_key' => '', 'active_cron' => 0]);
266 + $this->config->saveConfig(['license_key' => '', 'active_cron' => 0]);
249 267 }
250 268
251 269 exit;
252 270 }
253 271
272 + /**
273 + * Displays the message shown on the cron URL page
274 + */
254 275 private function triggeredMessage(): void
255 276 {
256 277 $firstMessage = acym_translationSprintf('ACYM_CRON_TRIGGERED', acym_date('now', 'd F Y H:i'));
257 278 $this->messages[] = $firstMessage;
@@ -257,11 +278,14 @@
257 278 $this->messages[] = $firstMessage;
258 279 acym_display($firstMessage, 'info');
259 280 }
260 281
282 + /**
283 + * Checks the cron frequency in the configuration to make sure the call is wanted
284 + */
261 285 private function checkCronFrequency(): bool
262 286 {
263 - if (!empty($this->startQueue)) {
287 + if ($this->isSendingCall) {
264 288 return true;
265 289 }
266 290
267 291 $time = time();
@@ -269,41 +293,39 @@
269 293 $cronFrequency = $this->config->get('cron_frequency', 900);
270 294
271 295 if ($nextCronTime > $time) {
272 296 if ($nextCronTime > ($time + $cronFrequency)) {
273 - $newConfig = new \stdClass();
274 - $newConfig->cron_next = $time + $cronFrequency;
275 - $this->config->save($newConfig);
297 + // The next cron time is too far in the future, should not happen but we'll handle the case, so we reset the next cron time
298 + $this->config->saveConfig(['cron_next' => $time + $cronFrequency]);
276 299 }
277 300
278 301 $notTimeMessage = acym_translationSprintf('ACYM_CRON_NEXT', acym_date($this->config->get('cron_next'), 'd F Y H:i'));
279 302 $this->messages[] = $notTimeMessage;
303 + // We don't need to trigger anything, it's not time yet
280 304 acym_display($notTimeMessage, 'info');
281 305
282 306 return false;
283 307 }
284 308
285 - $newConfig = new \stdClass();
286 - $newConfig->cron_last = $time;
287 - $newConfig->cron_fromip = acym_getIP();
288 - $newConfig->cron_next = $nextCronTime + $cronFrequency;
309 + // We update the next cron and the last cron dates
310 + $newConfig = [
311 + 'cron_last' => $time,
312 + 'cron_fromip' => acym_getIP(true),
313 + 'cron_next' => $nextCronTime + $cronFrequency,
314 + ];
289 315
290 - if ($newConfig->cron_next <= $time || $newConfig->cron_next > $time + $cronFrequency) {
291 - $newConfig->cron_next = $time + $cronFrequency;
316 + if ($newConfig['cron_next'] <= $time || $newConfig['cron_next'] > $time + $cronFrequency) {
317 + $newConfig['cron_next'] = $time + $cronFrequency;
292 318 }
293 319
294 - $this->config->save($newConfig);
320 + $this->config->saveConfig($newConfig);
295 321
296 322 return true;
297 323 }
298 324
299 - private function freeUpCall(): void
300 - {
301 - if (function_exists('fastcgi_finish_request')) {
302 - fastcgi_finish_request();
303 - }
304 - }
305 -
325 + /**
326 + * Adds the scheduled emails to the queue when the date is reached
327 + */
306 328 private function queueScheduledCampaigns(): void
307 329 {
308 330 if (in_array(self::STEP_SCHEDULE, $this->skip)) {
309 331 return;
@@ -310,9 +332,9 @@
310 332 }
311 333
312 334 $queueClass = new QueueClass();
313 335 $nbScheduled = $queueClass->scheduleReady();
314 - if ($nbScheduled) {
336 + if (!empty($nbScheduled)) {
315 337 $this->messages[] = acym_translationSprintf('ACYM_NB_SCHEDULED', $nbScheduled);
316 338 $this->detailMessages = array_merge($this->detailMessages, $queueClass->messages);
317 339 $this->processed = true;
318 340 }
@@ -317,8 +339,11 @@
317 339 $this->processed = true;
318 340 }
319 341 }
320 342
343 + /**
344 + * Cleans the emails that are stuck in the queue for too long (disabled/unconfirmed recipients)
345 + */
321 346 private function cleanQueue(): void
322 347 {
323 348 if (in_array(self::STEP_CLEAN_QUEUE, $this->skip)) {
324 349 return;
@@ -332,71 +357,26 @@
332 357 $this->processed = true;
333 358 }
334 359 }
335 360
336 - private function sendQueuedEmails(): void
361 + private function handleQueue(): void
337 362 {
338 - if (in_array(self::STEP_SEND, $this->skip) || $this->config->get('queue_type') === 'manual') {
363 + if (in_array(self::STEP_SEND, $this->skip) || !$this->canSendQueuedEmails()) {
339 364 return;
340 365 }
341 366
342 - $fromHour = $this->config->get('queue_send_from_hour', '00');
343 - $fromMinute = $this->config->get('queue_send_from_minute', '00');
344 - $toHour = $this->config->get('queue_send_to_hour', '23');
345 - $toMinute = $this->config->get('queue_send_to_minute', '59');
346 - $time = time();
347 -
348 - if ($fromHour != '00' || $fromMinute != '00' || $toHour != '23' || $toMinute != '59') {
349 - $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
350 - $fromBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$fromHour.':'.$fromMinute);
351 - $toBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$toHour.':'.$toMinute);
352 - if ($fromBasedOnCMSTimezoneAtSpecifiedHour > $toBasedOnCMSTimezoneAtSpecifiedHour) {
353 - if ($time > $fromBasedOnCMSTimezoneAtSpecifiedHour) {
354 - $toBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate(acym_date('tomorrow', 'Y-m-d').' '.$toHour.':'.$toMinute);
355 - } elseif ($time < $toBasedOnCMSTimezoneAtSpecifiedHour) {
356 - $fromBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate(acym_date('yesterday', 'Y-m-d').' '.$fromHour.':'.$fromMinute);
357 - }
358 - }
359 -
360 - if ($time < $fromBasedOnCMSTimezoneAtSpecifiedHour || $time > $toBasedOnCMSTimezoneAtSpecifiedHour) {
361 - return;
362 - }
367 + if (!$this->isSendingCall && acym_level(ACYM_ENTERPRISE) && function_exists('curl_multi_exec')) {
368 + $this->handleMultiCron();
363 369 }
364 370
365 - $dayOfWeek = acym_date('now', 'N');
366 - if ($this->config->get('queue_stop_weekend', 0) && $dayOfWeek >= 6) {
367 - return;
371 + if ($this->isSendingCall || $this->config->get('dedicated_send_process', 0) != 1) {
372 + $this->sendQueuedEmails();
368 373 }
369 -
370 - $this->handleMultiCron();
371 - $queueHelper = new QueueHelper();
372 - $queueHelper->send_limit = (int)$this->config->get('queue_nbmail_auto');
373 - $queueHelper->report = false;
374 - $queueHelper->emailTypes = $this->emailTypes;
375 - $queueHelper->process();
376 -
377 - if (!empty($queueHelper->messages)) {
378 - $this->detailMessages = array_merge($this->detailMessages, $queueHelper->messages);
379 - }
380 -
381 - if (!empty($queueHelper->nbprocess)) {
382 - $this->processed = true;
383 -
384 - if (!$queueHelper->finish && $this->externalSendingActivated) {
385 - $this->externalSendingNotFinished = true;
386 - }
387 - }
388 -
389 - $this->mainMessage = acym_translationSprintf('ACYM_CRON_PROCESS', $queueHelper->nbprocess, $queueHelper->successSend, $queueHelper->errorSend);
390 - $this->messages[] = $this->mainMessage;
391 -
392 - if (!empty($queueHelper->errorSend)) {
393 - $this->errorDetected = true;
394 - }
395 -
396 - $this->cronTimeLimit = $queueHelper->stoptime;
397 374 }
398 375
376 + /**
377 + * Makes sure the current process doesn't reach the server time limit
378 + */
399 379 private function checkTimeRemaining(): void
400 380 {
401 381 $time = time();
402 382 if (empty($this->cronTimeLimit) || $time < $this->cronTimeLimit) {
@@ -407,36 +387,47 @@
407 387
408 388 $this->cronTimeLimitReached = true;
409 389 }
410 390
391 + /**
392 + * If the configuration is set to send multiple batches of emails at the same time
393 + */
411 394 private function handleMultiCron(): void
412 395 {
413 - if (!empty($this->startQueue) || !acym_level(ACYM_ENTERPRISE)) {
396 + $emailsPerBatches = $this->config->get('queue_nbmail_auto', 70);
397 + if (empty($emailsPerBatches)) {
414 398 return;
415 399 }
416 400
417 - $emailsBatches = $this->config->get('queue_batch_auto', 1);
418 - $emailsBatches = intval($emailsBatches);
419 - $emailsPerBatches = $this->config->get('queue_nbmail_auto', 70);
420 - if ($emailsBatches < 2 || empty($emailsPerBatches)) {
421 - return;
401 + $nbBatches = $this->config->get('queue_batch_auto', 1);
402 + $nbBatches = intval($nbBatches);
403 +
404 + if ($this->config->get('dedicated_send_process', 0) == 1) {
405 + $startCron = 0;
406 + } else {
407 + if ($nbBatches < 2) {
408 + return;
409 + }
410 +
411 + $startCron = 1;
422 412 }
423 413
424 - $cronKey = '';
414 + $cronParams = '&t='.time();
425 415 if (!empty($this->config->get('cron_security', 0)) && !empty($this->config->get('cron_key'))) {
426 - $cronKey = '&cronKey='.$this->config->get('cron_key');
416 + $cronParams .= '&cronKey='.$this->config->get('cron_key');
427 417 }
428 418
429 419 $urls = [];
430 - for ($i = 1 ; $i <= $emailsBatches - 1 ; $i++) {
431 - $urls[] = acym_frontendLink('cron&task=cron&startqueue='.($emailsPerBatches * $i).'&t='.time().$cronKey);
420 + for ($i = $startCron; $i < $nbBatches; $i++) {
421 + $urls[] = acym_frontendLink('cron&task=cron&type=sending&startqueue='.($emailsPerBatches * $i).$cronParams);
432 422 }
433 423
434 - acym_asyncCurlCall($urls);
424 + acym_asyncUrlCalls($urls);
435 425 }
436 426
437 427 private function handleBounceMessages(): void
438 428 {
429 + $this->checkTimeRemaining();
439 430 $time = time();
440 431 $autoBounceHandlingActive = (int)$this->config->get('auto_bounce', 0) === 1;
441 432 $autoBounceHandlingNextTime = (int)$this->config->get('auto_bounce_next', 0);
442 433 $autoBounceHandlingFrequency = (int)$this->config->get('auto_bounce_frequency', 0);
@@ -445,23 +436,25 @@
445 436 if (in_array(self::STEP_BOUNCE, $this->skip) || $this->cronTimeLimitReached || !$isEnterprise || !$autoBounceHandlingActive || $time < $autoBounceHandlingNextTime) {
446 437 return;
447 438 }
448 439
449 - $newConfig = new \stdClass();
450 - $newConfig->auto_bounce_last = $time;
451 - $newConfig->auto_bounce_next = $time + $autoBounceHandlingFrequency;
452 - $this->config->save($newConfig);
440 + $this->config->saveConfig(
441 + [
442 + 'auto_bounce_last' => $time,
443 + 'auto_bounce_next' => $time + $autoBounceHandlingFrequency,
444 + ]
445 + );
453 446
454 447 $bounceHelper = new BounceHelper();
455 448 $bounceHelper->report = false;
456 449 $bounceHelper->stoptime = $this->cronTimeLimit;
457 450
458 - $newConfig = new \stdClass();
451 + $newConfig = [];
459 452 if ($bounceHelper->init() && $bounceHelper->connect()) {
460 453 $nbMessages = $bounceHelper->getNBMessages();
461 454 $nbMessagesReport = acym_translationSprintf('ACYM_NB_MAIL_MAILBOX', $nbMessages);
462 455 $this->messages[] = $nbMessagesReport;
463 - $newConfig->auto_bounce_report = $nbMessagesReport;
456 + $newConfig['auto_bounce_report'] = $nbMessagesReport;
464 457 $this->detailMessages[] = $nbMessagesReport;
465 458 if (!empty($nbMessages)) {
466 459 $bounceHelper->handleMessages();
467 460 $this->processed = true;
@@ -468,9 +461,10 @@
468 461 }
469 462 $this->detailMessages = array_merge($this->detailMessages, $bounceHelper->messages);
470 463 } else {
471 464 $bounceErrors = $bounceHelper->getErrors();
472 - $newConfig->auto_bounce_report = implode('<br />', $bounceErrors);
465 + $newConfig['auto_bounce_report'] = implode('<br />', $bounceErrors);
466 + //We add "bounce handling" just before the error so the user knows where it comes from...
473 467 if (!empty($bounceErrors[0])) {
474 468 $bounceErrors[0] = acym_translation('ACYM_BOUNCE_HANDLING').' : '.$bounceErrors[0];
475 469 }
476 470 $this->messages = array_merge($this->messages, $bounceErrors);
@@ -477,13 +471,17 @@
477 471 $this->processed = true;
478 472 $this->errorDetected = true;
479 473 }
480 474
481 - $this->config->save($newConfig);
475 + $this->config->saveConfig($newConfig);
482 476 }
483 477
478 + /**
479 + * Triggers the automations based on time frequency
480 + */
484 481 private function handleAutomations(): void
485 482 {
483 + $this->checkTimeRemaining();
486 484 if (in_array(self::STEP_AUTOMATION, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
487 485 return;
488 486 }
489 487
@@ -503,8 +501,9 @@
503 501 }
504 502
505 503 private function handleAutomaticCampaigns(): void
506 504 {
505 + $this->checkTimeRemaining();
507 506 if (in_array(self::STEP_CAMPAIGN, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
508 507 return;
509 508 }
510 509
@@ -516,10 +515,14 @@
516 515 $this->processed = true;
517 516 }
518 517 }
519 518
519 + /**
520 + * Sends special emails such as birthday or WooCommerce reminders
521 + */
520 522 private function handleSpecificEmails(): void
521 523 {
524 + $this->checkTimeRemaining();
522 525 if (in_array(self::STEP_SPECIFIC, $this->skip) || $this->cronTimeLimitReached) {
523 526 return;
524 527 }
525 528
@@ -537,10 +540,14 @@
537 540 $campaignClass->send($onespecialMail->id);
538 541 }
539 542 }
540 543
544 + /**
545 + * Sends follow-ups based on time frequency like birthdays
546 + */
541 547 private function handleFollowups(): void
542 548 {
549 + $this->checkTimeRemaining();
543 550 if (in_array(self::STEP_FOLLOWUP, $this->skip) || $this->cronTimeLimitReached) {
544 551 return;
545 552 }
546 553
@@ -546,11 +553,14 @@
546 553
547 554 $followupClass = new FollowupClass();
548 555 $followups = $followupClass->getFollowupDailyBases();
549 556
557 + // Only once a day
550 558 $dailyHour = $this->config->get('daily_hour', '12');
551 559 $dailyMinute = $this->config->get('daily_minute', '00');
560 + // The day it is currently based on the timezone specified in the CMS configuration
552 561 $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
562 + // The UTC timestamp of the current day based on the CMS timezone, at the specified hour
553 563 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
554 564 $time = time();
555 565
556 566 foreach ($followups as $followup) {
@@ -564,10 +574,14 @@
564 574 }
565 575 }
566 576 }
567 577
578 + /**
579 + * Checks messages received on mailboxes and triggers actions accordingly
580 + */
568 581 private function handleMailboxActions(): void
569 582 {
583 + $this->checkTimeRemaining();
570 584 if (in_array(self::STEP_MAILBOX_ACTION, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
571 585 return;
572 586 }
573 587
@@ -599,8 +613,9 @@
599 613 $mailboxHelper->handleAction();
600 614 $mailboxHelper->close();
601 615
602 616
617 + // Update next trigger
603 618 $oneMailboxAction->nextdate = time() + $oneMailboxAction->frequency;
604 619 unset($oneMailboxAction->conditions);
605 620 unset($oneMailboxAction->actions);
606 621 $mailboxActionClass->save($oneMailboxAction);
@@ -608,8 +623,9 @@
608 623 }
609 624
610 625 private function cleanData(): void
611 626 {
627 + // Clean the history and detailed stats based on the configuration
612 628 if (!in_array(self::STEP_DELETE_HISTORY, $this->skip) && $this->isDailyCron()) {
613 629 $userStatClass = new UserStatClass();
614 630 $userDetailedStatsDeleted = $userStatClass->deleteDetailedStatsPeriod();
615 631 if (!empty($userDetailedStatsDeleted['message'])) {
@@ -631,12 +647,14 @@
631 647 $this->processed = true;
632 648 }
633 649 }
634 650
651 + // Clean data on external sending method
635 652 if (!in_array(self::STEP_CLEAN_DATA_EXTERNAL_SENDING_METHOD, $this->skip) && $this->isDailyCron()) {
636 653 acym_trigger('onAcymCleanDataExternalSendingMethod');
637 654 }
638 655
656 + // Clean export changes
639 657 if (!in_array(self::STEP_CLEAN_EXPORT_CHANGES, $this->skip) && $this->isDailyCron()) {
640 658 $exportHelper = new ExportHelper();
641 659 $exportHelper->cleanExportChangesFile();
642 660 }
@@ -642,21 +660,25 @@
642 660 }
643 661
644 662 if ($this->isDailyCron()) {
645 663 $time = time();
646 - $this->config->save(['cron_last_daily' => $time]);
664 + $this->config->saveConfig(['cron_last_daily' => $time]);
647 665 }
648 666 }
649 667
650 668 private function isDailyCron(): bool
651 669 {
670 + // Only once a day
652 671 $dailyHour = $this->config->get('daily_hour', '12');
653 672 $dailyMinute = $this->config->get('daily_minute', '00');
673 + // The day it is currently based on the timezone specified in the CMS configuration
654 674 $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
675 + // The UTC timestamp of the current day based on the CMS timezone, at the specified hour
655 676 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
656 677
657 678 $time = time();
658 679
680 + //If we do not have run the cron we set it to yesterday so it will be trigger
659 681 $lastCronDayBasedOnCMSTimezone = acym_date($this->config->get('cron_last_daily', $time - 86400), 'Y-m-d');
660 682
661 683 return $time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && $lastCronDayBasedOnCMSTimezone != $dayBasedOnCMSTimezone;
662 684 }
@@ -662,8 +684,9 @@
662 684 }
663 685
664 686 private function handleABTestCampaigns(): void
665 687 {
688 + $this->checkTimeRemaining();
666 689 if (in_array(self::STEP_ABTEST, $this->skip) || $this->cronTimeLimitReached) {
667 690 return;
668 691 }
669 692
@@ -678,8 +701,9 @@
678 701 }
679 702
680 703 private function handleScenario(): void
681 704 {
705 + $this->checkTimeRemaining();
682 706 if (in_array(self::STEP_SCENARIO, $this->skip) || $this->cronTimeLimitReached) {
683 707 return;
684 708 }
685 709
@@ -687,8 +711,11 @@
687 711 $scenarioHelper->executeAvailableSteps();
688 712 $scenarioHelper->triggerTimeScenarios();
689 713 }
690 714
715 + /**
716 + * We reached the maximum time allowed so the tasks are not finished, we begin a new process to finish it up
717 + */
691 718 private function continueCronCall(): void
692 719 {
693 720 if ($this->externalSendingNotFinished) {
694 721 $cronKey = '';
@@ -696,6 +723,85 @@
696 723 $cronKey = '&cronKey='.$this->config->get('cron_key');
697 724 }
698 725 acym_makeCurlCall(acym_frontendLink('cron&task=cron&external_sending_repeat=1&t='.time().$cronKey), ['verifySsl' => false]);
699 726 }
727 + }
728 +
729 + private function canSendQueuedEmails(): bool
730 + {
731 + if ($this->config->get('queue_type') === 'manual') {
732 + return false;
733 + }
734 +
735 + $dayOfWeek = acym_date('now', 'N');
736 + if ($this->config->get('queue_stop_weekend', 0) && $dayOfWeek >= 6) {
737 + return false;
738 + }
739 +
740 + // Make sure we are within the sending hours defined in the configuration
741 + $fromHour = $this->config->get('queue_send_from_hour', '00');
742 + $fromMinute = $this->config->get('queue_send_from_minute', '00');
743 + $toHour = $this->config->get('queue_send_to_hour', '23');
744 + $toMinute = $this->config->get('queue_send_to_minute', '59');
745 + $time = time();
746 +
747 + if ($fromHour != '00' || $fromMinute != '00' || $toHour != '23' || $toMinute != '59') {
748 + // The day it is currently based on the timezone specified in the CMS configuration
749 + $dayBasedOnCMSTimezone = acym_date('now', 'Y-m-d');
750 + // The UTC timestamp of the current day based on the CMS timezone, at the specified hour
751 + $fromBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$fromHour.':'.$fromMinute);
752 + $toBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$toHour.':'.$toMinute);
753 + // In case we want to send during the night and the FROM is superior to the TO (from 8pm to 4am), we should change the day of ones of the limits
754 + if ($fromBasedOnCMSTimezoneAtSpecifiedHour > $toBasedOnCMSTimezoneAtSpecifiedHour) {
755 + // TO becomes tomorrow as we are not passed midnight
756 + if ($time > $fromBasedOnCMSTimezoneAtSpecifiedHour) {
757 + $toBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate(acym_date('tomorrow', 'Y-m-d').' '.$toHour.':'.$toMinute);
758 + } elseif ($time < $toBasedOnCMSTimezoneAtSpecifiedHour) {
759 + // FROM becomes yesterday as we are passed midnight
760 + $fromBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate(acym_date('yesterday', 'Y-m-d').' '.$fromHour.':'.$fromMinute);
761 + }
762 + }
763 +
764 + if ($time < $fromBasedOnCMSTimezoneAtSpecifiedHour || $time > $toBasedOnCMSTimezoneAtSpecifiedHour) {
765 + return false;
766 + }
767 + }
768 +
769 + return true;
770 + }
771 +
772 + private function sendQueuedEmails(): void
773 + {
774 + $queueHelper = new QueueHelper();
775 + $queueHelper->send_limit = (int)$this->config->get('queue_nbmail_auto');
776 + $queueHelper->report = false;
777 + $queueHelper->emailTypes = $this->emailTypes;
778 + $queueHelper->startSendingFrom = $this->startQueue;
779 + $queueHelper->process();
780 +
781 + if (!empty($queueHelper->messages)) {
782 + $this->detailMessages = array_merge($this->detailMessages, $queueHelper->messages);
783 + }
784 +
785 + if (!empty($queueHelper->nbprocess)) {
786 + $this->processed = true;
787 +
788 + if (!$queueHelper->finish && $this->externalSendingActivated) {
789 + $this->externalSendingNotFinished = true;
790 + }
791 + }
792 +
793 + $this->mainMessage = acym_translationSprintf(
794 + 'ACYM_CRON_PROCESS',
795 + $queueHelper->nbprocess,
796 + $queueHelper->successSend,
797 + $queueHelper->errorSend
798 + );
799 + $this->messages[] = $this->mainMessage;
800 +
801 + if (!empty($queueHelper->errorSend)) {
802 + $this->errorDetected = true;
803 + }
804 +
805 + $this->cronTimeLimit = $queueHelper->stoptime;
700 806 }
701 807 }