| Server IP : 103.48.194.25 / Your IP : 216.73.217.33 Web Server : Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.33 System : Linux VMHostDefault 3.10.0-1160.42.2.el7.x86_64 #1 SMP Tue Sep 7 14:49:57 UTC 2021 x86_64 User : sieuthimay ( 1016) PHP Version : 8.2.20 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /var/www/sieuthimayasia.com/public_html/wp-content/plugins/wp-smtp/src/Mail/ |
Upload File : |
<?php
namespace SolidWP\Mail;
use LogicException;
use PHPMailer\PHPMailer\Exception;
use SolidWP\Mail\Connectors\ConnectionPool;
use SolidWP\Mail\Connectors\ConnectorSMTP;
use SolidWP\Mail\Contracts\Api_Connector;
use WP_Error;
/**
* Extension of PHPMailer that adds API mailing capabilities.
*
* This class extends the base PHPMailer functionality to support sending emails
* through API endpoints in addition to traditional SMTP methods. It provides
* methods for configuring API connections and formatting email data for API
* transmission.
*
* To support compatibility with WordPress < 6.8, this class should not be used
* in static or regular context without requiring `inc/wp-phpmailer-compatibility.php` helper.
*
* @since 2.1.3
* @package SolidWP\Mail\Pro
* @extends \WP_PHPMailer
*/
class SolidMailer extends \WP_PHPMailer {
/**
* Whether to throw exceptions for errors.
*
* @var bool
*/
protected $exceptions = true;
/**
* Pool of connections available for the mail sending.
*
* @var ConnectionPool | null
*/
protected ?ConnectionPool $connection_pool = null;
/**
* @var array The initial settings from wp_mail().
*/
private array $initial_settings = [
'From' => '',
'FromName' => '',
'ReplyTo' => [],
'ReplyToQueue' => [],
];
public function __construct() {
parent::__construct( true );
}
/**
* Init connection pool for sending emails.
*
* @param ConnectionPool $connection_pool Pool of connections available for the mail sending.
*
* @return void
*/
public function init_pool( ConnectionPool $connection_pool ): void {
$this->connection_pool = $connection_pool;
// This setting will be modified during the sending process. So we need to capture initial values.
$this->initial_settings = [
'From' => $this->From,
'FromName' => $this->FromName,
'ReplyTo' => $this->ReplyTo,
'ReplyToQueue' => $this->ReplyToQueue,
];
}
/**
* Current connection.
*
* @return ConnectorSMTP|null
*/
public function get_connection(): ?ConnectorSMTP {
if ( ! $this->connection_pool instanceof ConnectionPool ) {
return null;
}
$current = $this->connection_pool->current();
return $current instanceof ConnectorSMTP ? $current : null;
}
/**
* Create a message and send it.
* Uses the sending method specified by $Mailer.
*
* @return bool false on error - See the ErrorInfo property for details of the error
* @throws Exception
*/
public function send(): bool {
if ( ! $this->connection_pool instanceof ConnectionPool || $this->connection_pool->count() === 0 ) {
// Pool is empty, send email without Kadence Mail
return parent::send();
}
try {
$connection = $this->get_connection();
if ( ! $connection instanceof ConnectorSMTP ) {
throw new LogicException( 'Connection is not defined' );
}
$this->configure_for_connection( $connection );
if ( ! $this->preSend() ) {
return false;
}
return $this->postSend();
} catch ( Exception $exc ) {
/**
* As we have an `exceptions` property enabled, `preSend` and `postSend` methods throw an exception on error.
* So we can handle a negative path only once in the catch construction.
*/
if ( $this->connection_pool->hasNext() ) {
$this->connection_pool->next();
return $this->send();
}
$this->mailHeader = '';
$this->setError( $exc->getMessage() );
throw $exc;
}
}
private function configure_for_connection( ConnectorSMTP $connection ): void {
$this->Mailer = $connection->isAPI() ? 'api' : 'smtp';
$this->Host = $connection->get_host();
$this->SMTPSecure = $connection->get_secure();
$this->Port = $connection->get_port();
$this->SMTPAuth = $connection->is_authentication();
$this->Username = $this->SMTPAuth ? $connection->get_username() : '';
$this->Password = $this->SMTPAuth ? $connection->get_password() : '';
$this->From = $connection->get_from_email();
$this->Sender = $this->From;
$this->maybe_update_default_settings( $connection );
}
private function maybe_update_default_settings( ConnectorSMTP $connection ): void {
$default_settings = $this->wordpress_default_settings();
if ( $this->initial_settings['FromName'] === $default_settings['FromName'] ) {
// Change the default `WordPress` to connection from name.
$this->FromName = $connection->get_from_name();
}
if ( count( $this->initial_settings['ReplyTo'] ) > 0 || count( $this->initial_settings['ReplyToQueue'] ) > 0 ) {
// The ReplyTo was provided by wp_mail already. We don't need to change it.
return;
}
if ( $this->initial_settings['From'] === $default_settings['From'] ) {
// Adding Reply-To with the default WordPress email does not make sense.
return;
}
if ( $this->initial_settings['From'] === $this->From ) {
// We don't want to add Reply-To with the same email as From.
return;
}
$this->clearReplyTos();
$this->AddReplyTo( $this->initial_settings['From'], $this->FromName );
}
/**
* Returns the default settings for WordPress.
*
* @return string[]
* @phpstan-return array{FromName: string, From: string}
* @see wp_mail()
*/
private function wordpress_default_settings(): array {
$sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
$from_email = 'wordpress@';
if ( null !== $sitename ) {
if ( str_starts_with( $sitename, 'www.' ) ) {
$sitename = substr( $sitename, 4 );
}
$from_email .= $sitename;
}
return [
'FromName' => 'WordPress',
'From' => $from_email,
];
}
/**
* Sends an email using the API connector.
*
* @param string $header The email headers
* @param string $body The email body
*
* @return bool | WP_Error The result from the API send operation
* @throws \Exception
* @since 2.1.3
*/
public function apiSend( $header, $body ) {
$connector = $this->get_connection();
if ( ! $connector instanceof Api_Connector ) {
throw new Exception( 'API connector is not defined', self::STOP_CRITICAL );
}
$email_data = $this->getEmailData( $header, $body );
if ( is_wp_error( $email_data ) ) {
throw new Exception( $email_data->get_error_message(), self::STOP_CRITICAL );
}
$result = $connector->send_use_api( $email_data );
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message(), self::STOP_CRITICAL );
}
return $result;
}
/**
* Gets formatted email data including recipients, headers, and body content.
* This method extracts and organizes all relevant email sending information.
*
* @param string $header The email headers.
* @param string $body The email body content.
*
* @return WP_Error | array{
* to: array<array{0: string, 1: string}>,
* cc: array<array{0: string, 1: string}>,
* bcc: array<array{0: string, 1: string}>,
* from: string,
* sender: string,
* subject: string,
* headers: string,
* body: string,
* custom_headers: array<string, string>,
* reply_to: array<array{0: string, 1: string}>,
* all_recipients: array<string>
* }
* @since 2.1.3
*/
protected function getEmailData( string $header, string $body ): array {
// Format header with proper line endings
$formatted_header = static::stripTrailingWSP( $header ) . static::$LE . static::$LE;
// Determine sender
$sender = '' === $this->Sender ? $this->From : $this->Sender;
// Get all custom headers
$custom_headers = [];
foreach ( $this->CustomHeader as $header ) {
$custom_headers[ $header[0] ] = $header[1];
}
// Collect all recipients
$all_recipients = array_merge(
array_column( $this->to, 0 ),
array_column( $this->cc, 0 ),
array_column( $this->bcc, 0 )
);
$email_data = [
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found
// Recipients with format: [[email, name], [email, name], ...]
'to' => $this->to,
'cc' => $this->cc,
'bcc' => $this->bcc,
// Sender information
'from' => $this->From,
'sender' => $sender,
// Content
'subject' => $this->Subject,
'headers' => $formatted_header,
'body' => $body,
'raw_body' => $this->encodeString( $this->Body, $this->Encoding ),
// Additional data
'custom_headers' => $custom_headers,
'reply_to' => $this->ReplyTo,
'all_recipients' => array_unique( $all_recipients ),
// Optional metadata if available
'message_type' => $this->ContentType ?? 'text/plain',
'charset' => $this->CharSet ?? 'utf-8',
'encoding' => $this->Encoding ?? '8bit',
// Attachments
'attachments' => $this->attachment,
];
// Validate required data
if ( empty( $email_data['from'] ) ) {
return new WP_Error(
'email_missing_from',
'From address is required',
[ 'status' => self::STOP_CRITICAL ]
);
}
if ( empty( $email_data['all_recipients'] ) ) {
return new WP_Error(
'email_missing_recipients',
'At least one recipient is required',
[ 'status' => self::STOP_CRITICAL ]
);
}
return $email_data;
}
}