JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
namespace WPForms\Emails;
use WPForms\Emails\Templates\General;
/**
* Mailer class to wrap wp_mail().
*
* @since 1.5.4
*/
class Mailer {
/**
* Array or comma-separated list of email addresses to send a message.
*
* @since 1.5.4
*
* @var string|string[]
*/
private $to_email;
/**
* CC addresses (comma delimited).
*
* @since 1.5.4
*
* @var string
*/
private $cc;
/**
* From address.
*
* @since 1.5.4
*
* @var string
*/
private $from_address = '';
/**
* From name.
*
* @since 1.5.4
*
* @var string
*/
private $from_name;
/**
* Reply to address.
*
* @since 1.5.4
*
* @var string
*/
private $reply_to;
/**
* Email headers.
*
* @since 1.5.4
*
* @var string
*/
private $headers;
/**
* Email content type.
*
* @since 1.5.4
*
* @var string
*/
private $content_type;
/**
* Email attachments.
*
* @since 1.5.4
*
* @var string|string[]
*/
private $attachments;
/**
* Email subject.
*
* @since 1.5.4
*
* @var string
*/
private $subject;
/**
* Email message.
*
* @since 1.5.4
*
* @var string
*/
private $message;
/**
* Email template.
*
* @since 1.5.4
*
* @var General
*/
private $template;
/**
* Set a property.
*
* @since 1.5.4
*
* @param string $key Property name.
* @param string|array $value Property value.
*/
public function __set( string $key, $value ) {
$this->$key = $value;
}
/**
* Get a property.
*
* @since 1.5.4
*
* @param string $key Property name.
*
* @return string
*/
public function __get( $key ) {
return $this->$key;
}
/**
* Check if a property exists.
*
* @since 1.5.4
*
* @param string $key Property name.
*
* @return bool
*/
public function __isset( $key ) {
return isset( $this->key );
}
/**
* Unset a property.
*
* @since 1.5.4
*
* @param string $key Property name.
*/
public function __unset( $key ) {
unset( $this->key );
}
/**
* Email kill switch if needed.
*
* @since 1.5.4
*
* @return bool
*/
public function is_email_disabled() {
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return (bool) apply_filters( 'wpforms_emails_mailer_is_email_disabled', false, $this );
}
/**
* Sanitize the string.
*
* @since 1.5.4
* @since 1.6.0 Deprecated param: $linebreaks. This is handled by wpforms_decode_string().
*
* @param string $input String that may contain tags.
* @param string $context Context of the string.
*
* @return string
* @uses wpforms_decode_string()
*/
public function sanitize( $input = '', $context = '' ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
return wpforms_decode_string( $input );
}
/**
* Get the email from the name.
*
* @since 1.5.4
*
* @return string
*/
public function get_from_name() {
$this->from_name = $this->from_name ? $this->sanitize( $this->from_name ) : get_bloginfo( 'name' );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return apply_filters( 'wpforms_emails_mailer_get_from_name', $this->from_name, $this );
}
/**
* Get the email from the address.
*
* @since 1.5.4
*
* @return string
*/
public function get_from_address() {
$from_address = $this->sanitize( $this->from_address, 'notification-from' );
$from_address = $from_address ? $from_address : get_option( 'admin_email' );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return apply_filters( 'wpforms_emails_mailer_get_from_address', $from_address, $this );
}
/**
* Get the email reply to the address.
*
* @since 1.5.4
*
* @return string
*/
public function get_reply_to_address() {
if ( empty( $this->reply_to ) || ! is_email( $this->reply_to ) ) {
$this->reply_to = $this->from_address;
}
$this->reply_to = $this->sanitize( $this->reply_to, 'notification-reply-to' );
if ( empty( $this->reply_to ) || ! is_email( $this->reply_to ) ) {
$this->reply_to = get_option( 'admin_email' );
}
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return apply_filters( 'wpforms_emails_mailer_get_reply_to_address', $this->reply_to, $this );
}
/**
* Get the email carbon copy addresses.
*
* @since 1.5.4
* @since 1.8.9 Allow using CC field as an array.
*
* @return string The email carbon copy addresses.
*/
public function get_cc_address() {
if ( is_array( $this->cc ) ) {
$this->cc = implode( ',', $this->cc );
}
if ( empty( $this->cc ) ) {
/**
* Filters the email carbon copy addresses.
*
* @since 1.5.4
*
* @param string $cc Carbon copy addresses.
* @param Mailer $this Mailer instance.
*/
return apply_filters( 'wpforms_emails_mailer_get_cc_address', $this->cc, $this );
}
$this->cc = $this->sanitize( $this->cc );
$addresses = array_filter( array_map( 'sanitize_email', explode( ',', $this->cc ) ) );
$this->cc = implode( ',', $addresses );
/** This filter is documented in src/Emails/Mailer.php. */
return apply_filters( 'wpforms_emails_mailer_get_cc_address', $this->cc, $this );
}
/**
* Get the email content type.
*
* @since 1.5.4
*
* @return string The email content type.
*/
public function get_content_type() {
$is_html = ! Helpers::is_plain_text_template();
if ( ! $this->content_type && $is_html ) {
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$this->content_type = apply_filters( 'wpforms_emails_mailer_get_content_type_default', 'text/html', $this );
} elseif ( ! $is_html ) {
$this->content_type = 'text/plain';
}
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
return apply_filters( 'wpforms_emails_mailer_get_content_type', $this->content_type, $this );
}
/**
* Get the email subject.
*
* @since 1.8.9
*
* @return string The email subject.
*/
private function get_subject() {
if ( empty( $this->subject ) ) {
$this->subject = __( 'New Email Submit', 'wpforms-lite' );
}
/**
* Filters the email subject.
*
* @since 1.8.9
*
* @param string $subject Email subject.
* @param Mailer $this Mailer instance.
*/
return apply_filters( 'wpforms_emails_mailer_get_subject', $this->subject, $this );
}
/**
* Get the email message.
*
* @since 1.5.4
*
* @return string The email message.
*/
public function get_message() {
if ( empty( $this->message ) && ! empty( $this->template ) ) {
$this->message = $this->template->get();
}
/**
* Filters the email message.
*
* @since 1.5.4
*
* @param string $message Email message.
* @param Mailer $this Mailer instance.
*/
return apply_filters( 'wpforms_emails_mailer_get_message', $this->message, $this );
}
/**
* Get the email headers.
*
* @since 1.5.4
*
* @return string The email headers.
*/
public function get_headers() {
$from_name = $this->sanitize_email_header_name( (string) $this->get_from_name() );
$from_address = $this->sanitize_email_header( (string) $this->get_from_address() );
$reply_to = $this->sanitize_email_header( (string) $this->get_reply_to_address() );
$cc = $this->sanitize_email_header( (string) $this->get_cc_address() );
$this->headers = "From: {$from_name} <{$from_address}>\r\n";
if ( $reply_to ) {
$this->headers .= "Reply-To: {$reply_to}\r\n";
}
if ( $cc ) {
$this->headers .= "Cc: {$cc}\r\n";
}
$this->headers .= "Content-Type: {$this->get_content_type()}; charset=utf-8\r\n";
/**
* Filters the email headers.
*
* @since 1.5.4
*
* @param string $headers Email headers.
* @param Mailer $this Mailer instance.
*/
return (string) apply_filters( 'wpforms_emails_mailer_get_headers', $this->headers, $this );
}
/**
* Sanitize a value for safe use as an email header.
*
* Replaces line breaks with a space to prevent email header (CRLF) injection,
* using the same strategy as the email subject. Address and CC slots use this
* method so their commas (recipient separators) are preserved.
*
* @since 1.10.2.1
*
* @param string $value Header value to sanitize.
*
* @return string Header value with line breaks replaced by spaces.
*/
private function sanitize_email_header( string $value ): string {
return trim( str_replace( [ "\r\n", "\r", "\n" ], ' ', $value ) );
}
/**
* Sanitize a value for safe use as an email header display name.
*
* On top of neutralizing line breaks, this strips the characters wp_mail()
* treats as address-list delimiters when it re-parses the assembled header:
* commas (it splits Reply-To/Cc/Bcc on them) and angle brackets (address
* delimiters). RFC quoting is not enough because wp_mail() ignores it, so a
* comma left in a display name would inject an extra recipient. Declared
* protected because the Notifications subclass sanitizes the Reply-To name.
*
* @since 1.10.2.1
*
* @param string $name Display name to sanitize.
*
* @return string Display name safe to embed in a From/Reply-To header.
*/
protected function sanitize_email_header_name( string $name ): string {
return trim( str_replace( [ ',', '<', '>' ], '', $this->sanitize_email_header( $name ) ) );
}
/**
* Get the email attachments.
*
* @since 1.5.4
*
* @return string|string[]
*/
public function get_attachments() {
if ( $this->attachments === null ) {
$this->attachments = [];
}
/**
* Filters the email attachments.
*
* @since 1.5.4
*
* @param string|string[] $attachments Array or string with attachment paths.
* @param Mailer $this Mailer instance.
*/
return apply_filters( 'wpforms_emails_mailer_get_attachments', $this->attachments, $this );
}
/**
* Set an email address to send to.
*
* @since 1.5.4
*
* @param string|string[] $email Array or comma-separated list of email addresses to send a message.
*
* @return Mailer
*/
public function to_email( $email ) {
if ( is_string( $email ) ) {
$email = explode( ',', $email );
}
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$this->to_email = apply_filters( 'wpforms_emails_mailer_to_email', $email, $this );
return $this;
}
/**
* Set an email subject.
*
* @since 1.5.4
*
* @param string $subject Email subject.
*
* @return Mailer
*/
public function subject( $subject ) {
$subject = $this->sanitize( $subject );
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$this->subject = apply_filters( 'wpforms_emails_mailer_subject', $subject, $this );
return $this;
}
/**
* Set an email message (body).
*
* @since 1.5.4
*
* @param string $message Email message.
*
* @return Mailer
*/
public function message( $message ) {
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$this->message = apply_filters( 'wpforms_emails_mailer_message', $message, $this );
return $this;
}
/**
* Set email template.
*
* @since 1.5.4
*
* @param General $template Email template.
*
* @return Mailer
*/
public function template( General $template ) {
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
$this->template = apply_filters( 'wpforms_emails_mailer_template', $template, $this );
return $this;
}
/**
* Get email errors.
*
* @since 1.5.4
*
* @return array
*/
protected function get_errors() {
$errors = [];
foreach ( (array) $this->to_email as $email ) {
if ( ! is_email( $email ) ) {
$errors[] = sprintf( /* translators: %1$s - namespaced class name, %2$s - invalid email. */
esc_html__( '%1$s Invalid email address %2$s.', 'wpforms-lite' ),
'[WPForms\Emails\Mailer]',
$email
);
}
}
if ( empty( $this->get_subject() ) ) {
$errors[] = sprintf( /* translators: %s - namespaced class name. */
esc_html__( '%s Empty subject line.', 'wpforms-lite' ),
'[WPForms\Emails\Mailer]'
);
}
if ( empty( $this->get_message() ) ) {
$errors[] = sprintf( /* translators: %s - namespaced class name. */
esc_html__( '%s Empty message.', 'wpforms-lite' ),
'[WPForms\Emails\Mailer]'
);
}
return $errors;
}
/**
* Log given email errors.
*
* @since 1.5.4
*
* @param array $errors Errors to log.
*/
protected function log_errors( $errors ): void {
if ( empty( $errors ) || ! is_array( $errors ) ) {
return;
}
foreach ( $errors as $error ) {
wpforms_log(
$error,
[
'to_email' => $this->to_email,
'subject' => $this->subject,
'message' => wp_trim_words( $this->get_message() ),
],
[
'type' => 'error',
]
);
}
}
/**
* Send the email.
*
* @since 1.5.4
*
* @return bool
*/
public function send() {
if ( ! did_action( 'init' ) && ! did_action( 'admin_init' ) ) {
_doing_it_wrong( __FUNCTION__, esc_html__( 'You cannot send emails with WPForms\Emails\Mailer until init/admin_init has been reached.', 'wpforms-lite' ), null );
return false;
}
// Don't send anything if emails have been disabled.
if ( $this->is_email_disabled() ) {
return false;
}
$errors = $this->get_errors();
if ( $errors ) {
$this->log_errors( $errors );
return false;
}
$this->send_before();
$sent = wp_mail(
$this->to_email,
$this->get_subject(),
$this->get_message(),
$this->get_headers(),
$this->get_attachments()
);
$this->send_after();
return $sent;
}
/**
* Add filters / actions before the email is sent.
*
* @since 1.5.4
*/
public function send_before(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
do_action( 'wpforms_emails_mailer_send_before', $this );
add_filter( 'wp_mail_from', [ $this, 'get_from_address' ] );
add_filter( 'wp_mail_from_name', [ $this, 'get_from_name' ] );
add_filter( 'wp_mail_content_type', [ $this, 'get_content_type' ] );
}
/**
* Remove filters / actions after the email is sent.
*
* @since 1.5.4
*/
public function send_after(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// phpcs:ignore WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
do_action( 'wpforms_emails_mailer_send_after', $this );
remove_filter( 'wp_mail_from', [ $this, 'get_from_address' ] );
remove_filter( 'wp_mail_from_name', [ $this, 'get_from_name' ] );
remove_filter( 'wp_mail_content_type', [ $this, 'get_content_type' ] );
}
}