PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / trunk
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress vtrunk
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 / CronHelper.php

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

808 lines 28.6 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\AutomationClass;
6 use AcyMailing\Classes\CampaignClass;
7 use AcyMailing\Classes\FollowupClass;
8 use AcyMailing\Classes\MailArchiveClass;
9 use AcyMailing\Classes\MailboxClass;
10 use AcyMailing\Classes\MailClass;
11 use AcyMailing\Classes\QueueClass;
12 use AcyMailing\Classes\UserClass;
13 use AcyMailing\Classes\UserStatClass;
14 use AcyMailing\Core\AcymObject;
15
16 class CronHelper extends AcymObject
17 {
18 const STEP_SCHEDULE = 'schedule';
19 const STEP_CLEAN_QUEUE = 'cleanqueue';
20 const STEP_SEND = 'send';
21 const STEP_BOUNCE = 'bounce';
22 const STEP_AUTOMATION = 'automation';
23 const STEP_CAMPAIGN = 'campaign';
24 const STEP_SPECIFIC = 'specific';
25 const STEP_FOLLOWUP = 'followup';
26 const STEP_DELETE_HISTORY = 'delete_history';
27 const STEP_CLEAN_DATA_EXTERNAL_SENDING_METHOD = 'clean_data_external_sending_method';
28 const STEP_CLEAN_EXPORT_CHANGES = 'clean_export_changes';
29 const STEP_MAILBOX_ACTION = 'mailbox_action';
30 const STEP_ABTEST = 'abtest';
31 const STEP_SCENARIO = 'scenario';
32
33 const ALL_STEPS = [
34 self::STEP_SCHEDULE,
35 self::STEP_CLEAN_QUEUE,
36 self::STEP_SEND,
37 self::STEP_BOUNCE,
38 self::STEP_AUTOMATION,
39 self::STEP_CAMPAIGN,
40 self::STEP_SPECIFIC,
41 self::STEP_FOLLOWUP,
42 self::STEP_DELETE_HISTORY,
43 self::STEP_CLEAN_DATA_EXTERNAL_SENDING_METHOD,
44 self::STEP_CLEAN_EXPORT_CHANGES,
45 self::STEP_MAILBOX_ACTION,
46 self::STEP_ABTEST,
47 self::STEP_SCENARIO,
48 ];
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
55 private array $messages = [];
56 private array $detailMessages = [];
57 private array $emailTypes = [];
58 private array $skip = [];
59
60 private int $cronTimeLimit = 0;
61 private bool $cronTimeLimitReached = false;
62 private int $startQueue;
63
64 // Save the main message
65 private string $mainMessage = '';
66
67 // Did the cron process anything?
68 private bool $processed = false;
69
70 // Is there any error in the process
71 private bool $errorDetected = false;
72
73 // If we call the cron just to send a batch of emails
74 private bool $externalSendingActivated = false;
75 private bool $externalSendingRepeat;
76 private bool $externalSendingNotFinished = false;
77 private bool $isSendingCall = false;
78
79 public function __construct()
80 {
81 parent::__construct();
82
83 acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$this->externalSendingActivated]);
84
85 $callType = acym_getVar('string', 'type', '');
86 $this->startQueue = acym_getVar('int', 'startqueue', 0);
87 $this->externalSendingRepeat = !empty(acym_getVar('int', 'external_sending_repeat', 0));
88
89 if ($callType === 'sending' || !empty($this->startQueue) || !empty($this->externalSendingRepeat)) {
90 $this->skip = array_diff(self::ALL_STEPS, [self::STEP_SEND]);
91 $this->isSendingCall = true;
92 }
93 }
94
95 public function addSkipFromString(string $skipVar): void
96 {
97 if (empty($skipVar)) {
98 return;
99 }
100
101 $skipVar = explode(',', $skipVar);
102
103 if (empty($skipVar)) {
104 return;
105 }
106
107 $this->skip = array_unique(array_merge($this->skip, $skipVar));
108 }
109
110 public function cron(): void
111 {
112
113 $this->handleUnlinkLicenseCalls();
114 $this->triggeredMessage();
115 if (!$this->checkCronFrequency()) {
116 return;
117 }
118
119 $this->queueScheduledCampaigns();
120 $this->cleanQueue();
121 $this->handleQueue();
122 $this->handleBounceMessages();
123 $this->handleAutomations();
124 $this->handleAutomaticCampaigns();
125 $this->handleSpecificEmails();
126 $this->handleFollowups();
127 $this->handleMailboxActions();
128 $this->cleanData();
129 $this->handleABTestCampaigns();
130 $this->handleScenario();
131 $this->continueCronCall();
132 $this->handleCronReport();
133 }
134
135 public function saveReport(array $messages = [], array $detailMessages = []): void
136 {
137 $saveReport = $this->config->get('cron_savereport');
138 $reportPath = $this->config->get('cron_savepath');
139
140 if (empty($saveReport) || empty($reportPath)) {
141 return;
142 }
143
144 if (!(($saveReport == 2 && $this->processed) || $saveReport == 1 || ($saveReport == 3 && $this->errorDetected))) {
145 return;
146 }
147
148 if (!empty($messages)) {
149 $this->messages = $messages;
150 }
151
152 if (!empty($detailMessages)) {
153 $this->detailMessages = $detailMessages;
154 }
155
156 // Prepare the cron file path
157 $reportPath = str_replace(['{year}', '{month}'], [gmdate('Y'), gmdate('m')], $reportPath);
158 $reportPath = acym_cleanPath(ACYM_ROOT.trim(html_entity_decode($reportPath)));
159 acym_createDir(dirname($reportPath), true, true);
160
161 $lr = "\r\n";
162 // Catch warnings
163 ob_start();
164 file_put_contents(
165 $reportPath,
166 $lr.$lr.'******************** '.acym_getDate(time()).' UTC ********************'.$lr.implode($lr, $this->messages),
167 FILE_APPEND
168 );
169
170 if ($saveReport == 2 && !empty($this->detailMessages)) {
171 file_put_contents(
172 $reportPath,
173 $lr.'---- Details ----'.$lr.implode($lr, $this->detailMessages),
174 FILE_APPEND
175 );
176 }
177 $potentialWarnings = ob_get_clean();
178
179 if (!empty($potentialWarnings)) {
180 $this->messages[] = $potentialWarnings;
181 }
182 }
183
184 /**
185 * Sets the types of emails that will be sent by the cron
186 */
187 public function setEmailTypes(array $emailTypes): void
188 {
189 $this->emailTypes = $emailTypes;
190 }
191
192 public function addMessage(string $message): void
193 {
194 $this->messages[] = $message;
195 }
196
197 public function handleCronReport(): void
198 {
199 $sendReport = (int)$this->config->get('cron_sendreport');
200
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 ) {
206 $mailerHelper = new MailerHelper();
207 $mailerHelper->report = false;
208 $mailerHelper->autoAddUser = true;
209 $mailerHelper->addParam('report', implode('<br />', $this->messages));
210 $mailerHelper->addParam('mainreport', $this->mainMessage);
211 $mailerHelper->addParam('detailreport', implode('<br />', $this->detailMessages));
212
213 $receiverString = $this->config->get('cron_sendto');
214 $receivers = [];
215 if (substr_count($receiverString, '@') > 1) {
216 $receivers = explode(' ', trim(preg_replace('# +#', ' ', str_replace([';', ','], ' ', $receiverString))));
217 } else {
218 $receivers[] = trim($receiverString);
219 }
220
221 if (!empty($receivers)) {
222 $mailClass = new MailClass();
223 foreach ($receivers as $oneReceiver) {
224 if (empty($oneReceiver)) {
225 continue;
226 }
227
228 try {
229 $reportEmail = $mailClass->getOneByName('acy_report');
230 if (!empty($reportEmail)) {
231 $mailerHelper->sendOne($reportEmail->id, $oneReceiver);
232 }
233 } catch (\Exception $e) {
234 acym_logError('Error while sending the cron report to '.$oneReceiver.' : '.$e->getMessage());
235 }
236 }
237 }
238 }
239
240 $this->saveReport();
241
242 $newConfig = [
243 'cron_report' => implode("\n", $this->messages),
244 ];
245
246 if (strlen($newConfig['cron_report']) > 800) {
247 $newConfig['cron_report'] = substr($newConfig['cron_report'], 0, 795).'...';
248 }
249
250 $this->config->saveConfig($newConfig);
251 }
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 */
256 private function handleUnlinkLicenseCalls(): void
257 {
258 if (acym_getVar('int', 'unlink', 0) !== 1) {
259 return;
260 }
261
262 $callLicenseKey = acym_getVar('string', 'licenseKey');
263 $configLicenseKey = $this->config->get('license_key');
264
265 if (!empty($configLicenseKey) && $configLicenseKey === $callLicenseKey) {
266 $this->config->saveConfig(['license_key' => '', 'active_cron' => 0]);
267 }
268
269 exit;
270 }
271
272 /**
273 * Displays the message shown on the cron URL page
274 */
275 private function triggeredMessage(): void
276 {
277 $firstMessage = acym_translationSprintf('ACYM_CRON_TRIGGERED', acym_date('now', 'd F Y H:i'));
278 $this->messages[] = $firstMessage;
279 acym_display($firstMessage, 'info');
280 }
281
282 /**
283 * Checks the cron frequency in the configuration to make sure the call is wanted
284 */
285 private function checkCronFrequency(): bool
286 {
287 if ($this->isSendingCall) {
288 return true;
289 }
290
291 $time = time();
292 $nextCronTime = $this->config->get('cron_next', 0);
293 $cronFrequency = $this->config->get('cron_frequency', 900);
294
295 if ($nextCronTime > $time) {
296 if ($nextCronTime > ($time + $cronFrequency)) {
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]);
299 }
300
301 $notTimeMessage = acym_translationSprintf('ACYM_CRON_NEXT', acym_date($this->config->get('cron_next'), 'd F Y H:i'));
302 $this->messages[] = $notTimeMessage;
303 // We don't need to trigger anything, it's not time yet
304 acym_display($notTimeMessage, 'info');
305
306 return false;
307 }
308
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 ];
315
316 if ($newConfig['cron_next'] <= $time || $newConfig['cron_next'] > $time + $cronFrequency) {
317 $newConfig['cron_next'] = $time + $cronFrequency;
318 }
319
320 $this->config->saveConfig($newConfig);
321
322 return true;
323 }
324
325 /**
326 * Adds the scheduled emails to the queue when the date is reached
327 */
328 private function queueScheduledCampaigns(): void
329 {
330 if (in_array(self::STEP_SCHEDULE, $this->skip)) {
331 return;
332 }
333
334 $queueClass = new QueueClass();
335 $nbScheduled = $queueClass->scheduleReady();
336 if (!empty($nbScheduled)) {
337 $this->messages[] = acym_translationSprintf('ACYM_NB_SCHEDULED', $nbScheduled);
338 $this->detailMessages = array_merge($this->detailMessages, $queueClass->messages);
339 $this->processed = true;
340 }
341 }
342
343 /**
344 * Cleans the emails that are stuck in the queue for too long (disabled/unconfirmed recipients)
345 */
346 private function cleanQueue(): void
347 {
348 if (in_array(self::STEP_CLEAN_QUEUE, $this->skip)) {
349 return;
350 }
351
352 $queueClass = new QueueClass();
353 $deletedNb = $queueClass->cleanQueue();
354
355 if (!empty($deletedNb)) {
356 $this->messages[] = acym_translationSprintf('ACYM_EMAILS_REMOVED_QUEUE_CLEAN', $deletedNb);
357 $this->processed = true;
358 }
359 }
360
361 private function handleQueue(): void
362 {
363 if (in_array(self::STEP_SEND, $this->skip) || !$this->canSendQueuedEmails()) {
364 return;
365 }
366
367 if (!$this->isSendingCall && acym_level(ACYM_ENTERPRISE) && function_exists('curl_multi_exec')) {
368 $this->handleMultiCron();
369 }
370
371 if ($this->isSendingCall || $this->config->get('dedicated_send_process', 0) != 1) {
372 $this->sendQueuedEmails();
373 }
374 }
375
376 /**
377 * Makes sure the current process doesn't reach the server time limit
378 */
379 private function checkTimeRemaining(): void
380 {
381 $time = time();
382 if (empty($this->cronTimeLimit) || $time < $this->cronTimeLimit) {
383 return;
384 }
385
386 $this->messages[] = acym_translation('ACYM_CRON_TIME_LIMIT_REACHED');
387
388 $this->cronTimeLimitReached = true;
389 }
390
391 /**
392 * If the configuration is set to send multiple batches of emails at the same time
393 */
394 private function handleMultiCron(): void
395 {
396 $emailsPerBatches = $this->config->get('queue_nbmail_auto', 70);
397 if (empty($emailsPerBatches)) {
398 return;
399 }
400
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;
412 }
413
414 $cronParams = '&t='.time();
415 if (!empty($this->config->get('cron_security', 0)) && !empty($this->config->get('cron_key'))) {
416 $cronParams .= '&cronKey='.$this->config->get('cron_key');
417 }
418
419 $urls = [];
420 for ($i = $startCron; $i < $nbBatches; $i++) {
421 $urls[] = acym_frontendLink('cron&task=cron&type=sending&startqueue='.($emailsPerBatches * $i).$cronParams);
422 }
423
424 acym_asyncUrlCalls($urls);
425 }
426
427 private function handleBounceMessages(): void
428 {
429 $this->checkTimeRemaining();
430 $time = time();
431 $autoBounceHandlingActive = (int)$this->config->get('auto_bounce', 0) === 1;
432 $autoBounceHandlingNextTime = (int)$this->config->get('auto_bounce_next', 0);
433 $autoBounceHandlingFrequency = (int)$this->config->get('auto_bounce_frequency', 0);
434 $isEnterprise = acym_level(ACYM_ENTERPRISE);
435
436 if (in_array(self::STEP_BOUNCE, $this->skip) || $this->cronTimeLimitReached || !$isEnterprise || !$autoBounceHandlingActive || $time < $autoBounceHandlingNextTime) {
437 return;
438 }
439
440 $this->config->saveConfig(
441 [
442 'auto_bounce_last' => $time,
443 'auto_bounce_next' => $time + $autoBounceHandlingFrequency,
444 ]
445 );
446
447 $bounceHelper = new BounceHelper();
448 $bounceHelper->report = false;
449 $bounceHelper->stoptime = $this->cronTimeLimit;
450
451 $newConfig = [];
452 if ($bounceHelper->init() && $bounceHelper->connect()) {
453 $nbMessages = $bounceHelper->getNBMessages();
454 $nbMessagesReport = acym_translationSprintf('ACYM_NB_MAIL_MAILBOX', $nbMessages);
455 $this->messages[] = $nbMessagesReport;
456 $newConfig['auto_bounce_report'] = $nbMessagesReport;
457 $this->detailMessages[] = $nbMessagesReport;
458 if (!empty($nbMessages)) {
459 $bounceHelper->handleMessages();
460 $this->processed = true;
461 }
462 $this->detailMessages = array_merge($this->detailMessages, $bounceHelper->messages);
463 } else {
464 $bounceErrors = $bounceHelper->getErrors();
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...
467 if (!empty($bounceErrors[0])) {
468 $bounceErrors[0] = acym_translation('ACYM_BOUNCE_HANDLING').' : '.$bounceErrors[0];
469 }
470 $this->messages = array_merge($this->messages, $bounceErrors);
471 $this->processed = true;
472 $this->errorDetected = true;
473 }
474
475 $this->config->saveConfig($newConfig);
476 }
477
478 /**
479 * Triggers the automations based on time frequency
480 */
481 private function handleAutomations(): void
482 {
483 $this->checkTimeRemaining();
484 if (in_array(self::STEP_AUTOMATION, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
485 return;
486 }
487
488 $automationClass = new AutomationClass();
489 $automationClass->trigger('classic');
490
491 $userStatusCheckTriggers = [];
492 acym_trigger('onAcymDefineUserStatusCheckTriggers', [&$userStatusCheckTriggers]);
493 $automationClass->trigger($userStatusCheckTriggers);
494
495 if (!empty($automationClass->report)) {
496 if ($automationClass->didAnAction) {
497 $this->processed = true;
498 }
499 $this->messages = array_merge($this->messages, $automationClass->report);
500 }
501 }
502
503 private function handleAutomaticCampaigns(): void
504 {
505 $this->checkTimeRemaining();
506 if (in_array(self::STEP_CAMPAIGN, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
507 return;
508 }
509
510 $campaignClass = new CampaignClass();
511 $campaignClass->triggerAutoCampaign();
512
513 if (!empty($campaignClass->messages)) {
514 $this->messages = array_merge($this->messages, $campaignClass->messages);
515 $this->processed = true;
516 }
517 }
518
519 /**
520 * Sends special emails such as birthday or WooCommerce reminders
521 */
522 private function handleSpecificEmails(): void
523 {
524 $this->checkTimeRemaining();
525 if (in_array(self::STEP_SPECIFIC, $this->skip) || $this->cronTimeLimitReached) {
526 return;
527 }
528
529 $campaignClass = new CampaignClass();
530 $specialTypes = [];
531
532 acym_trigger('getCampaignTypes', [&$specialTypes]);
533
534 $specialMail = $campaignClass->getCampaignsByTypes($specialTypes, true);
535 $time = time();
536
537 acym_trigger('filterSpecificMailsToSend', [&$specialMail, $time]);
538
539 foreach ($specialMail as $onespecialMail) {
540 $campaignClass->send($onespecialMail->id);
541 }
542 }
543
544 /**
545 * Sends follow-ups based on time frequency like birthdays
546 */
547 private function handleFollowups(): void
548 {
549 $this->checkTimeRemaining();
550 if (in_array(self::STEP_FOLLOWUP, $this->skip) || $this->cronTimeLimitReached) {
551 return;
552 }
553
554 $followupClass = new FollowupClass();
555 $followups = $followupClass->getFollowupDailyBases();
556
557 // Only once a day
558 $dailyHour = $this->config->get('daily_hour', '12');
559 $dailyMinute = $this->config->get('daily_minute', '00');
560 // The day it is currently based on the timezone specified in the CMS configuration
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
563 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
564 $time = time();
565
566 foreach ($followups as $followup) {
567 $lastTrigger = empty($followup->last_trigger) ? '' : acym_date($followup->last_trigger, 'Y-m-d');
568 if ($time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && $lastTrigger != $dayBasedOnCMSTimezone) {
569 if (!empty($followup->condition)) {
570 $followup->condition = json_decode($followup->condition, true);
571 }
572
573 acym_trigger('onAcymFollowupDailyBasesNeedToBeTriggered', [$followup]);
574 }
575 }
576 }
577
578 /**
579 * Checks messages received on mailboxes and triggers actions accordingly
580 */
581 private function handleMailboxActions(): void
582 {
583 $this->checkTimeRemaining();
584 if (in_array(self::STEP_MAILBOX_ACTION, $this->skip) || $this->cronTimeLimitReached || !acym_level(ACYM_ENTERPRISE)) {
585 return;
586 }
587
588 $mailboxActionClass = new MailboxClass();
589 $mailboxes = $mailboxActionClass->getAllActiveReadyWithActions();
590
591 if (empty($mailboxes)) {
592 return;
593 }
594
595 $mailboxHelper = new MailboxHelper();
596 $mailboxHelper->report = false;
597 foreach ($mailboxes as $oneMailboxAction) {
598 $this->processed = true;
599
600 if (!$mailboxHelper->isConnectionValid($oneMailboxAction, false)) {
601 $this->messages[] = acym_translationSprintf('ACYM_CONNECTION_FAILED_MAILBOX_X', $oneMailboxAction->id.'-'.$oneMailboxAction->name);
602 $this->errorDetected = true;
603 continue;
604 }
605
606 $nbMessages = $mailboxHelper->getNBMessages();
607 if (!$nbMessages) {
608 $this->messages[] = acym_translationSprintf('ACYM_NO_MESSAGE_IN_MAILBOX_X', $oneMailboxAction->id.'-'.$oneMailboxAction->name);
609 $mailboxHelper->close();
610 continue;
611 }
612
613 $mailboxHelper->handleAction();
614 $mailboxHelper->close();
615
616
617 // Update next trigger
618 $oneMailboxAction->nextdate = time() + $oneMailboxAction->frequency;
619 unset($oneMailboxAction->conditions);
620 unset($oneMailboxAction->actions);
621 $mailboxActionClass->save($oneMailboxAction);
622 }
623 }
624
625 private function cleanData(): void
626 {
627 // Clean the history and detailed stats based on the configuration
628 if (!in_array(self::STEP_DELETE_HISTORY, $this->skip) && $this->isDailyCron()) {
629 $userStatClass = new UserStatClass();
630 $userDetailedStatsDeleted = $userStatClass->deleteDetailedStatsPeriod();
631 if (!empty($userDetailedStatsDeleted['message'])) {
632 $this->messages[] = $userDetailedStatsDeleted['message'];
633 $this->processed = true;
634 }
635
636 $userClass = new UserClass();
637 $userHistoryDeleted = $userClass->deleteHistoryPeriod();
638 if (!empty($userHistoryDeleted['message'])) {
639 $this->messages[] = $userHistoryDeleted['message'];
640 $this->processed = true;
641 }
642
643 $mailArchiveClass = new MailArchiveClass();
644 $archiveDeleted = $mailArchiveClass->deleteArchivePeriod();
645 if (!empty($archiveDeleted['message'])) {
646 $this->messages[] = $archiveDeleted['message'];
647 $this->processed = true;
648 }
649 }
650
651 // Clean data on external sending method
652 if (!in_array(self::STEP_CLEAN_DATA_EXTERNAL_SENDING_METHOD, $this->skip) && $this->isDailyCron()) {
653 acym_trigger('onAcymCleanDataExternalSendingMethod');
654 }
655
656 // Clean export changes
657 if (!in_array(self::STEP_CLEAN_EXPORT_CHANGES, $this->skip) && $this->isDailyCron()) {
658 $exportHelper = new ExportHelper();
659 $exportHelper->cleanExportChangesFile();
660 }
661
662 if ($this->isDailyCron()) {
663 $time = time();
664 $this->config->saveConfig(['cron_last_daily' => $time]);
665 }
666 }
667
668 private function isDailyCron(): bool
669 {
670 // Only once a day
671 $dailyHour = $this->config->get('daily_hour', '12');
672 $dailyMinute = $this->config->get('daily_minute', '00');
673 // The day it is currently based on the timezone specified in the CMS configuration
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
676 $dayBasedOnCMSTimezoneAtSpecifiedHour = acym_getTimeFromCMSDate($dayBasedOnCMSTimezone.' '.$dailyHour.':'.$dailyMinute);
677
678 $time = time();
679
680 //If we do not have run the cron we set it to yesterday so it will be trigger
681 $lastCronDayBasedOnCMSTimezone = acym_date($this->config->get('cron_last_daily', $time - 86400), 'Y-m-d');
682
683 return $time >= $dayBasedOnCMSTimezoneAtSpecifiedHour && $lastCronDayBasedOnCMSTimezone != $dayBasedOnCMSTimezone;
684 }
685
686 private function handleABTestCampaigns(): void
687 {
688 $this->checkTimeRemaining();
689 if (in_array(self::STEP_ABTEST, $this->skip) || $this->cronTimeLimitReached) {
690 return;
691 }
692
693 $campaignClass = new CampaignClass();
694 $abTestCampaigns = $campaignClass->getAllAbTestCampaignsToFinishSending();
695 foreach ($abTestCampaigns as $campaign) {
696 if ($campaignClass->finishAbTestCampaign($campaign)) {
697 $this->messages[] = acym_translationSprintf('ACYM_ABTEST_CAMPAIGN_X_FINAL_VERSION_SENT', $campaign->id);
698 $this->processed = true;
699 }
700 }
701 }
702
703 private function handleScenario(): void
704 {
705 $this->checkTimeRemaining();
706 if (in_array(self::STEP_SCENARIO, $this->skip) || $this->cronTimeLimitReached) {
707 return;
708 }
709
710 $scenarioHelper = new ScenarioHelper();
711 $scenarioHelper->executeAvailableSteps();
712 $scenarioHelper->triggerTimeScenarios();
713 }
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 */
718 private function continueCronCall(): void
719 {
720 if ($this->externalSendingNotFinished) {
721 $cronKey = '';
722 if (!empty($this->config->get('cron_security', 0)) && !empty($this->config->get('cron_key'))) {
723 $cronKey = '&cronKey='.$this->config->get('cron_key');
724 }
725 acym_makeCurlCall(acym_frontendLink('cron&task=cron&external_sending_repeat=1&t='.time().$cronKey), ['verifySsl' => false]);
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;
806 }
807 }
808