PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /home/trave494/tiktechtok.org/wp-content/plugins/mailpoet/lib/Services/Bridge/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : /home/trave494/tiktechtok.org/wp-content/plugins/mailpoet/lib/Services/Bridge/API.php |
<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing namespace MailPoet\Services\Bridge; if (!defined('ABSPATH')) exit; use MailPoet\Logging\LoggerFactory; use MailPoet\WP\Functions as WPFunctions; use WP_Error; class API { const RESPONSE_STATUS_OK = 'ok'; const RESPONSE_STATUS_ERROR = 'error'; const SENDING_STATUS_CONNECTION_ERROR = 'connection_error'; const SENDING_STATUS_SEND_ERROR = 'send_error'; const REQUEST_TIMEOUT = 10; // seconds const RESPONSE_CODE_KEY_INVALID = 401; const RESPONSE_CODE_STATS_SAVED = 204; const RESPONSE_CODE_CREATED = 201; const RESPONSE_CODE_INTERNAL_SERVER_ERROR = 500; const RESPONSE_CODE_BAD_GATEWAY = 502; const RESPONSE_CODE_TEMPORARY_UNAVAILABLE = 503; const RESPONSE_CODE_GATEWAY_TIMEOUT = 504; const RESPONSE_CODE_NOT_ARRAY = 422; const RESPONSE_CODE_PAYLOAD_TOO_BIG = 413; const RESPONSE_CODE_PAYLOAD_ERROR = 400; const RESPONSE_CODE_CAN_NOT_SEND = 403; // Bridge messages from https://github.com/mailpoet/services-bridge/blob/master/api/messages.rb public const ERROR_MESSAGE_BANNED = 'Key is valid, but the action is forbidden'; public const ERROR_MESSAGE_INVALID_FROM = 'The email address is not authorized'; public const ERROR_MESSAGE_PENDING_APPROVAL = 'Key is valid, but not approved yet; you can send only to authorized email addresses at the moment'; public const ERROR_MESSAGE_DMRAC = "Email violates Sender Domain's DMARC policy. Please set up sender authentication."; public const ERROR_MESSAGE_BULK_EMAIL_FORBIDDEN = 'Bulk email are forbidden for the sender address'; // Bridge message from https://github.com/mailpoet/services-bridge/blob/master/extensions/authentication/basic_strategy.rb public const ERROR_MESSAGE_UNAUTHORIZED = 'No valid API key provided'; public const ERROR_MESSAGE_INSUFFICIENT_PRIVILEGES = 'Insufficient privileges'; public const ERROR_MESSAGE_EMAIL_VOLUME_LIMIT_REACHED = 'Email volume limit reached'; public const ERROR_MESSAGE_SUBSCRIBERS_LIMIT_REACHED = 'Subscribers limit reached'; // Proxy request `authorized_email_address` from shop https://github.com/mailpoet/shop/blob/master/routes/hooks/sending/v1/index.js#L65 public const ERROR_MESSAGE_AUTHORIZED_EMAIL_NO_FREE = 'You cannot use a free email address. Please use an address from your website’s domain, for example.'; public const ERROR_MESSAGE_AUTHORIZED_EMAIL_INVALID = 'Invalid email.'; public const ERROR_MESSAGE_AUTHORIZED_EMAIL_ALREADY_ADDED = 'This email was already added to the list.'; // Proxy request `sender_domain_verify` from shop https://github.com/mailpoet/shop/blob/master/routes/hooks/sending/v1/index.js#L137 public const ERROR_MESSAGE_AUTHORIZED_DOMAIN_VERIFY_NOT_FOUND = 'Domain not found'; public const ERROR_MESSAGE_AUTHORIZED_DOMAIN_VERIFY_FAILED = 'Some DNS records were not set up correctly. Please check the records again. You may need to wait up to 24 hours for DNS changes to propagate.'; // Proxy request `sender_domain` from shop https://github.com/mailpoet/shop/blob/master/routes/hooks/sending/v1/index.js#L65 public const ERROR_MESSAGE_SENDER_DOMAIN_INVALID = 'Invalid domain. Please enter a valid domain name.'; public const ERROR_MESSAGE_SENDER_DOMAIN_ALREADY_ADDED = 'This domain was already added to the list.'; public const KEY_CHECK_TYPE_PREMIUM = 'premium'; public const KEY_CHECK_TYPE_MSS = 'mss'; private $apiKey; private $wp; /** @var LoggerFactory */ private $loggerFactory; /** @var mixed|null It is an instance of \CurlHandle in PHP8 and aboove but a resource in PHP7 */ private $curlHandle = null; public $urlMe = 'https://bridge.mailpoet.com/api/v0/me'; public $urlPremium = 'https://bridge.mailpoet.com/api/v0/premium'; public $urlMessages = 'https://bridge.mailpoet.com/api/v0/messages'; public $urlBounces = 'https://bridge.mailpoet.com/api/v0/bounces/search'; public $urlStats = 'https://bridge.mailpoet.com/api/v0/stats'; public $urlAuthorizedEmailAddresses = 'https://bridge.mailpoet.com/api/v1/authorized_email_address'; public $urlAuthorizedSenderDomains = 'https://bridge.mailpoet.com/api/v1/sender_domain'; public $urlAuthorizedSenderDomainVerification = 'https://bridge.mailpoet.com/api/v1/sender_domain_verify'; public function __construct( $apiKey, $wp = null ) { $this->setKey($apiKey); if (is_null($wp)) { $this->wp = new WPFunctions(); } else { $this->wp = $wp; } $this->loggerFactory = LoggerFactory::getInstance(); } public function checkMSSKey() { return $this->checkKey(self::KEY_CHECK_TYPE_MSS); } public function checkPremiumKey() { return $this->checkKey(self::KEY_CHECK_TYPE_PREMIUM); } private function checkKey(string $keyCheckType): array { if ($keyCheckType === self::KEY_CHECK_TYPE_PREMIUM) { $apiUrl = $this->urlPremium; } else { $apiUrl = $this->urlMe; } $result = $this->request( $apiUrl, ['site' => strtolower(WPFunctions::get()->homeUrl())] ); $errorMessage = null; $code = $this->wp->wpRemoteRetrieveResponseCode($result); switch ($code) { case 200: $body = $this->wp->wpRemoteRetrieveBody($result); if ($body) { $body = json_decode($body, true); } break; default: $this->logKeyCheckError((int)$code, $keyCheckType); $body = null; $errorMessage = $this->wp->wpRemoteRetrieveBody($result); break; } return ['code' => $code, 'data' => $body, 'error_message' => $errorMessage]; } /** * This method logs data from 'requests-curl.after_request' hook. * The hook is mostly called with two parameters but sometimes only with one. */ public function logCurlInformation($headers, $info = null) { $this->loggerFactory->getLogger(LoggerFactory::TOPIC_MSS)->info( 'requests-curl.after_request', ['headers' => $headers, 'curl_info' => $info] ); } public function setCurlHandle($handle) { $this->curlHandle = $handle; } public function sendMessages($messageBody) { $this->curlHandle = null; add_action('requests-curl.before_request', [$this, 'setCurlHandle'], 10, 1); add_action('requests-curl.after_request', [$this, 'logCurlInformation'], 10, 2); $result = $this->request( $this->urlMessages, $messageBody ); remove_action('requests-curl.after_request', [$this, 'logCurlInformation']); remove_action('requests-curl.before_request', [$this, 'setCurlHandle']); if (is_wp_error($result)) { $this->logCurlError($result); return [ 'status' => self::SENDING_STATUS_CONNECTION_ERROR, 'message' => $result->get_error_message(), ]; } $responseCode = $this->wp->wpRemoteRetrieveResponseCode($result); if ($responseCode !== 201) { $response = ($this->wp->wpRemoteRetrieveBody($result)) ? $this->wp->wpRemoteRetrieveBody($result) : $this->wp->wpRemoteRetrieveResponseMessage($result); return $this->createErrorResponse((int)$responseCode, $response, self::SENDING_STATUS_SEND_ERROR); } return ['status' => self::RESPONSE_STATUS_OK]; } public function checkBounces(array $emails) { $result = $this->request( $this->urlBounces, $emails ); if ($this->wp->wpRemoteRetrieveResponseCode($result) === 200) { return json_decode($this->wp->wpRemoteRetrieveBody($result), true); } return false; } public function updateSubscriberCount($count): bool { $result = $this->request( $this->urlStats, ['subscriber_count' => (int)$count], 'PUT' ); $code = $this->wp->wpRemoteRetrieveResponseCode($result); $isSuccess = $code === self::RESPONSE_CODE_STATS_SAVED; if (!$isSuccess) { $logData = [ 'code' => $code, 'error' => is_wp_error($result) ? $result->get_error_message() : null, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_BRIDGE)->error('Stats API call failed.', $logData); } return $isSuccess; } public function getAuthorizedEmailAddresses(): ?array { $result = $this->request( $this->urlAuthorizedEmailAddresses, null, 'GET' ); if ($this->wp->wpRemoteRetrieveResponseCode($result) !== 200) { return null; } $data = json_decode($this->wp->wpRemoteRetrieveBody($result), true); return is_array($data) ? $data : null; } /** * Create Authorized Email Address * * @param string $emailAddress * @return array{status: string, code?: int, error?: string, message?: string} */ public function createAuthorizedEmailAddress(string $emailAddress): array { $body = ['email' => $emailAddress]; $result = $this->request( $this->urlAuthorizedEmailAddresses, $body ); $responseCode = $this->wp->wpRemoteRetrieveResponseCode($result); if ($responseCode !== self::RESPONSE_CODE_CREATED) { $errorBody = $this->wp->wpRemoteRetrieveBody($result); $logData = [ 'code' => $responseCode, 'error' => is_wp_error($result) ? $result->get_error_message() : $errorBody, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_BRIDGE)->error('CreateAuthorizedEmailAddress API call failed.', $logData); $errorResponseData = json_decode($errorBody, true); // translators: %d is the error code. $fallbackError = sprintf(__('An error has happened while performing a request, the server has responded with response code %d', 'mailpoet'), $responseCode); $error = is_array($errorResponseData) && isset($errorResponseData['error']) ? $errorResponseData['error'] : $fallbackError; return $this->createErrorResponse((int)$responseCode, $error); } return ['status' => self::RESPONSE_STATUS_OK]; } /** * Get a list of sender domains * Fetched from API * @see https://github.com/mailpoet/services-bridge#sender-domains */ public function getAuthorizedSenderDomains(): ?array { $result = $this->request( $this->urlAuthorizedSenderDomains, null, 'GET' ); if ($this->wp->wpRemoteRetrieveResponseCode($result) !== 200) { return null; } $rawData = $this->wp->wpRemoteRetrieveBody($result); $data = json_decode($rawData, true); if (!is_array($data)) { $this->logInvalidDataFormat('getAuthorizedSenderDomains', $rawData); return null; } return $data; } /** * Create Sender domain record * Done via API * Returns same response se sender_domain_verify @see https://github.com/mailpoet/services-bridge#verify-a-sender-domain */ public function createAuthorizedSenderDomain(string $domain): array { $body = ['domain' => strtolower($domain)]; $result = $this->request( $this->urlAuthorizedSenderDomains, $body ); $responseCode = $this->wp->wpRemoteRetrieveResponseCode($result); $rawResponseBody = $this->wp->wpRemoteRetrieveBody($result); $responseBody = json_decode($rawResponseBody, true); if ($responseCode !== self::RESPONSE_CODE_CREATED) { $logData = [ 'code' => $responseCode, 'error' => is_wp_error($result) ? $result->get_error_message() : $rawResponseBody, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_BRIDGE)->error('createAuthorizedSenderDomain API call failed.', $logData); // translators: %d will be replaced by an error code $fallbackError = sprintf(__('An error has happened while performing a request, the server has responded with response code %d', 'mailpoet'), $responseCode); $error = is_array($responseBody) && isset($responseBody['error']) ? $responseBody['error'] : $fallbackError; return $this->createErrorResponse((int)$responseCode, $error); } if (!is_array($responseBody)) { $this->logInvalidDataFormat('createAuthorizedSenderDomain', $rawResponseBody); return []; } $responseBody['status'] = self::RESPONSE_STATUS_OK; return $responseBody; } /** * Verify Sender Domain records * returns an Array of DNS response or an array of error * @see https://github.com/mailpoet/services-bridge#verify-a-sender-domain */ public function verifyAuthorizedSenderDomain(string $domain): array { $url = $this->urlAuthorizedSenderDomainVerification . '/' . urlencode(strtolower($domain)); $result = $this->request( $url, null ); $responseCode = $this->wp->wpRemoteRetrieveResponseCode($result); $rawResponseBody = $this->wp->wpRemoteRetrieveBody($result); $responseBody = json_decode($rawResponseBody, true); if ($responseCode !== 200) { if ($responseCode === 400) { // we need to return the body as it is, but for consistency we add status and translated error message $response = is_array($responseBody) ? $responseBody : []; $response['status'] = self::RESPONSE_STATUS_ERROR; $response['message'] = $this->getTranslatedErrorMessage($response['error']); return $response; } $logData = [ 'code' => $responseCode, 'error' => is_wp_error($result) ? $result->get_error_message() : $rawResponseBody, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_BRIDGE)->error('verifyAuthorizedSenderDomain API call failed.', $logData); // translators: %d will be replaced by an error code $fallbackError = sprintf(__('An error has happened while performing a request, the server has responded with response code %d', 'mailpoet'), $responseCode); $error = is_array($responseBody) && isset($responseBody['error']) ? $responseBody['error'] : $fallbackError; return $this->createErrorResponse((int)$responseCode, $error); } if (!is_array($responseBody)) { $this->logInvalidDataFormat('verifyAuthorizedSenderDomain', $rawResponseBody); return []; } $responseBody['status'] = self::RESPONSE_STATUS_OK; return $responseBody; } public function setKey($apiKey) { $this->apiKey = $apiKey; } public function getKey() { return $this->apiKey; } public function getTranslatedErrorMessage(string $errorMessage): string { switch ($errorMessage) { case self::ERROR_MESSAGE_BANNED: return __('Key is valid, but the action is forbidden.', 'mailpoet'); case self::ERROR_MESSAGE_INVALID_FROM: return __('The email address is not authorized.', 'mailpoet'); case self::ERROR_MESSAGE_PENDING_APPROVAL: return __('Key is valid, but not approved yet; you can send only to authorized email addresses at the moment.', 'mailpoet'); case self::ERROR_MESSAGE_DMRAC: return __("Email violates Sender Domain's DMARC policy. Please set up sender authentication.", 'mailpoet'); case self::ERROR_MESSAGE_BULK_EMAIL_FORBIDDEN: return __('Email violates Sender Domain requirements. Please authenticate the sender domain.', 'mailpoet'); case self::ERROR_MESSAGE_UNAUTHORIZED: return __('No valid API key provided.', 'mailpoet'); case self::ERROR_MESSAGE_INSUFFICIENT_PRIVILEGES: return __('Insufficient privileges.', 'mailpoet'); case self::ERROR_MESSAGE_EMAIL_VOLUME_LIMIT_REACHED: return __('Email volume limit reached.', 'mailpoet'); case self::ERROR_MESSAGE_SUBSCRIBERS_LIMIT_REACHED: return __('Subscribers limit reached.', 'mailpoet'); case self::ERROR_MESSAGE_AUTHORIZED_EMAIL_NO_FREE: return __('You cannot use a free email address. Please use an address from your website’s domain, for example.', 'mailpoet'); case self::ERROR_MESSAGE_AUTHORIZED_EMAIL_INVALID: return __('Invalid email.', 'mailpoet'); case self::ERROR_MESSAGE_AUTHORIZED_EMAIL_ALREADY_ADDED: return __('This email was already added to the list.', 'mailpoet'); case self::ERROR_MESSAGE_AUTHORIZED_DOMAIN_VERIFY_NOT_FOUND: return __('Domain not found.', 'mailpoet'); case self::ERROR_MESSAGE_AUTHORIZED_DOMAIN_VERIFY_FAILED: return __('Some DNS records were not set up correctly. Please check the records again. You may need to wait up to 24 hours for DNS changes to propagate.', 'mailpoet'); case self::ERROR_MESSAGE_SENDER_DOMAIN_INVALID: return __('Invalid domain. Please enter a valid domain name.', 'mailpoet'); case self::ERROR_MESSAGE_SENDER_DOMAIN_ALREADY_ADDED: return __('This domain was already added to the list.', 'mailpoet'); // when we don't match translation we return the origin default: return $errorMessage; } } private function auth() { return 'Basic ' . base64_encode('api:' . $this->apiKey); } private function request($url, $body, $method = 'POST') { $params = [ 'timeout' => $this->wp->applyFilters('mailpoet_bridge_api_request_timeout', self::REQUEST_TIMEOUT), 'httpversion' => '1.0', 'method' => $method, 'headers' => [ 'Content-Type' => 'application/json', 'Authorization' => $this->auth(), ], 'body' => $body !== null ? json_encode($body) : null, ]; return $this->wp->wpRemotePost($url, $params); } private function logCurlError(WP_Error $error) { $logData = [ 'curl_errno' => $this->curlHandle ? curl_errno($this->curlHandle) : 'n/a', 'curl_error' => $this->curlHandle ? curl_error($this->curlHandle) : $error->get_error_message(), 'curl_info' => $this->curlHandle ? curl_getinfo($this->curlHandle) : 'n/a', ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_MSS)->error('requests-curl.failed', $logData); } private function logKeyCheckError(int $code, string $keyType): void { $logData = [ 'http_code' => $code, 'home_url' => $this->wp->homeUrl(), 'key_type' => $keyType, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_MSS)->error('key-validation.failed', $logData); } private function logInvalidDataFormat(string $method, ?string $response = null): void { $logData = [ 'code' => json_last_error(), 'response' => $response, ]; $this->loggerFactory->getLogger(LoggerFactory::TOPIC_BRIDGE)->error($method . ' API response was not in expected format.', $logData); } /** * @return array{status: string, code: int, error: string, message: string} */ private function createErrorResponse(int $responseCode, string $error, string $errorStatus = self::RESPONSE_STATUS_ERROR): array { return [ 'status' => $errorStatus, 'code' => $responseCode, 'error' => $error, 'message' => $this->getTranslatedErrorMessage($error), ]; } }