queueClass = new QueueClass(); $this->userClass = new UserClass(); $this->send_limit = (int)$this->config->get('queue_nbmail', 40); acym_increasePerf(); @ini_set('default_socket_timeout', 10); @ignore_user_abort(true); $timelimit = intval(ini_get('max_execution_time')); if (empty($timelimit)) { $timelimit = 600; } if (!empty($timelimit)) { $this->stoptime = time() + $timelimit - 4; } } public function process(): bool { $creditsLeft = 10000; acym_trigger('onAcymCreditsLeft', [&$creditsLeft]); if (empty($creditsLeft)) { $this->finish = true; if ($this->report) { acym_display(acym_translation('ACYM_NOT_ENOUGH_CREDITS'), 'info'); } return false; } if ($this->send_limit > $creditsLeft) { $this->send_limit = $creditsLeft; } $queueClass = new QueueClass(); $queueClass->emailtypes = $this->emailTypes; $queueElements = $queueClass->getReady($this->send_limit, $this->id); if (empty($queueElements)) { $this->finish = true; if ($this->report) { acym_display(acym_translation('ACYM_NO_PROCESS'), 'info'); } return true; } if ($this->report) { if (function_exists('apache_get_modules')) { $modules = apache_get_modules(); $this->mod_security2 = in_array('mod_security2', $modules); } @ini_set('output_buffering', 'off'); @ini_set('zlib.output_compression', 0); if (!headers_sent()) { while (ob_get_level() > 0 && $this->obend++ < 3) { @ob_end_flush(); } } $disp = ''; $disp .= ''.acym_translation('ACYM_SEND_PROCESS').''; $disp .= ''; $disp .= '

'.acym_translation( 'ACYM_DONT_CLOSE' ).'

'; $disp .= "
"; $disp .= ""; $disp .= acym_translation('ACYM_SEND_PROCESS').': '.$this->start.' / '.$this->total; $disp .= '
'; $disp .= ""; $disp .= '

'; $url = acym_completeLink('queue&task=continuesend&id='.$this->id.'&totalsend='.$this->total, true, true).'&alreadysent='; $disp .= ''; echo $disp; if (function_exists('ob_flush')) { @ob_flush(); } if (!$this->mod_security2) { @flush(); } } $mailerHelper = new MailerHelper(); $mailerHelper->report = false; if ($this->config->get('smtp_keepalive', 1) || in_array($this->config->get('mailer_method'), ['elasticemail'])) { $mailerHelper->SMTPKeepAlive = true; } $queueDelete = []; $queueUpdate = []; $statsAdd = []; $maxTry = (int)$this->config->get('queue_try', 0); $currentMail = $this->start; $this->nbprocess = 0; if (count($queueElements) < $this->send_limit) { $this->finish = true; } $externalSending = false; acym_trigger('onAcymProcessQueueExternalSendingCampaign', [&$externalSending]); if ($externalSending) { $listExternalSending = []; if (!empty($this->id)) { acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $this->id]); } else { $mailIds = []; foreach ($queueElements as $oneElement) { if (!in_array($oneElement->mail_id, $mailIds)) { $mailIds[] = $oneElement->mail_id; acym_trigger('onAcymInitExternalSendingMethodBeforeSend', [&$listExternalSending, $oneElement->mail_id]); } } } } $sentEmails = []; $emailFrequency = $this->fromManual ? 0 : intval($this->config->get('email_frequency', 0)); if ($emailFrequency < 0) { $emailFrequency = 0; } $userLists = []; $userClass = new UserClass(); $isSendingMethodByListActive = false; acym_trigger('sendingMethodByListActive', [&$isSendingMethodByListActive]); $mailerHelper->isSendingMethodByListActive = $isSendingMethodByListActive; foreach ($queueElements as $oneQueue) { if ($isSendingMethodByListActive && empty($userLists[$oneQueue->user_id])) { $userSubscriptions = $userClass->getUserStandardListIdById($oneQueue->user_id); $userLists[$oneQueue->user_id] = empty($userSubscriptions) ? [] : $userSubscriptions; } if (!empty($emailFrequency)) { sleep($emailFrequency); } $currentMail++; $this->nbprocess++; if ($this->report) { echo ''; if (function_exists('ob_flush')) { @ob_flush(); } if (!$this->mod_security2) { @flush(); } } $isAbTesting = false; if (!empty($oneQueue->sending_params)) { $sendingParams = json_decode($oneQueue->sending_params, true); $isAbTesting = isset($sendingParams['abtest']); } $this->triggerSentHook((int)$oneQueue->mail_id); try { $mailerHelper->isAbTest = $isAbTesting; $mailerHelper->listsIds = empty($userLists[$oneQueue->user_id]) ? [] : $userLists[$oneQueue->user_id]; $mailerHelper->setSendingMethodSetting(); $result = $mailerHelper->sendOne($oneQueue->mail_id, $oneQueue->user_id); } catch (\Exception $e) { $result = false; $this->displayMessage([$e->getMessage()], self::MESSAGE_TYPE_ERROR, $e->getCode()); } if (empty($sentEmails[$oneQueue->mail_id])) { $sentEmails[$oneQueue->mail_id] = $mailerHelper->Body; } $queueDeleteOk = true; $otherMessage = ''; if ($result === true) { $this->successSend++; $this->consecutiveError = 0; $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id; $statsAdd[$oneQueue->mail_id][1][] = $oneQueue->user_id; $queueDeleteOk = $this->deleteQueue($queueDelete); $mailerHelper->triggerFollowUpAgain($oneQueue->mail_id, $oneQueue->user_id); $queueDelete = []; if ($this->nbprocess % 10 == 0) { $this->statsAdd($statsAdd); $this->queueUpdate($queueUpdate); $statsAdd = []; $queueUpdate = []; } } elseif ($mailerHelper->dtextsFailed) { $this->consecutiveError = 0; $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id; $queueDeleteOk = $this->deleteQueue($queueDelete); $queueDelete = []; } else { acym_trigger('onAcymFailedSendingEmail', [$oneQueue->mail_id, $oneQueue->user_id, $mailerHelper->errorNumber]); $this->errorSend++; $shouldTrySendingLater = false; if (in_array($mailerHelper->errorNumber, MailerHelper::NEW_TRY_ERRORS)) { if (empty($maxTry) || $oneQueue->try < $maxTry - 1) { $shouldTrySendingLater = true; if ($mailerHelper->failedCounting) { $otherMessage = acym_translationSprintf('ACYM_QUEUE_NEXT_TRY', 60); } } if ($mailerHelper->errorNumber === 1) { $this->consecutiveError++; } if ($this->consecutiveError === 2) { sleep(1); } } if (!$shouldTrySendingLater) { $queueDelete[$oneQueue->mail_id][] = $oneQueue->user_id; $statsAdd[$oneQueue->mail_id][0][] = $oneQueue->user_id; if ($mailerHelper->errorNumber === 1 && $this->config->get('bounce_action_maxtry')) { $queueDeleteOk = $this->deleteQueue($queueDelete); $queueDelete = []; $otherMessage .= $this->failedActions((int)$oneQueue->user_id); } } elseif ($mailerHelper->failedCounting) { $queueUpdate[$oneQueue->mail_id][] = $oneQueue->user_id; } $mailerHelper->failedCounting = true; } $messageOnScreen = '[ID '.$oneQueue->mail_id.'] '.$mailerHelper->reportMessage; if (!empty($otherMessage)) { $messageOnScreen .= ' => '.$otherMessage; } $this->displayMessage([$messageOnScreen], $result ? self::MESSAGE_TYPE_SUCCESS : self::MESSAGE_TYPE_ERROR, $currentMail); if (!$queueDeleteOk) { $this->finish = true; break; } if (!empty($this->stoptime) && $this->stoptime < time()) { $this->displayMessage([acym_translation('ACYM_SEND_REFRESH_TIMEOUT')], self::MESSAGE_TYPE_WARNING); if ($this->nbprocess < count($queueElements)) { $this->finish = false; } break; } if ($this->consecutiveError > 3 && $this->successSend > 3) { $this->displayMessage([acym_translation('ACYM_SEND_REFRESH_CONNECTION')], self::MESSAGE_TYPE_WARNING); break; } if ($this->consecutiveError > 5 || ($this->report && connection_aborted())) { $this->finish = true; break; } } if ($externalSending) { foreach ($sentEmails as $mailId => $content) { if (!$queueClass->isSendingFinished($mailId)) continue; acym_trigger('onAcymSendCampaignOnExternalSendingMethod', [$mailId, $content]); } } $this->deleteQueue($queueDelete); $this->statsAdd($statsAdd); $this->queueUpdate($queueUpdate); if ($mailerHelper->SMTPKeepAlive) { $mailerHelper->smtpClose(); } if (!empty($this->total) && $currentMail >= $this->total) { $this->finish = true; } if ($this->consecutiveError > 5) { $this->handleError(); return false; } if ($this->report && !$this->finish) { echo ''; } if ($this->report) { echo ""; while ($this->obend-- > 0) { ob_start(); } exit; } return true; } public function statsAdd(array $statsAdd): void { if (empty($statsAdd)) { return; } $userStatClass = new UserStatClass(); $mailStatClass = new MailStatClass(); $currentDate = acym_date('now', 'Y-m-d H:i:s'); $subscriberIds = []; foreach ($statsAdd as $mailId => $infos) { $mailId = intval($mailId); foreach ($infos as $status => $subscribers) { foreach ($subscribers as $oneSubscriber) { $oneSubscriber = intval($oneSubscriber); $userStat = []; $userStat['user_id'] = $oneSubscriber; $userStat['mail_id'] = $mailId; $userStat['send_date'] = $currentDate; $userStat['fail'] = $status ? 0 : 1; $userStat['sent'] = $status ? 1 : 0; $userStat['statusSending'] = $status; $userStatClass->save($userStat, true); if ($status) { $subscriberIds[] = $oneSubscriber; } } } $nbSent = empty($infos[1]) ? 0 : count($infos[1]); $nbFail = empty($infos[0]) ? 0 : count($infos[0]); $mailStat = [ 'mail_id' => $mailId, 'sent' => $nbSent, 'fail' => $nbFail, ]; $mailStatClass->save($mailStat); } if (!empty($subscriberIds)) { acym_query('UPDATE `#__acym_user` SET `last_sent_date` = '.acym_escapeDB($currentDate).' WHERE `id` IN ('.implode(',', $subscriberIds).')'); } } private function triggerSentHook(int $mailId): void { static $triggered = []; if (!empty($triggered[$mailId])) { return; } $triggered[$mailId] = true; acym_triggerCmsHook('onAcymSendMail', [$mailId]); } private function deleteQueue(array $queueDelete): bool { if (empty($queueDelete)) { return true; } $status = true; foreach ($queueDelete as $mailid => $subscribers) { $nbsub = count($subscribers); $res = $this->queueClass->deleteOne($subscribers, $mailid); if ($res === false || !empty($this->queueClass->errors)) { $status = false; $this->displayMessage($this->queueClass->errors, self::MESSAGE_TYPE_ERROR); } else { $nbdeleted = $res; if ($nbdeleted != $nbsub) { $status = false; $this->displayMessage( [$nbdeleted < $nbsub ? acym_translation('ACYM_QUEUE_DOUBLE') : $nbdeleted.' emails deleted from the queue whereas we only have '.$nbsub.' subscribers'], self::MESSAGE_TYPE_ERROR ); } } } return $status; } private function queueUpdate(array $queueUpdate): void { if (empty($queueUpdate)) { return; } foreach ($queueUpdate as $mailid => $subscribers) { $this->queueClass->delayFailed($mailid, $subscribers); } } private function handleError(): void { $this->finish = true; $message = acym_translation('ACYM_SEND_STOPED'); $message .= '
'; $message .= acym_translation('ACYM_SEND_KEPT_ALL'); $message .= '
'; if ($this->report) { if (empty($this->successSend) && empty($this->start)) { $message .= acym_translation('ACYM_SEND_CHECKONE'); $message .= '
'; $message .= acym_translation('ACYM_SEND_ADVISE_LIMITATION'); } else { $message .= acym_translation('ACYM_SEND_REFUSE'); $message .= '
'; if (!acym_level(ACYM_ESSENTIAL)) { $message .= acym_translation('ACYM_SEND_CONTINUE_COMMERCIAL'); } else { $message .= acym_translation('ACYM_SEND_CONTINUE_AUTO'); } } } $this->displayMessage([$message], self::MESSAGE_TYPE_WARNING); } private function displayMessage(array $messages, int $status, int $num = 0): void { foreach ($messages as $message) { $this->messages[] = strip_tags($message); } if (!$this->report) { return; } $color = $status === self::MESSAGE_TYPE_SUCCESS ? 'green' : ($status === self::MESSAGE_TYPE_WARNING ? 'orange' : 'red'); foreach ($messages as $message) { if (!empty($num)) { echo '
'.$num.' : '.$message.''; } else { echo ''; } } if (function_exists('ob_flush')) { @ob_flush(); } if (!$this->mod_security2) { @flush(); } } private function failedActions(int $userId): string { $listId = 0; if (in_array($this->config->get('bounce_action_maxtry'), ['sub', 'remove', 'unsub'])) { $subscriptions = $this->userClass->getUserSubscriptionById($userId); } $message = ''; switch ($this->config->get('bounce_action_maxtry')) { case 'sub' : $listId = $this->config->get('bounce_action_lists_maxtry'); if (!empty($listId)) { $message .= ' user '.$userId.' subscribed to list n°'.$listId; $this->userClass->subscribe($userId, [$listId]); } case 'remove' : $unsubLists = array_diff(array_keys($subscriptions), [$listId]); if (!empty($unsubLists)) { $message .= ' user '.$userId.' removed from lists '.implode(',', $unsubLists); $this->userClass->removeSubscription($userId, $unsubLists); } else { $message .= ' user '.$userId.' not subscribed'; } break; case 'unsub' : $unsubLists = array_diff(array_keys($subscriptions), [$listId]); if (!empty($unsubLists)) { $message .= ' user '.$userId.' unsubscribed from lists '.implode(',', $unsubLists); $this->userClass->unsubscribe($userId, $unsubLists); } else { $message .= ' user '.$userId.' not unsubscribed'; } break; case 'delete' : $message .= ' user '.$userId.' deleted'; $this->userClass->delete($userId); break; case 'block' : $message .= ' user '.$userId.' blocked'; $this->userClass->deactivate($userId); $this->queueClass->deleteOne($userId); break; } return $message; } }