File: /var/www/html/TriadGov/wp-content/plugins/wp-mail-smtp-pro/src/Pro/Emails/Logs/Email.php
<?php
namespace WPMailSMTP\Pro\Emails\Logs;
use WPMailSMTP\Helpers\Helpers;
use WPMailSMTP\WP;
use Exception;
use DateTime;
/**
* Class Email represents single email log entry.
*
* @since 1.5.0
*/
class Email {
/**
* This email is not successfully sent.
*
* @since 1.5.0
*/
const STATUS_UNSENT = 0;
/**
* This email is successfully sent.
*
* @since 1.5.0
*/
const STATUS_SENT = 1;
/**
* This email is waiting the API "is sent" verification check.
*
* @since 2.5.0
*/
const STATUS_WAITING = 2;
/**
* This email was delivered (confirmed via API).
*
* @since 2.5.0
*/
const STATUS_DELIVERED = 3;
/**
* @since 1.5.0
*
* @var int
*/
protected $id = 0;
/**
* Message ID (mainly external ID from email provider).
*
* @since 3.3.0
*
* @var string
*/
protected $message_id = '';
/**
* @since 1.5.0
*
* @var string
*/
protected $subject = '';
/**
* @since 1.5.0
*
* @var string JSON string of email header TO/CC/BCC/FROM values.
*/
protected $people = '';
/**
* @since 1.5.0
*
* @var string JSON strong of email header.
*/
protected $headers = '';
/**
* Error recorded when email was sent.
*
* @since 2.5.0
*
* @var string
*/
protected $error_text = '';
/**
* @since 1.5.0
*
* @var string
*/
protected $content_plain = '';
/**
* @since 1.5.0
*
* @var string
*/
protected $content_html = '';
/**
* @since 1.5.0
*
* @var int Check statuses constants to refer to the meaning of the status value.
*/
protected $status = 0;
/**
* @since 1.5.0
*
* @var \DateTime
*/
protected $date_sent;
/**
* @since 1.5.0
*
* @var string
*/
protected $mailer = 'smtp';
/**
* @since 1.5.0
*
* @var 0 Whether this email had attachments or not. 0 is none, number is any.
*/
protected $attachments = 0;
/**
* Name of the plugin/theme (or WP core) that initiated/called the `wp_mail` function.
*
* @since 3.0.0
*
* @var string
*/
protected $initiator_name = '';
/**
* File path and line number of the plugin/theme (or WP core) that initiated/called the `wp_mail` function.
*
* @since 3.0.0
*
* @var string
*/
protected $initiator_file = '';
/**
* Parent ID.
*
* @since 3.7.0
*
* @var int
*/
protected $parent_id = 0;
/**
* Retrieve a particular email when constructing the object.
*
* @since 1.5.0
*
* @param int|object $id_or_row
*/
public function __construct( $id_or_row = null ) {
$this->populate_email( $id_or_row );
}
/**
* Get and prepare the email data.
*
* @since 1.5.0
*
* @param int|object $id_or_row
*/
private function populate_email( $id_or_row ) {
$email = null;
if ( is_numeric( $id_or_row ) && $id_or_row > 0 ) {
// Get by ID.
$collection = new EmailsCollection( [ 'id' => (int) $id_or_row ] );
$emails = $collection->get();
if ( $emails->valid() ) {
$email = $emails->current();
}
} elseif (
is_object( $id_or_row ) &&
isset(
$id_or_row->id,
$id_or_row->subject,
$id_or_row->people,
$id_or_row->headers,
$id_or_row->content_plain,
$id_or_row->content_html,
$id_or_row->status,
$id_or_row->date_sent,
$id_or_row->mailer,
$id_or_row->attachments
)
) {
$email = $id_or_row;
}
if ( $email !== null ) {
foreach ( get_object_vars( $email ) as $key => $value ) {
$this->{$key} = $value;
}
}
}
/**
* Get email by message ID.
*
* @since 3.3.0
*
* @param string $message_id Message ID.
*
* @return Email|null
*/
public static function get_by_message_id( $message_id ) {
// Get by message ID.
$collection = new EmailsCollection(
[
'message_id' => $message_id,
'per_page' => 1,
]
);
$emails = $collection->get();
return $emails->current();
}
/**
* Email ID as per our DB table.
*
* @since 1.5.0
*
* @return int
*/
public function get_id() {
return (int) $this->id;
}
/**
* Message ID (mainly external ID from email provider).
*
* @since 3.3.0
*
* @return string
*/
public function get_message_id() {
return $this->message_id;
}
/**
* Email subject.
*
* @since 1.5.0
*
* @return string
*/
public function get_subject() {
return $this->subject;
}
/**
* All the people that participated in this email: sent to, from or copied.
*
* @since 1.5.0
*
* @param string $type Who exactly to retrieve. Supported values: to, from, cc, bcc. If empty - get everyone.
*
* @return array|string List of people. Empty array if type is incorrect or everyone are requested.
* String for some specific type.
*/
public function get_people( $type = '' ) {
$people = WP::is_json( $this->people ) ? json_decode( $this->people ) : array();
$type = sanitize_key( $type );
// Get all.
if ( empty( $type ) ) {
$data = $people;
} elseif ( isset( $people->{$type} ) ) {
$data = $people->{$type};
} else {
$data = array();
}
return $data;
}
/**
* Email from name.
*
* @since 2.8.0
*
* @return string
*/
public function get_from_name() {
$from_header = $this->get_header( 'From' );
if ( ! empty( $from_header ) ) {
preg_match( '/\s*(.*[^\s])\s*<\s*(.*[^\s])\s*>/', $from_header, $output );
if ( ! empty( $output[1] ) ) {
return trim( $output[1] );
}
}
return '';
}
/**
* All the headers, JSON string.
*
* @since 1.5.0
*
* @return string
*/
public function get_headers() {
return WP::is_json( $this->headers ) ? $this->headers : '[]';
}
/**
* Get a specific header by its name if it exists.
*
* @since 2.5.0
*
* @param string $name The name of the header to get.
*
* @return string
*/
public function get_header( $name ) {
if ( empty( $name ) ) {
return '';
}
$headers = json_decode( $this->get_headers(), true );
foreach ( $headers as $header ) {
preg_match( '/^' . $name . ': (.*)/', $header, $output );
if ( ! empty( $output[1] ) ) {
return $output[1];
}
}
return '';
}
/**
* Error recorded when email was attempted to be sent.
*
* @since 2.5.0
*
* @return string
*/
public function get_error_text() {
return $this->error_text;
}
/**
* Get the content of the email.
*
* @since 1.5.0
*
* @return string Return HTML content by default. Fallback to plain.
*/
public function get_content() {
$html = $this->get_content_html();
if ( empty( $html ) ) {
return $this->get_content_plain();
}
return $html;
}
/**
* Get the plain content of the email.
*
* @since 1.5.0
*
* @return string
*/
public function get_content_plain() {
return $this->content_plain;
}
/**
* Get the HTML content of the email.
*
* @since 1.5.0
*
* @return string
*/
public function get_content_html() {
return $this->content_html;
}
/**
* 1 means sent, 0 means not sent.
*
* @since 1.5.0
*
* @return int
*/
public function get_status() {
return (int) $this->status;
}
/**
* Get human readable status name.
*
* @since 2.8.0
*
* @return string
*/
public function get_status_name() {
$statuses = [
self::STATUS_UNSENT => __( 'Not Sent', 'wp-mail-smtp-pro' ),
self::STATUS_SENT => __( 'Sent', 'wp-mail-smtp-pro' ),
self::STATUS_WAITING => __( 'Waiting for confirmation', 'wp-mail-smtp-pro' ),
self::STATUS_DELIVERED => __( 'Delivered', 'wp-mail-smtp-pro' ),
];
return isset( $statuses[ $this->status ] ) ? $statuses[ $this->status ] : '';
}
/**
* Get the date/time when this email was sent.
*
* @since 1.5.0
* @since 2.6.0 Added UTC timezone.
* @since 3.8.0 Handle cases where `$this->date_sent` is a `DateTime` object.
*
* @return DateTime
* @throws \Exception Emits exception on incorrect date.
*/
public function get_date_sent() {
$timezone = new \DateTimeZone( 'UTC' );
$date = false;
if ( ! empty( $this->date_sent ) ) {
$date_sent_string = is_a( $this->date_sent, DateTime::class ) ? $this->date_sent->format( WP::datetime_mysql_format() ) : $this->date_sent;
$date = DateTime::createFromFormat( WP::datetime_mysql_format(), $date_sent_string, $timezone );
}
if ( $date === false ) {
$date = new \DateTime( 'now', $timezone );
}
return $date;
}
/**
* @return string
*/
public function get_mailer() {
return $this->mailer;
}
/**
* Whether email had attachments on not.
*
* @since 1.5.0
*
* @return int 0 when no attachments, number if any.
*/
public function get_attachments() {
return (int) $this->attachments;
}
/**
* Get name of the plugin/theme (or WP core) that initiated/called the `wp_mail` function.
*
* @since 3.0.0
*
* @return string
*/
public function get_initiator_name() {
return $this->initiator_name;
}
/**
* Get file path and line number of the plugin/theme (or WP core) that initiated/called the `wp_mail` function.
*
* @since 3.0.0
*
* @return string
*/
public function get_initiator_file() {
return $this->initiator_file;
}
/**
* Get the parent's ID.
*
* @since 3.7.0
*
* @return int
*/
public function get_parent_id() {
return $this->parent_id;
}
/**
* Get email content type.
*
* @since 2.9.0
*
* @return string
*/
public function get_content_type() {
// Check if HTML content is available.
if ( ! empty( $this->get_content_html() ) ) {
return 'text/html';
}
// Check the content-type header.
$content_type_header = $this->get_header( 'Content-Type' );
$content_type_data = explode( ';', $content_type_header );
if ( ! empty( $content_type_data[0] ) ) {
return strtolower( trim( $content_type_data[0] ) );
}
return 'text/plain';
}
/**
* Get email charset.
*
* @since 2.9.0
*
* @return string
*/
public function get_charset() {
$charset = get_bloginfo( 'charset' );
$content_type_header = $this->get_header( 'Content-Type' );
list( , $charset_content ) = explode( ';', $content_type_header );
if ( false !== stripos( $charset_content, 'charset=' ) ) {
$charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
}
return $charset;
}
/**
* Get email connection ID.
* If an email was sent via the primary connection, then it returns an empty string.
*
* @since 3.7.0
*
* @return string
*/
public function get_connection_id() {
return $this->get_header( 'X-WP-Mail-SMTP-Connection' );
}
/**
* Is content type HTML based.
*
* @since 2.9.0
*
* @return bool
*/
public function is_content_type_html_based() {
$html_content_types = [
'text/html',
'multipart/alternative',
'multipart/mixed',
];
return in_array( $this->get_content_type(), $html_content_types, true );
}
/**
* Set message ID (mainly external ID from email provider).
*
* @since 3.3.0
*
* @param string $message_id Message ID.
*
* @return Email
*/
public function set_message_id( $message_id ) {
$this->message_id = $message_id;
return $this;
}
/**
* Set the email subject.
*
* @since 1.5.0
* @since 2.2.0 Limit the length of the subject to 191 chars because the `subject` DB column is `varchar(191)`.
*
* @param string $subject
*
* @return Email
*/
public function set_subject( $subject ) {
if ( ! function_exists( 'mb_substr' ) ) {
Helpers::include_mbstring_polyfill();
}
$this->subject = mb_substr( wp_kses( $subject, [] ), 0, 191 );
return $this;
}
/**
* Set the email people. Contains to/cc/bcc/from.
* We require array here.
*
* @since 1.5.0
*
* @param array $people
*
* @return Email
*/
public function set_people( $people ) {
if ( ! is_array( $people ) ) {
$people = array();
}
$this->people = wp_json_encode( $people );
return $this;
}
/**
* Set the email headers.
* We require array here.
*
* @since 1.5.0
*
* @param array $headers
*
* @return Email
*/
public function set_headers( $headers ) {
if ( ! is_array( $headers ) ) {
$headers = [];
}
$filtered_headers = array_filter(
$headers,
function ( $header ) {
return strpos( $header, ':' ) !== false;
}
);
$this->headers = wp_json_encode( array_values( $filtered_headers ) );
return $this;
}
/**
* Set the email error text.
*
* @since 2.5.0
*
* @param string $error_text The email error text to be set.
*
* @return Email
*/
public function set_error_text( $error_text ) {
$this->error_text = sanitize_textarea_field( $error_text );
return $this;
}
/**
* Set the email content and try to define whether it has HTML or not.
*
* @since 1.5.0
*
* @param string $content The email content.
*
* @return Email
*/
public function set_content( $content ) {
// TODO: this check is NOT reliable.
if ( $content === wp_strip_all_tags( $content ) ) {
$this->set_content_plain( wp_strip_all_tags( $content ) );
} else {
$this->set_content_html( $content );
}
return $this;
}
/**
* Set the email plain text content.
*
* @since 1.5.0
*
* @param string $plain
*
* @return Email
*/
public function set_content_plain( $plain ) {
$this->content_plain = $plain;
return $this;
}
/**
* Set the email HTML content.
*
* @since 1.5.0
*
* @param string $html
*
* @return Email
*/
public function set_content_html( $html ) {
$this->content_html = $html;
return $this;
}
/**
* Set the date when the email was sent.
* Date validation is taken from wp_insert_post().
* Date should be set in this format: Y-m-d H:i:s.
* DateTime object will be set in property, on save it will be converted to string.
*
* @since 1.5.0
* @since 2.6.0 Added UTC timezone.
*
* @param string $date_sent
*
* @return Email
*
* @throws \Exception When date is incorrectly generated on PHP side.
*/
public function set_date_sent( $date_sent ) {
// Validate the date. Time is ignored.
$mm = substr( $date_sent, 5, 2 );
$jj = substr( $date_sent, 8, 2 );
$aa = substr( $date_sent, 0, 4 );
$valid_date = wp_checkdate( $mm, $jj, $aa, $date_sent );
$timezone = new \DateTimeZone( 'UTC' );
if ( $valid_date ) {
$date_sent = \DateTime::createFromFormat( WP::datetime_mysql_format(), $date_sent, $timezone );
} else {
$date_sent = new \DateTime( 'now', $timezone );
}
$this->date_sent = $date_sent;
return $this;
}
/**
* Set whether the email had attachments.
*
* @since 1.5.0
*
* @param int $attachments
*
* @return Email
*/
public function set_attachments( $attachments ) {
$this->attachments = (int) $attachments;
return $this;
}
/**
* Set the mailer key that was used to send this email.
*
* @since 1.5.0
*
* @param string $mailer
*
* @return Email
*/
public function set_mailer( $mailer ) {
$this->mailer = sanitize_key( $mailer );
return $this;
}
/**
* Set the status of the email: was it successfully sent or not?
* We do not insist on exact default statuses, as developers may want to change that.
* We do insist on an integer.
*
* @since 1.5.0
*
* @param int $status
*
* @return Email
*/
public function set_status( $status ) {
$this->status = (int) $status;
return $this;
}
/**
* Set the initiator by checking the backtrace for the wp_mail function call.
*
* @since 3.0.0
*
* @return Email
*/
public function set_initiator() {
$initiator = wp_mail_smtp()->get_wp_mail_initiator();
if ( empty( $initiator->get_file() ) ) {
return $this;
}
$this->initiator_file = $initiator->get_file();
if ( ! empty( $initiator->get_line() ) ) {
$this->initiator_file .= ':' . $initiator->get_line();
}
$this->initiator_name = $initiator->get_name();
return $this;
}
/**
* Set the initiator name.
*
* @since 3.8.0
*
* @param string $name Initiator name.
*
* @return Email
*/
public function set_initiator_name( $name ) {
$this->initiator_name = $name;
return $this;
}
/**
* Set parent ID.
*
* @since 3.7.0
*
* @param int $parent_id Parent ID.
*
* @return Email
*/
public function set_parent_id( $parent_id ) {
$this->parent_id = $parent_id;
return $this;
}
/**
* Save a new or modified email in DB.
*
* @since 1.5.0
*
* @return Email New or updated email class instance.
* @throws \Exception When email init fails.
*/
public function save() {
global $wpdb;
$table = Logs::get_table_name();
if ( (bool) $this->get_id() ) {
// Update the existing DB table record.
$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
$table,
[
'message_id' => $this->message_id,
'subject' => $this->subject,
'people' => $this->people,
'headers' => $this->headers,
'error_text' => $this->error_text,
'content_plain' => $this->content_plain,
'content_html' => $this->content_html,
'status' => $this->status,
'date_sent' => $this->get_date_sent()->format( WP::datetime_mysql_format() ),
'mailer' => $this->mailer,
'attachments' => $this->attachments,
'initiator_name' => $this->initiator_name,
'initiator_file' => $this->initiator_file,
'parent_id' => $this->parent_id,
],
[
'id' => $this->get_id(),
],
[
'%s', // message ID.
'%s', // subject.
'%s', // people.
'%s', // headers.
'%s', // error_text.
'%s', // content_plain.
'%s', // content_html.
'%s', // status.
'%s', // date_sent.
'%s', // mailer.
'%d', // attachments.
'%s', // initiator_name.
'%s', // initiator_file.
'%d', // parent ID.
],
[
'%d',
]
);
$email_id = $this->get_id();
} else {
// Create a new DB table record.
$wpdb->insert(
$table,
[
'message_id' => $this->message_id,
'subject' => $this->subject,
'people' => $this->people,
'headers' => $this->headers,
'error_text' => $this->error_text,
'content_plain' => $this->content_plain,
'content_html' => $this->content_html,
'status' => $this->status,
'date_sent' => $this->get_date_sent()->format( WP::datetime_mysql_format() ),
'mailer' => $this->mailer,
'attachments' => $this->attachments,
'initiator_name' => $this->initiator_name,
'initiator_file' => $this->initiator_file,
'parent_id' => $this->parent_id,
],
[
'%s', // message ID.
'%s', // subject.
'%s', // people.
'%s', // headers.
'%s', // error_text.
'%s', // content_plain.
'%s', // content_html.
'%s', // status.
'%s', // date_sent.
'%s', // mailer.
'%d', // attachments.
'%s', // initiator_name.
'%s', // initiator_file.
'%d', // parent ID.
]
);
$email_id = $wpdb->insert_id;
}
try {
$email = new Email( $email_id );
} catch ( \Exception $e ) {
$email = new Email();
}
return $email;
}
/**
* Delete the current email.
* Usage:
* $email = new Email( $email_id );
* $email->delete();
*
* @since 1.5.0
*
* @return int|false Integer on successful deletion (number of rows), false on failure.
*/
public function delete() {
global $wpdb;
return $wpdb->delete(
Logs::get_table_name(),
array(
'id' => $this->get_id(),
),
array(
'%d',
)
);
}
/**
* Whether the email instance is a valid entity to work with.
*
* @since 1.5.0
*/
public function is_valid() {
if (
empty( $this->id ) ||
empty( $this->headers ) ||
empty( $this->date_sent )
) {
return false;
}
return true;
}
/**
* Whether the email was successfully sent or not.
*
* @since 1.5.0
*
* @return bool
*/
public function is_sent() {
return self::STATUS_SENT === $this->get_status();
}
/**
* Whether the email is waiting for delivery verification.
*
* @since 3.9.0
*
* @return bool
*/
public function is_waiting_for_delivery_verification() {
return $this->get_status() === self::STATUS_WAITING;
}
/**
* Whether the email sending has failed.
*
* @since 2.5.0
*
* @return bool
*/
public function has_failed() {
return self::STATUS_UNSENT === $this->get_status();
}
/**
* Whether the email content has HTML or not.
*
* @since 1.5.0
*
* @return bool
*/
public function is_html() {
return ! empty( $this->get_content_html() );
}
/**
* If the email has an error text.
*
* @since 2.5.0
*
* @return bool
*/
public function has_error() {
return ! empty( $this->get_error_text() );
}
/**
* If the email is a test.
*
* @since 3.10.0
*
* @return bool
*/
public function is_test() {
$mailer_type = $this->get_header( 'X-Mailer-Type' );
return in_array(
$mailer_type,
[
'WPMailSMTP/Admin/Test',
'WPMailSMTP/Admin/SetupWizard/Test',
],
true
);
}
}