<?php
/**
* File containing the Email_Sender class.
*
* @package sensei
*/
namespace Sensei\Internal\Emails;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Sensei\ThirdParty\Pelago\Emogrifier\CssInliner;
use Sensei_Settings;
use WP_Post;
/**
* Class Email_Sender
*
* @package Sensei\Internal\Emails
*/
class Email_Sender {
/**
* Email unique identifier meta key.
*/
public const EMAIL_ID_META_KEY = '_sensei_email_identifier';
/**
* Email repository instance.
*
* @var Email_Repository
*/
private $repository;
/**
* Email template repository instance.
*
* @var Email_Template_Repository
*/
private $template_repository;
/**
* Email settings instance.
*
* @var Sensei_Settings
*/
private $settings;
/**
* Email patterns instance.
*
* @var Email_Patterns
*/
private $email_patterns;
/**
* Email_Sender constructor.
*
* @param Email_Repository $repository Email repository instance.
* @param Sensei_Settings $settings Sensei settings instance.
* @param Email_Patterns $email_patterns Email patterns instance.
*/
public function __construct( Email_Repository $repository, Sensei_Settings $settings, Email_Patterns $email_patterns ) {
$this->repository = $repository;
$this->settings = $settings;
$this->email_patterns = $email_patterns;
}
/**
* Adds all filters and actions.
*/
public function init() {
/**
* Send email of predefined types with provided contents.
*
* @param string $email_name The name of the email template.
* @param array $replacements The placeholder replacements.
*/
add_action( 'sensei_email_send', [ $this, 'send_email' ], 10, 3 );
}
/**
* Send email of type.
*
* @param string $email_name The email type.
* @param array $replacements The placeholder replacements.
* @param string $usage_tracking_type Usage tracking type.
*
* @access private
*/
public function send_email( $email_name, $replacements, $usage_tracking_type ) {
$email_post = $this->get_email_post_by_name( $email_name );
if ( ! $email_post || 'publish' !== $email_post->post_status ) {
return;
}
// In case patterns are not registered.
$this->email_patterns->register_email_block_patterns();
/**
* Filter the email replacements.
*
* @since 4.12.0
*
* @hook sensei_email_replacements
*
* @param {array} $replacements The email replacements.
* @param {string} $email_name The email name.
* @param {WP_Post} $email_post The email post.
* @param {Email_Sender} $email_sender The email sender class instance.
* @return {Array} The email replacements.
*/
$replacements = apply_filters( 'sensei_email_replacements', $replacements, $email_name, $email_post, $this );
add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
foreach ( $replacements as $recipient => $replacement ) {
$subject = $this->get_email_subject( $email_post, $replacement );
$message = $this->get_email_body( $email_post, $replacement );
/*
* For documentation of the filter check class-sensei-emails.php file.
*/
if ( apply_filters( 'sensei_send_emails', true, $recipient, $subject, $message, $email_name, $replacement ) ) {
wp_mail(
$recipient,
$subject,
$message,
$this->get_email_headers()
);
sensei_log_event( 'email_send', [ 'type' => $usage_tracking_type ] );
}
}
remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
}
/**
* Get from name for email.
*
* @since 4.16.0
* @return string
*/
public function get_from_name() {
$settings = $this->settings->get_settings();
$from_name = $settings['email_from_name'] ?? '';
if ( empty( $from_name ) ) {
return get_bloginfo( 'name' );
}
return $from_name;
}
/**
* Get from email address.
*
* @since 4.16.0
* @return string
*/
public function get_from_address() {
$settings = $this->settings->get_settings();
$from_address = $settings['email_from_address'] ?? '';
if ( empty( $from_address ) ) {
return get_bloginfo( 'admin_email' );
}
return $from_address;
}
/**
* Get the email subject.
*
* @internal
*
* @param WP_Post $email_post The email post.
* @param array $placeholders The placeholders.
*
* @return string
*/
public function get_email_subject( WP_Post $email_post, array $placeholders = [] ): string {
return $this->replace_placeholders(
wp_strip_all_tags( $email_post->post_title ),
$placeholders
);
}
/**
* Get the email body.
*
* @internal
*
* @param WP_Post $email_post The email post.
* @param array $placeholders The placeholders.
*
* @return string
*/
public function get_email_body( WP_Post $email_post, array $placeholders = [] ): string {
$post_id = 'revision' === $email_post->post_type ? $email_post->post_parent : $email_post->ID;
// phpcs:ignore WordPress.WP.DiscouragedFunctions.query_posts_query_posts -- We need to modify the global query object in order to render templates.
query_posts(
[
'posts_per_page' => 1,
'p' => $post_id,
'post_type' => Email_Post_Type::POST_TYPE,
]
);
the_post();
$templated_output = $this->get_templated_post_content( $placeholders );
wp_reset_query(); // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- We need to reset the global query object.
return CssInliner::fromHtml( $templated_output )
->inlineCss( $this->load_email_styles() )
->render();
}
/**
* Replace the placeholders in the provided string.
*
* @internal
*
* @param string $content Content to replace the placeholders in.
* @param array $placeholders The placeholders.
*
* @return string
*/
private function replace_placeholders( string $content, array $placeholders ): string {
foreach ( $placeholders as $placeholder => $value ) {
// Strip out URL protocol if necessary. Partial solution for https://github.com/Automattic/sensei/issues/7621.
$content = preg_replace( '~(https?://)?\[' . $placeholder . '\]~', $value, $content );
}
return $content;
}
/**
* Load the emails styles that should overwrite the Gutebenrg styles
*
* @internal
*
* @return string
*/
private function load_email_styles(): string {
$styles = wp_get_global_stylesheet();
$css_dist_path = Sensei()->assets->dist_path( 'css/email-notifications/email-style.css' );
if ( file_exists( $css_dist_path ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file usage.
$styles .= file_get_contents( $css_dist_path );
}
return $styles;
}
/**
* Get the email post by name meta.
*
* @param string $email_identifier The email's unique name.
*
* @return WP_Post|null
*/
private function get_email_post_by_name( $email_identifier ) {
$email_post = $this->repository->get( $email_identifier );
if ( ! $email_post ) {
return;
}
return $email_post;
}
/**
* Get the email body rendered in the email template.
*
* @param array $placeholders The placeholder replaced email content.
*
* @return string
*/
private function get_templated_post_content( $placeholders ) {
global $sensei_email_data;
$sensei_email_data['body_class'] = '';
// Force use the default template usage.
$template = do_blocks( get_block_template( Email_Page_Template::ID, 'wp_template' )->content );
$post_content = $this->replace_placeholders(
$template,
$placeholders
);
$post_content = $this->add_base_url_for_images( $post_content );
$sensei_email_data['email_body'] = $post_content;
ob_start();
require Sensei()->plugin_path() . 'templates/emails/block-email-template.php';
return ltrim( ob_get_clean() );
}
/**
* Append the site URL on all images before send the email.
*
* @param string $content The email content that should be updated.
*
* @return string
*/
private function add_base_url_for_images( $content ) {
return str_replace( 'src="/wp-content', 'src="' . site_url( '/' ) . 'wp-content', $content );
}
/**
* Return the email headers.
*
* @return array Headers.
*/
private function get_email_headers(): array {
$settings = $this->settings->get_settings();
$headers = [
'Content-Type: text/html; charset=UTF-8',
];
if ( ! empty( $settings['email_reply_to_address'] ) ) {
$reply_to_address = $settings['email_reply_to_address'];
$reply_to_name = isset( $settings['email_reply_to_name'] ) ? $settings['email_reply_to_name'] : '';
$headers[] = "Reply-To: $reply_to_name <$reply_to_address>";
}
if ( ! empty( $settings['email_cc'] ) ) {
$headers[] = 'Cc: ' . $settings['email_cc'];
}
if ( ! empty( $settings['email_bcc'] ) ) {
$headers[] = 'Bcc: ' . $settings['email_bcc'];
}
return $headers;
}
}