<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
namespace MailPoet\API\JSON\v1;
if (!defined('ABSPATH')) exit;
use MailPoet\Analytics\Analytics as AnalyticsHelper;
use MailPoet\API\JSON\Endpoint as APIEndpoint;
use MailPoet\API\JSON\Error as APIError;
use MailPoet\API\JSON\Response;
use MailPoet\Config\AccessControl;
use MailPoet\Config\Installer;
use MailPoet\Config\ServicesChecker;
use MailPoet\Cron\Workers\KeyCheck\PremiumKeyCheck;
use MailPoet\Cron\Workers\KeyCheck\SendingServiceKeyCheck;
use MailPoet\Mailer\MailerLog;
use MailPoet\Services\AuthorizedEmailsController;
use MailPoet\Services\AuthorizedSenderDomainController;
use MailPoet\Services\Bridge;
use MailPoet\Services\CongratulatoryMssEmailController;
use MailPoet\Services\SubscribersCountReporter;
use MailPoet\Settings\SettingsController;
use MailPoet\Util\Helpers;
use MailPoet\WP\DateTime;
use MailPoet\WP\Functions as WPFunctions;
class Services extends APIEndpoint {
/** @var Bridge */
private $bridge;
/** @var SettingsController */
private $settings;
/** @var AnalyticsHelper */
private $analytics;
/** @var DateTime */
public $dateTime;
/** @var SendingServiceKeyCheck */
private $mssWorker;
/** @var PremiumKeyCheck */
private $premiumWorker;
/** @var ServicesChecker */
private $servicesChecker;
/** @var CongratulatoryMssEmailController */
private $congratulatoryMssEmailController;
/** @var WPFunctions */
private $wp;
/** @var AuthorizedSenderDomainController */
private $senderDomainController;
/** @var AuthorizedEmailsController */
private $authorizedEmailsController;
/** @var SubscribersCountReporter */
private $subscribersCountReporter;
public $permissions = [
'global' => AccessControl::PERMISSION_MANAGE_SETTINGS,
'methods' => ['pingBridge' => AccessControl::PERMISSION_ACCESS_PLUGIN_ADMIN],
];
public function __construct(
Bridge $bridge,
SettingsController $settings,
AnalyticsHelper $analytics,
SendingServiceKeyCheck $mssWorker,
PremiumKeyCheck $premiumWorker,
ServicesChecker $servicesChecker,
SubscribersCountReporter $subscribersCountReporter,
CongratulatoryMssEmailController $congratulatoryMssEmailController,
WPFunctions $wp,
AuthorizedSenderDomainController $senderDomainController,
AuthorizedEmailsController $authorizedEmailsController
) {
$this->bridge = $bridge;
$this->settings = $settings;
$this->analytics = $analytics;
$this->mssWorker = $mssWorker;
$this->premiumWorker = $premiumWorker;
$this->dateTime = new DateTime();
$this->servicesChecker = $servicesChecker;
$this->subscribersCountReporter = $subscribersCountReporter;
$this->congratulatoryMssEmailController = $congratulatoryMssEmailController;
$this->wp = $wp;
$this->senderDomainController = $senderDomainController;
$this->authorizedEmailsController = $authorizedEmailsController;
}
public function checkMSSKey($data = []) {
$key = isset($data['key']) ? trim($data['key']) : null;
if (!$key) {
return $this->badRequest([
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet'),
]);
}
$wasPendingApproval = $this->servicesChecker->isMailPoetAPIKeyPendingApproval();
try {
$result = $this->bridge->checkMSSKey($key);
$this->bridge->storeMSSKeyAndState($key, $result);
} catch (\Exception $e) {
return $this->errorResponse([
$e->getCode() => $e->getMessage(),
]);
}
// pause sending when key is pending approval, resume when not pending anymore
$isPendingApproval = $this->servicesChecker->isMailPoetAPIKeyPendingApproval();
if (!$wasPendingApproval && $isPendingApproval) {
MailerLog::pauseSending(MailerLog::getMailerLog());
} elseif ($wasPendingApproval && !$isPendingApproval) {
MailerLog::resumeSending();
}
$state = !empty($result['state']) ? $result['state'] : null;
$successMessage = null;
if ($state == Bridge::KEY_VALID) {
$successMessage = __('Your MailPoet Sending Service key has been successfully validated', 'mailpoet');
} else if ($state == Bridge::KEY_VALID_UNDERPRIVILEGED) {
$successMessage = __('Your Premium key has been successfully validated, but is not valid for MailPoet Sending Service', 'mailpoet');
} elseif ($state == Bridge::KEY_EXPIRING) {
$successMessage = sprintf(
// translators: %s is the expiration date.
__('Your MailPoet Sending Service key expires on %s!', 'mailpoet'),
$this->dateTime->formatDate(strtotime($result['data']['expire_at']))
);
}
if (!empty($result['data']['public_id'])) {
$this->analytics->setPublicId($result['data']['public_id']);
}
if ($successMessage) {
return $this->successResponse(['message' => $successMessage, 'state' => $state, 'result' => $result]);
}
switch ($state) {
case Bridge::KEY_INVALID:
$error = __('Your key is not valid for the MailPoet Sending Service', 'mailpoet');
break;
case Bridge::KEY_ALREADY_USED:
$error = __('Your MailPoet Sending Service key is already <a>used on another site</a>', 'mailpoet'); // we will use createInterpolateElement to replace <a> element
break;
default:
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
// translators: %s is the error message.
$errorMessage = __('Error validating MailPoet Sending Service key, please try again later (%s).', 'mailpoet');
// If site runs on localhost
if (1 === preg_match("/^(http|https)\:\/\/(localhost|127\.0\.0\.1)/", $this->wp->siteUrl())) {
$errorMessage .= ' ' . __("Note that it doesn't work on localhost.", 'mailpoet');
}
$error = sprintf(
$errorMessage,
$this->getErrorDescriptionByCode($code)
);
break;
}
return $this->errorResponse([APIError::BAD_REQUEST => $error]);
}
public function checkPremiumKey($data = []) {
$key = isset($data['key']) ? trim($data['key']) : null;
if (!$key) {
return $this->badRequest([
APIError::BAD_REQUEST => __('Please specify a key.', 'mailpoet'),
]);
}
try {
$result = $this->bridge->checkPremiumKey($key);
$this->bridge->storePremiumKeyAndState($key, $result);
} catch (\Exception $e) {
return $this->errorResponse([
$e->getCode() => $e->getMessage(),
]);
}
$state = !empty($result['state']) ? $result['state'] : null;
$successMessage = null;
if ($state == Bridge::KEY_VALID) {
$successMessage = __('Your Premium key has been successfully validated', 'mailpoet');
} else if ($state == Bridge::KEY_VALID_UNDERPRIVILEGED) {
$successMessage = __('Your Premium key has been successfully validated, but is not valid for MailPoet Sending Service', 'mailpoet');
} elseif ($state == Bridge::KEY_EXPIRING) {
$successMessage = sprintf(
// translators: %s is the expiration date.
__('Your Premium key expires on %s', 'mailpoet'),
$this->dateTime->formatDate(strtotime($result['data']['expire_at']))
);
}
if (!empty($result['data']['public_id'])) {
$this->analytics->setPublicId($result['data']['public_id']);
}
if ($successMessage) {
return $this->successResponse(
['message' => $successMessage, 'state' => $state, 'result' => $result],
Installer::getPremiumStatus()
);
}
switch ($state) {
case Bridge::KEY_INVALID:
$error = __('Your key is not valid for MailPoet Premium', 'mailpoet');
break;
case Bridge::KEY_ALREADY_USED:
$error = __('Your Premium key is already <a>used on another site</a>', 'mailpoet'); // we will use createInterpolateElement to replace <a> element
break;
default:
$code = !empty($result['code']) ? $result['code'] : Bridge::CHECK_ERROR_UNKNOWN;
$error = sprintf(
// translators: %s is the error message.
__('Error validating Premium key, please try again later (%s)', 'mailpoet'),
$this->getErrorDescriptionByCode($code)
);
break;
}
return $this->errorResponse(
[APIError::BAD_REQUEST => $error],
['code' => $result['code'] ?? null]
);
}
public function recheckKeys() {
// Report subscribers count before rechecking keys so that shop can lift access restrictions in case
// user deleted subscribers and no longer exceeds the limit.
$key = $this->servicesChecker->getValidAccountKey();
if ($key) {
$this->subscribersCountReporter->report($key);
}
$this->mssWorker->init();
$mssCheck = $this->mssWorker->checkKey();
$this->premiumWorker->init();
$premiumCheck = $this->premiumWorker->checkKey();
// continue sending when it is paused and states are valid
$mailerLog = MailerLog::getMailerLog();
if (
(isset($mailerLog['status']) && $mailerLog['status'] === MailerLog::STATUS_PAUSED)
&& (isset($mssCheck['state']) && $mssCheck['state'] === Bridge::KEY_VALID)
&& (isset($premiumCheck['state']) && $premiumCheck['state'] === Bridge::PREMIUM_KEY_VALID)
) {
MailerLog::resumeSending();
}
return $this->successResponse();
}
public function sendCongratulatoryMssEmail() {
if (!Bridge::isMPSendingServiceEnabled()) {
return $this->createBadRequest(__('MailPoet Sending Service is not active.', 'mailpoet'));
}
$fromEmail = $this->settings->get('sender.address');
if (!$fromEmail) {
return $this->createBadRequest(__('Sender email address is not set.', 'mailpoet'));
}
$verifiedDomains = $this->senderDomainController->getVerifiedSenderDomainsIgnoringCache();
$emailDomain = Helpers::extractEmailDomain($fromEmail);
if (!$this->isItemInArray($emailDomain, $verifiedDomains)) {
$authorizedEmails = $this->authorizedEmailsController->getAuthorizedEmailAddresses();
if (!$authorizedEmails) {
return $this->createBadRequest(__('No FROM email addresses are authorized.', 'mailpoet'));
}
if (!$this->isItemInArray($fromEmail, $authorizedEmails)) {
// translators: %s is the email address, which is not authorized.
return $this->createBadRequest(sprintf(__("Sender email address '%s' is not authorized.", 'mailpoet'), $fromEmail));
}
}
try {
// congratulatory email is sent to the current FROM address (authorized at this point)
$this->congratulatoryMssEmailController->sendCongratulatoryEmail($fromEmail);
} catch (\Throwable $e) {
return $this->errorResponse([
APIError::UNKNOWN => __('Sending of congratulatory email failed.', 'mailpoet'),
], [], Response::STATUS_UNKNOWN);
}
return $this->successResponse([
'email_address' => $fromEmail,
]);
}
public function pingBridge() {
try {
$bridgePingResponse = $this->bridge->pingBridge();
} catch (\Exception $e) {
return $this->errorResponse([
APIError::UNKNOWN => $e->getMessage(),
]);
}
if (!$this->bridge->validateBridgePingResponse($bridgePingResponse)) {
$code = $bridgePingResponse ?: Bridge::CHECK_ERROR_UNKNOWN;
return $this->errorResponse([
APIError::UNKNOWN => $this->getErrorDescriptionByCode($code),
]);
}
return $this->successResponse();
}
public function refreshMSSKeyStatus() {
$key = $this->settings->get('mta.mailpoet_api_key');
return $this->checkMSSKey(['key' => $key]);
}
public function refreshPremiumKeyStatus() {
$key = $this->settings->get('premium.premium_key');
return $this->checkPremiumKey(['key' => $key]);
}
private function isItemInArray($item, $array): bool {
return in_array($item, $array, true);
}
private function getErrorDescriptionByCode($code) {
switch ($code) {
case Bridge::CHECK_ERROR_UNAVAILABLE:
$text = __('Service unavailable', 'mailpoet');
break;
case Bridge::CHECK_ERROR_UNKNOWN:
$text = __('Contact your hosting support to check the connection between your host and https://bridge.mailpoet.com', 'mailpoet');
break;
default:
// translators: %s is the code.
$text = sprintf(_x('code: %s', 'Error code (inside parentheses)', 'mailpoet'), $code);
break;
}
return $text;
}
private function createBadRequest(string $message) {
return $this->badRequest([
APIError::BAD_REQUEST => $message,
]);
}
}