'ACYM_ERROR_SEE_LOGS', 1 => 'ACYM_PLEASE_INSTALL_LATEST_VERSION', 2 => 'ACYM_EMAIL_SEND_FAIL_SEE_LOGS', 3 => 'ACYM_DOMAIN_NOT_ATTACHED_TO_THE_SITE', 4 => 'ACYM_NOT_ALLOWED_TO_DELETE_THIS_DOMAIN', 5 => 'ACYM_DOMAIN_DOES_NOT_EXIST', 6 => 'ACYM_DOMAIN_STATUS_SUCCESSFULLY_OBTAINED', 7 => 'ACYM_AN_ERROR_RELATED_TO_THE_REMAINING_CREDITS_OCCURRED', 8 => 'ACYM_LICENSE_EXPIRED', 9 => 'ACYM_IDENTITY_CREATED', 10 => 'ACYM_DOMAIN_ALREADY_EXISTS_FOR_THIS_WEBSITE', 11 => 'ACYM_DOMAIN_ALREADY_EXISTS_AND_DOES_NOT_BELONG_TO_YOU', 12 => 'ACYM_NO_DOMAINS_TO_CHECK', 13 => 'ACYM_DOMAINS_VERIFIED', 14 => 'ACYM_ERROR_WHILE_CHECKING_DOMAINS', 15 => 'ACYM_BLOCKED_LICENSE', 16 => 'ACYM_MISSING_EMAIL', 17 => 'ACYM_MISSING_ARGUMENTS', 18 => 'ACYM_NO_CREDITS_LEFT', 19 => 'ACYM_MISSING_API_KEY', 20 => 'ACYM_DOMAIN_NOT_FOUND', 21 => 'ACYM_LICENSE_NOT_FOUND', 22 => 'ACYM_EMAIL_SENT', 23 => 'ACYM_INVALID_API_KEY', 24 => 'ACYM_LICENSE_BLOCKED', 25 => 'ACYM_SEE_LOGS_FILE', 26 => 'ACYM_SERVICE_TEMPORARY_UNAVAILABLE', 27 => 'ACYM_DOMAIN_ONLY_ON_ONE_LICENSE', 28 => 'ACYM_EMAIL_UNDER_VERIFICATION', 29 => 'ACYM_DOMAIN_BLOCKED', 30 => 'ACYM_DOMAIN_UNDER_REVIEW', ]; private array $errorCodes = [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30]; private array $cnameErrors = []; public function __construct() { parent::__construct(); $this->pluginDescription->name = self::SENDING_METHOD_NAME; } protected function callApiSendingMethod( string $url, array $data = [], array $headers = [], string $type = 'GET', array $authentication = [], bool $dataDecoded = false, array $files = [] ): array { if (strpos($url, self::SENDING_METHOD_API_URL) === false) { $url = self::SENDING_METHOD_API_URL.'api/'.$url; } $defaultHeaders = [ 'Accept' => 'application/json', 'Content-Type' => 'application/json', 'API-KEY' => $this->config->get(self::SENDING_METHOD_ID.'_apikey', ''), 'Version' => $this->config->get('version'), ]; $usedHeaders = array_map('strtolower', array_keys($headers)); foreach ($defaultHeaders as $key => $value) { if (!in_array(strtolower($key), $usedHeaders, true)) { $headers[$key] = $value; } } $result = parent::callApiSendingMethod($url, $data, $headers, $type, $authentication, $dataDecoded, $files); if (!empty($result['error_curl'])) { $result['message'] = $result['error_curl']; $this->errors[] = $result['error_curl']; $this->errorCallback(); } else { $result['message'] = $this->translate($result); } return $result; } public function onAcymGetSendingMethods(&$data, $isMailer = false) { $data['sendingMethods'][self::SENDING_METHOD_ID] = [ 'name' => $this->pluginDescription->name, 'image' => ACYM_IMAGES.'mailers/'.self::SENDING_METHOD_ID.'.png', 'recommended' => true, 'image_class' => '', ]; } public function onAcymAttachedLicenseOption() { $licenseKey = $this->config->get('license_key'); $acyMailerKey = $this->config->get('acymailer_apikey'); $mailerMethod = $this->config->get('mailer_method'); if (empty($licenseKey) || empty($acyMailerKey) || $mailerMethod === self::SENDING_METHOD_ID) { return; } ?>
config->get('license_key'); $acyMailerKey = $this->config->get('acymailer_apikey'); $domains = $this->config->get(self::SENDING_METHOD_ID.'_domains', []); $domainsWaiting = []; if (!empty($domains)) { $domains = json_decode($domains, true); $domainsWaiting = array_filter($domains, function ($domain) { return $domain['status'] !== 'SUCCESS'; }); } $unverifiedDomains = $this->getDomainsUnVerified(); ?>
'ACYM_BOUNCE_RATE_DESC']); ?>
'ACYM_COMPLAINT_RATE_DESC']); ?>
$cnameRecord) { $domain['CnameRecords'][$i]['isValid'] = $this->checkCnameEntry($cnameRecord['name'], $cnameRecord['value']); $isValid = $isValid && $domain['CnameRecords'][$i]['isValid']; $domain['CnameRecords'][$i]['tooltipMessages'] = $this->cnameErrors; $this->cnameErrors = []; } if (!$isValid && $domain['status'] === 'SUCCESS') { $domain['status'] = 'PENDING'; $this->config->saveConfig([self::SENDING_METHOD_ID.'_domains' => json_encode($domains)]); } $bounceRateColorClass = $this->getRateColor( empty($domain['bounce_rate']) ? 0 : $domain['bounce_rate'], empty($domain['allowed_bounce_rate']) ? 0 : $domain['allowed_bounce_rate'], empty($domain['warning_bounce_rate']) ? 0 : $domain['warning_bounce_rate'] ); $complaintRateColorClass = $this->getRateColor( empty($domain['complaint_rate']) ? 0 : $domain['complaint_rate'], empty($domain['allowed_complaint_rate']) ? 0 : $domain['allowed_complaint_rate'], empty($domain['warning_complaint_rate']) ? 0 : $domain['warning_complaint_rate'] ); ?>
'', 'textShownInTooltip' => $tooltipText, ] ); ?>
displayRateIcon( empty($domain['bounce_rate']) ? 0 : $domain['bounce_rate'], empty($domain['allowed_bounce_rate']) ? 0 : $domain['allowed_bounce_rate'], empty($domain['warning_bounce_rate']) ? 0 : $domain['warning_bounce_rate'] ); ?>
displayRateIcon( empty($domain['complaint_rate']) ? 0 : $domain['complaint_rate'], empty($domain['allowed_complaint_rate']) ? 0 : $domain['allowed_complaint_rate'], empty($domain['warning_complaint_rate']) ? 0 : $domain['warning_complaint_rate'] ); ?>
displayCnameStatus($cnameRecord); ?>
displayCnameStatus($cnameRecord); ?>
'', 'textShownInTooltip' => acym_translation('ACYM_SHOW_DNS_SETTINGS'), ] ); $buttonModal = ob_get_clean(); acym_modal( $buttonModal, $modalContent, null, [], [], true, true, 'acym__config__acymailer__cname__modal__container' ); acym_tooltip( [ 'hoveredText' => '', 'textShownInTooltip' => acym_translation('ACYM_DELETE'), ] ); ?>
isLogFileEmpty()) { ?>
'button', 'data-ajax' => 'true', 'data-iframe' => '&ctrl=configuration&task=seeLogs&filename='.$this->logFilename, ] ); ?>
callApiSendingMethod( 'get_credits', [], [ 'API-KEY' => $credentials[self::SENDING_METHOD_ID.'_apikey'], ] ); if (empty($response)) { $errorMsg = acym_translation('ACYM_NO_ANSWER'); acym_sendAjaxResponse(acym_translationSprintf('ACYM_ERROR_OCCURRED_WHILE_CALLING_API', $errorMsg), [], false); } elseif (!empty($response['error_curl'])) { acym_sendAjaxResponse(acym_translationSprintf('ACYM_ERROR_OCCURRED_WHILE_CALLING_API', $response['error_curl']), [], false); } elseif (!empty($response['message']) && $response['message'] == 'Invalid private key') { acym_sendAjaxResponse(acym_translation('ACYM_AUTHENTICATION_FAILS_WITH_API_KEY'), [], false); } elseif (!empty($response['message']) && !$this->checkSuccessCode($this->responseCode)) { if ($this->responseCode === 401) { acym_sendAjaxResponse( acym_translationSprintf( 'ACYM_SENDING_METHOD_ERROR_WHILE_ACTION', self::SENDING_METHOD_NAME, acym_translation('ACYM_GETTING_REMAINING_CREDITS'), $response['message'] ), [], false ); } acym_sendAjaxResponse($response['message'], [], false); } else { acym_sendAjaxResponse(acym_translation('ACYM_API_KEY_CORRECT')); } } public function onAcymSendEmail(&$response, $mailerHelper, $to, $from, $replyTo, $bcc = [], $attachments = [], $sendingMethodListParams = []) { if ($mailerHelper->externalMailer !== self::SENDING_METHOD_ID) { return; } $apikey = $this->config->get(self::SENDING_METHOD_ID.'_apikey'); if (empty($apikey)) { $mailerHelper->failedCounting = false; $response['error'] = true; $response['message'] = acym_translation('ACYM_MISSING_API_KEY'); return; } if ($this->isUnsubscribeLinkMissing($mailerHelper)) { $mailerHelper->failedCounting = false; $response['error'] = true; $response['message'] = acym_translation('ACYM_MISSING_UNSUBSCRIBE_LINK'); return; } // In Amazon SES, you must verify each identity used as a "From", "Source", "Sender", or "Return-Path" address. $domainsUsed = [ acym_getDomain($from['email']), acym_getDomain($replyTo['email']), ]; $bounceAddress = $this->getBounceAddress($mailerHelper); if (!empty($bounceAddress)) { $domainsUsed[] = acym_getDomain($bounceAddress); } $domainsUsed = array_unique($domainsUsed); // Maybe don't try to send if one of these domains is not validated? $verifiedDomains = $this->getVerifiedDomains(); $unverifiedDomains = array_diff($domainsUsed, $verifiedDomains); if (!empty($unverifiedDomains)) { $mailerHelper->failedCounting = false; $response['error'] = true; $response['message'] = acym_translationSprintf('ACYM_UNVERIFIED_DOMAINS_PREVENTING_EMAILS_FROM_BEING_SENT_X', implode(', ', $unverifiedDomains)); return; } $recipients = []; if (!empty($mailerHelper->cc) || !empty($mailerHelper->bcc)) { $recipients[] = $to['email']; $recipients = array_merge($recipients, array_column($mailerHelper->cc, 0), array_column($mailerHelper->bcc, 0)); $mailerHelper->cc = []; $mailerHelper->bcc = []; } $responseMailer = $this->callApiSendingMethod( 'send', [ 'email' => $mailerHelper->getSentMIMEMessage(), 'domainsUsed' => $domainsUsed, 'isTransactional' => $mailerHelper->isTransactional, 'isOneTimeMail' => $mailerHelper->isOneTimeMail, 'recipients' => $recipients, ], [], 'POST' ); if (!empty($responseMailer['logs'])) { acym_logError($responseMailer['logs'], self::SENDING_METHOD_ID); } if (!empty($responseMailer['error_curl'])) { $mailerHelper->failedCounting = false; $response['error'] = true; $response['message'] = acym_translationSprintf('ACYM_ERROR_OCCURRED_WHILE_CALLING_API', $responseMailer['error_curl']); } elseif (!$this->checkSuccessCode($this->responseCode)) { $response['error'] = true; $response['message'] = $this->translate($responseMailer); if ($response['message'] === acym_translation('ACYM_NO_CREDITS_LEFT')) { $response['message'] = acym_translation('ACYM_NOT_ENOUGH_CREDITS').' : '; $response['message'] .= ''; $response['message'] .= acym_translation('ACYM_GET_MORE_CREDITS'); $response['message'] .= ''; } // Don't unsubscribe/remove recipients if the error isn't related to them if (!empty($responseMailer['code']) && in_array($responseMailer['code'], [18, 26, 28])) { $mailerHelper->failedCounting = false; } if ($responseMailer['code'] == 26) { $queueClass = new QueueClass(); $queueClass->delayAll(24); } } else { $response['error'] = false; } } public function replaceUserInformation(object &$email, ?object &$user, bool $send = true): void { if (empty($email->externalMailer) || $email->externalMailer !== self::SENDING_METHOD_ID) return; $bounceAddress = $this->getBounceAddress($email); if (!empty($bounceAddress) && method_exists($email, 'addCustomHeader')) { $email->addCustomHeader('Return-Path', $bounceAddress); } } public function ajaxCheckDomain() { $sendingMethod = acym_getVar('string', 'sendingMethod', ''); if ($sendingMethod != self::SENDING_METHOD_ID) { acym_sendAjaxResponse(); } $domains = json_decode($this->config->get(self::SENDING_METHOD_ID.'_domains', '[]'), true); if (empty($domains)) { acym_sendAjaxResponse(); } $remainingDomains = $this->checkDomainsDNS($domains); if (empty($remainingDomains)) { acym_sendAjaxResponse('', ['domains' => $domains]); } $preparedDomains = array_map( function ($domain) { return $domain['domain']; }, $remainingDomains ); $responseApi = $this->callApiSendingMethod( 'getDomainStatus', ['domains' => $preparedDomains], [], 'POST' ); if (empty($responseApi)) { acym_sendAjaxResponse('', ['domains' => $domains]); } if (!$this->checkSuccessCode($this->responseCode)) { acym_sendAjaxResponse(acym_translation('ACYM_ERROR_ON_CALL_ACYBA_WEBSITE').': '.$responseApi['message'], ['domains' => $domains], false); } $errorDomains = []; foreach ($domains as $key => $oneDomain) { $domainName = $oneDomain['domain']; if (!array_key_exists($domainName, $responseApi['data']) || !is_array($responseApi['data'][$domainName])) { $errorDomains[] = acym_translation('ACYM_ERROR_ON_CALL_ACYBA_WEBSITE').': '.$responseApi['data'][$domainName].': '.$domainName; continue; } if (!isset($responseApi['data'][$domainName]['code'])) { $domains[$key] = array_merge($domains[$key], $responseApi['data'][$domainName]); continue; } if (!empty(self::TRANSLATIONS[$responseApi['data'][$domainName]['code']])) { $message = self::TRANSLATIONS[$responseApi['data'][$domainName]['code']]; } else { if (empty($responseApi['data'][$domainName]['message'])) { $message = 'ACYM_DOMAIN_DEFAULT_ERROR'; } else { $message = $this->displayMessage($responseApi['data'][$domainName]['message']); } } acym_enqueueMessage( acym_translation('ACYM_ERROR_ON_CALL_ACYBA_WEBSITE').': '.acym_translationSprintf( $message, $domainName ), 'error' ); } $this->config->saveConfig([self::SENDING_METHOD_ID.'_domains' => json_encode($domains)]); if (!empty($errorDomains)) { acym_sendAjaxResponse(implode(', ', $errorDomains), [], false); } acym_sendAjaxResponse('', ['domains' => $domains]); } public function onAcymDeleteDomain() { $oneDomain = acym_getVar('string', 'oneDomain', ''); if (empty($oneDomain)) { return; } $apikey = $this->config->get(self::SENDING_METHOD_ID.'_apikey'); if (empty($apikey)) { acym_sendAjaxResponse(acym_translation('ACYM_MISSING_API_KEY'), [], false); } $responseApi = $this->callApiSendingMethod( 'deleteDomainIdentity', ['domain' => $oneDomain, 'siteUrl' => acym_baseURI()], [], 'POST' ); if (!empty($responseApi['error_curl'])) { $message = acym_translationSprintf('ACYM_ERROR_OCCURRED_WHILE_CALLING_API', $responseApi['error_curl']); acym_sendAjaxResponse($message, [], false); } elseif (!empty($responseApi['message']) && $responseApi['message'] === 'Invalid private key') { acym_sendAjaxResponse(acym_translation('ACYM_AUTHENTICATION_FAILS_WITH_API_KEY'), [], false); } elseif (!$this->checkSuccessCode($this->responseCode) && (empty($responseApi['code']) || !in_array($responseApi['code'], [5, 4, 3]))) { /* We may ask the API for the deletion of a domain while: - it already doesn't exist (error code 5) - it doesn't belong to the current API key (error code 4) - it isn't linked to this website (error code 3) In all 3 cases, we don't want to keep this domain in the AcyMailing configuration, so we remove it and consider the deletion as a success */ $message = acym_translation('ACYM_ERROR_ON_CALL_ACYBA_WEBSITE').': '.$this->translate($responseApi); if (!empty($responseApi['logs'])) { acym_logError($responseApi['logs'], self::SENDING_METHOD_ID); } acym_sendAjaxResponse($message, [], false); } else { $field = self::SENDING_METHOD_ID.'_domains'; $domains = json_decode($this->config->get($field, '[]'), true); $domains = array_filter($domains, function ($domain) use ($oneDomain) { return $domain['domain'] !== $oneDomain; }); $this->config->saveConfig([$field => json_encode($domains)]); acym_sendAjaxResponse(); } } public function ajaxAddDomain(): void { $oneDomain = acym_getVar('string', 'oneDomain', ''); $this->config->saveConfig(['mailer_method' => self::SENDING_METHOD_ID]); if (empty($oneDomain)) { acym_sendAjaxResponse(acym_translation('ACYM_MISSING_DOMAIN'), [], false); } $oneDomain = acym_strtolower($oneDomain); $apikey = $this->config->get(self::SENDING_METHOD_ID.'_apikey'); if (empty($apikey)) { acym_sendAjaxResponse(acym_translation('ACYM_MISSING_API_KEY'), [], false); } $domainValidation = preg_match('/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/u', $oneDomain); if (!$domainValidation) { $message = acym_translationSprintf('ACYM_THE_DOMAIN_X_IS_NOT_VALID', $oneDomain); acym_sendAjaxResponse($message, ['type' => 'DOMAIN_VALIDATION'], false); } $field = self::SENDING_METHOD_ID.'_domains'; $domains = $this->config->get($field, []); if (!empty($domains)) { $domains = @json_decode($domains, true); } if (!empty($domains[$oneDomain])) { acym_sendAjaxResponse(acym_translation('ACYM_DOMAIN_ALREADY_SENT'), [], false); } $responseApi = $this->callApiSendingMethod( 'createDomainIdentity', ['domain' => $oneDomain, 'siteUrl' => acym_baseURI()], [], 'POST' ); if (!empty($responseApi['error_curl'])) { $message = acym_translationSprintf('ACYM_ERROR_OCCURRED_WHILE_CALLING_API', $responseApi['error_curl']); acym_sendAjaxResponse($message, [], false); } elseif (!empty($responseApi['message']) && $responseApi['message'] === 'Invalid API-KEY') { acym_sendAjaxResponse(acym_translation('ACYM_AUTHENTICATION_FAILS_WITH_API_KEY'), [], false); } elseif (!$this->checkSuccessCode($this->responseCode)) { if (empty($responseApi['code']) || empty(self::TRANSLATIONS[$responseApi['code']])) { $errorCodeTranslationKey = empty($responseApi['message']) ? 'ACYM_DOMAIN_DEFAULT_ERROR' : $this->displayMessage($responseApi['message']); } else { $errorCodeTranslationKey = self::TRANSLATIONS[$responseApi['code']]; } $message = acym_translationSprintf($errorCodeTranslationKey, $oneDomain); acym_sendAjaxResponse($message, [], false); } elseif ($this->responseCode === 201) { $domains[$oneDomain] = [ 'domain' => $oneDomain, 'CnameRecords' => $responseApi['cnameRecords'], 'status' => 'PENDING', ]; $this->config->saveConfig([$field => json_encode($domains)]); acym_sendAjaxResponse($this->translate($responseApi), ['cnameRecords' => $responseApi['cnameRecords']]); } else { $cnameRecords = $responseApi['data'][$oneDomain]['cnameRecords']; $status = $responseApi['data'][$oneDomain]['status']; $domains[$oneDomain] = [ 'domain' => $oneDomain, 'CnameRecords' => $cnameRecords, 'status' => $status, ]; $this->config->saveConfig([$field => json_encode($domains)]); acym_sendAjaxResponse( $this->translate($responseApi), ['cnameRecords' => $cnameRecords, 'alreadyVerified' => $status === 'SUCCESS'] ); } } public function onAcymDisplayPage() { if (intval($this->config->get('walk_through', 0)) === 1) { return; } $unverifiedDomains = $this->getDomainsUnVerified(); if (!empty($unverifiedDomains)) { $message = acym_translationSprintf('ACYM_UNVERIFIED_DOMAINS_PREVENTING_EMAILS_FROM_BEING_SENT_X', implode(', ', $unverifiedDomains)); $notification = [ 'name' => 'unverified_domains', 'removable' => 0, ]; if (acym_getVar('string', 'page') === 'acymailing_dashboard' || acym_getVar('string', 'ctrl') === 'dashboard') { acym_enqueueMessage($message, 'warning', false, [$notification], false); } else { acym_enqueueMessage($message, 'warning', true, [$notification]); } } else { acym_removeDashboardNotification('unverified_domains'); } } public function onAcymGetCreditRemainingSendingMethod(&$html, $reloading = false) { $sendingMethod = $this->config->get('mailer_method', ''); if (empty($sendingMethod) || $sendingMethod != self::SENDING_METHOD_ID) return; $apiKey = $this->config->get(self::SENDING_METHOD_ID.'_apikey', ''); if (empty($apiKey)) return; $response = $this->getRemainingCredits($reloading); if (!isset($response['remaining_credits'])) { if (empty($this->translate($response))) { $html = acym_translation('ACYM_ERROR_CONTACT_ACYMAILING'); } else { $html = acym_translationSprintf( 'ACYM_SENDING_METHOD_ERROR_WHILE_ACTION', self::SENDING_METHOD_NAME, acym_translation('ACYM_GETTING_REMAINING_CREDITS'), $this->translate($response) ); } return; } $unityTranslation = 'ACYM_CREDITS_REMAINING'; $creditsShowed = $response['remaining_credits']; if (!empty($response['is_multisite'])) { $fromEmail = $this->config->get('from_email', ''); // If the license is multisite we check and increment credits on the domain of the from email $domain = explode('@', $fromEmail)[1]; if (empty($domain) || empty($response['domains'][$domain])) { $html = acym_translation('ACYM_DOMAIN_FROM_EMAIL_NOT_VALID_ASS'); } else { $domainData = $response['domains'][$domain]; if ($domainData['is_limited']) { $creditsShowed = $domainData['remaining_credits']; } else { $creditsShowed = $domainData['credits_used']; $unityTranslation = 'ACYM_CREDITS_USED'; } } } // Add security empty text message if (empty($creditsShowed)) { $creditsShowed = '0'; } ob_start(); acym_tooltip( [ 'hoveredText' => $creditsShowed, 'textShownInTooltip' => acym_translation('ACYM_GET_MORE_CREDITS'), 'classContainer' => 'credits_remaining', 'link' => ACYM_ACYMAILING_WEBSITE, ] ); $tooltip = ob_get_clean(); $html = acym_translationSprintf( 'ACYM_SENDING_METHOD_X_UNITY', self::SENDING_METHOD_NAME, $tooltip, ''.esc_html(acym_translation($unityTranslation)).'' ); } /** * Called before loading the emails of a batch to get the credits left * * @param int $creditsLeft * * @return void */ public function onAcymCreditsLeft(&$creditsLeft) { $sendingMethod = $this->config->get('mailer_method', ''); if (empty($sendingMethod) || $sendingMethod != self::SENDING_METHOD_ID) return; $apiKey = $this->config->get(self::SENDING_METHOD_ID.'_apikey', ''); if (empty($apiKey)) { $creditsLeft = 0; return; } $credits = $this->getRemainingCredits(true); if (empty($credits['remaining_credits'])) { $creditsLeft = 0; } else { $creditsLeft = (int)$credits['remaining_credits']; } } public function onAcymAttachLicense($licenseKey) { if (empty($licenseKey)) { return; } $response = $this->callApiSendingMethod(self::SENDING_METHOD_API_URL.'public/licenses/'.$licenseKey); if (!empty($response['id'])) { $this->config->saveConfig([self::SENDING_METHOD_ID.'_apikey' => $response['id']]); } } public function onAcymDetachLicense() { $this->config->saveConfig([self::SENDING_METHOD_ID.'_apikey' => '']); } public function onAcymCampaignSummary(&$data) { if ($this->config->get('mailer_method') !== self::SENDING_METHOD_ID) { return; } $mail = $data['mailClass']->getOneById($data['mailId']); if (empty($mail->body) || !$this->isContainingUnsubscribeLink($mail->body)) { $data['notAllowedSendingError'] = acym_translation('ACYM_UNSUB_LINK'); } $unverifiedDomains = $this->getDomainsUnVerified(); if (!empty($unverifiedDomains)) { $data['notAllowedSendingError'] = acym_translationSprintf( 'ACYM_UNVERIFIED_DOMAINS_PREVENTING_EMAILS_FROM_BEING_SENT_X', implode(', ', $unverifiedDomains) ); } } public function onAcymSendingMethodOptions(&$data) { $data['embedImage'][self::SENDING_METHOD_ID] = false; $data['embedAttachment'][self::SENDING_METHOD_ID] = false; } private function getDomainsUnVerified(): array { if ($this->config->get('mailer_method') !== self::SENDING_METHOD_ID) { return []; } // In Amazon SES, you must verify each identity used as a "From", "Source", "Sender", or "Return-Path" address. $allDomains = []; $allDomains[] = acym_getDomain($this->config->get('from_email')); if ($this->config->get('from_as_replyto') === '0') { $allDomains[] = acym_getDomain($this->config->get('replyto_email')); } if ($this->config->get('multilingual') === '1') { $senderInfoTranslation = $this->config->get('sender_info_translation'); if (!empty($senderInfoTranslation)) { $senderInfoTranslation = json_decode($senderInfoTranslation, true); foreach ($senderInfoTranslation as $oneSenderInfo) { $allDomains[] = acym_getDomain($oneSenderInfo['from_email']); $allDomains[] = acym_getDomain($oneSenderInfo['replyto_email']); } } } $allDomains[] = acym_getDomain($this->config->get('bounce_email')); $allDomains = array_filter(array_unique($allDomains)); $verifiedDomains = $this->getVerifiedDomains(); return array_diff($allDomains, $verifiedDomains); } private function getRateColor(float $rate, float $allowedRate, float $warningRate): string { if (empty($rate) || empty($allowedRate) || empty($warningRate)) { return ''; } if ($rate > $allowedRate) { return 'acym__color__red'; } elseif ($rate >= $warningRate) { return 'acym__color__orange'; } return ''; } private function displayRateIcon(float $rate, float $allowedRate, float $warningRate): void { if (empty($rate) || empty($allowedRate) || empty($warningRate)) { return; } if ($rate > $allowedRate) { acym_tooltip( [ 'hoveredText' => '', 'textShownInTooltip' => acym_translation('ACYM_RATE_BLOCKED'), ] ); } elseif ($rate >= $warningRate) { acym_tooltip( [ 'hoveredText' => '', 'textShownInTooltip' => acym_translation('ACYM_RATE_WARNING'), ] ); } } private function updateDomainRates($domainsRates) { $domains = $this->config->get(self::SENDING_METHOD_ID.'_domains', []); if (empty($domains)) { return; } $domains = @json_decode($domains, true); foreach ($domainsRates as $oneDomain => $oneRate) { if (empty($domains[$oneDomain])) continue; foreach ($oneRate as $rateKey => $rateValue) { $domains[$oneDomain][$rateKey] = $rateValue; } } $this->config->saveConfig([self::SENDING_METHOD_ID.'_domains' => json_encode($domains)]); } private function getVerifiedDomains(): array { $verifiedDomains = []; $domainStatuses = json_decode($this->config->get(self::SENDING_METHOD_ID.'_domains', '[]'), true); if (!empty($domainStatuses)) { foreach ($domainStatuses as $domain => $info) { if ($info['status'] === 'SUCCESS') { $verifiedDomains[] = $domain; } } } return $verifiedDomains; } private function displayMessage($message): string { $correspondances = [ 'AlreadyExistsException' => 'ACYM_DOMAIN_X_ALREADY_EXIST', 'TooManyRequestsException' => 'ACYM_DOMAIN_TO_MANY_REQUEST', 'NotFoundException' => 'ACYM_DOMAIN_NOT_FOUND', 'NotExistException' => 'ACYM_CANT_DELETE_DOMAIN', ]; if (empty($message) || empty($correspondances[$message])) { return 'ACYM_DOMAIN_DEFAULT_ERROR'; } return $correspondances[$message]; } private function getRemainingCredits($forceReload = false) { $time = time(); $lastCheck = $this->config->get(self::SENDING_METHOD_ID.'_last_credits_check', 0); $lastDetails = $this->config->get(self::SENDING_METHOD_ID.'_credits_details', []); if (!$forceReload && !empty($lastDetails) && $lastCheck > $time - self::CREDITS_RELOAD_DELAY) { return json_decode($lastDetails, true); } $response = $this->callApiSendingMethod('get_credits'); $this->config->saveConfig([ self::SENDING_METHOD_ID.'_credits_details' => json_encode($response), self::SENDING_METHOD_ID.'_last_credits_check' => $time, ]); if (!empty($response['domains'])) { $this->updateDomainRates($response['domains']); } return $response; } private function checkSuccessCode($responseCode): bool { // we can add more success response codes if needed but for now, that's all the success response codes the API can send return in_array($responseCode, [200, 201]); } private function translate($response) { if (!isset($response['code']) && empty($response['message'])) { return ''; } $correspondence = ''; $messageData = []; if (isset($response['code']) && !empty(self::TRANSLATIONS[$response['code']])) { $correspondence = self::TRANSLATIONS[$response['code']]; } if (!empty($response['messageData'])) { $messageData = $response['messageData']; } if (empty($correspondence)) { if (empty($messageData)) { return $response['message']; } return acym_translationVsprintf($response['message'], $messageData, false); } if (empty($messageData)) { $finalMessage = acym_translation($correspondence); } else { $finalMessage = acym_translationVsprintf($correspondence, $messageData); } if (in_array($response['code'], $this->errorCodes)) { $this->errors[] = $finalMessage; $this->errorCallback(); } return $finalMessage; } /** * Emails not needing an unsubscribe link: * - Sending a test from config, from campaign tab or from editor * - Forwarding a bounce message * - Sending a spam-test * - Overriding website emails * - Sending notification messages * - Unsubscribe emails * - Sending the walkthrough email * - Forwarding an email to a specific address from mailbox actions */ private function isUnsubscribeLinkMissing($mailerHelper): bool { if ($mailerHelper->isTest || $mailerHelper->isForward || $mailerHelper->isSpamTest || empty($mailerHelper->mail)) { return false; } if (in_array($mailerHelper->mail->type, MailClass::TYPES_TRANSACTIONAL) || $mailerHelper->mail->type === MailClass::TYPE_AUTOMATION) { return false; } return !$this->isContainingUnsubscribeLink($mailerHelper->mail->body); } private function isContainingUnsubscribeLink(string $text): bool { return strpos($text, '{unsubscribe}') !== false || strpos($text, '{unsubscribeall}') !== false || strpos($text, 'task=unsubscribe') !== false || strpos($text, 'frontusers/unsubscribe') !== false || strpos($text, '{direct_unsubscribe') !== false; } private function checkDomainsDNS($domains): array { $remainingDomains = []; foreach ($domains as $oneDomain) { // No DNS entries yet, we have to check the domain to get DNS entries if (empty($oneDomain['CnameRecords'])) { $remainingDomains[] = $oneDomain; continue; } // Check if all DNS entries are valid before calling the API foreach ($oneDomain['CnameRecords'] as $oneRecord) { if (!$this->checkCnameEntry($oneRecord['name'], $oneRecord['value'])) { $domainPosition = strpos($oneRecord['name'], '_domainkey') + 11; $domainName = substr($oneRecord['name'], $domainPosition); acym_sendAjaxResponse(acym_translation('ACYM_A_CNAME_MISSING'), ['domain' => $domainName], false); } } $remainingDomains[] = $oneDomain; } return $remainingDomains; } private function checkCnameEntry(string $entryName, string $entryValue): bool { $domainPosition = strpos($entryName, '_domainkey') + 11; $domainName = substr($entryName, $domainPosition); $dnsEntries = @dns_get_record($entryName, DNS_CNAME); if (empty($dnsEntries)) { $entryName = substr($entryName, 0, $domainPosition); $doubleDomainEntry = $entryName.'.'.$domainName; $dnsEntries = @dns_get_record($doubleDomainEntry, DNS_CNAME); if (!empty($dnsEntries)) { $this->cnameErrors[] = acym_translationSprintf('ACYM_ACYMAILER_MALFORMED_CNAME', $doubleDomainEntry); $this->cnameErrors[] = acym_translationSprintf('ACYM_ACYMAILER_CNAME_TRY_WITHOUT_DOMAIN', $entryName, $entryValue); } else { $this->cnameErrors[] = acym_translation('ACYM_WALK_ACYMAILER_STATUS_WAIT'); } return false; } if (!is_array($dnsEntries)) { $dnsEntries = [$dnsEntries]; } foreach ($dnsEntries as $oneEntry) { if ($oneEntry['type'] !== 'CNAME' || $oneEntry['host'] !== $entryName) { continue; } if (rtrim($oneEntry['target'], '.') === rtrim($entryValue, '.')) { return true; } $this->cnameErrors[] = acym_translationSprintf('ACYM_DOMAIN_CHECK_INCORRECT_VALUE', $entryName, $domainName); return false; } $this->cnameErrors[] = acym_translation('ACYM_DNS_NOT_CNAME'); return false; } private function displayCnameStatus(array $cnameRecord): void { if ($cnameRecord['isValid']) { echo ''; } else { acym_tooltip( [ 'hoveredText' => '', 'textShownInTooltip' => implode('
', $cnameRecord['tooltipMessages']), ] ); } } }