<?php
declare(strict_types = 1);
namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer;
if (!defined('ABSPATH')) exit;
use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer;
use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Process_Manager;
use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates;
use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
use WP_Style_Engine;
class Renderer {
 private Theme_Controller $theme_controller;
 private Content_Renderer $content_renderer;
 private Templates $templates;
 private Css_Inliner $css_inliner;
 private Process_Manager $process_manager;
 private Personalization_Tags_Registry $personalization_tags_registry;
 private array $personalization_tag_placeholders = array();
 const TEMPLATE_FILE = 'template-canvas.php';
 const TEMPLATE_STYLES_FILE = 'template-canvas.css';
 public function __construct(
 Content_Renderer $content_renderer,
 Templates $templates,
 Css_Inliner $css_inliner,
 Theme_Controller $theme_controller,
 Personalization_Tags_Registry $personalization_tags_registry,
 Process_Manager $process_manager
 ) {
 $this->content_renderer = $content_renderer;
 $this->templates = $templates;
 $this->theme_controller = $theme_controller;
 $this->css_inliner = $css_inliner;
 $this->personalization_tags_registry = $personalization_tags_registry;
 $this->process_manager = $process_manager;
 }
 public function render( \WP_Post $post, string $subject, string $pre_header, string $language, string $meta_robots = '', string $template_slug = '' ): array {
 if ( ! $template_slug ) {
 $template_slug = get_page_template_slug( $post ) ? get_page_template_slug( $post ) : 'email-general';
 }
 // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
 $template = $this->templates->get_block_template( $template_slug );
 $previous_rendering_context = $this->content_renderer->get_current_rendering_context();
 try {
 $rendering_context = $this->content_renderer->create_rendering_context( $language, $post, $template );
 $this->content_renderer->set_rendering_context( $rendering_context );
 $email_styles = $this->theme_controller->get_styles();
 $content_result = $this->content_renderer->render_without_css_inline( $post, $template );
 $template_html = $content_result['html'];
 $content_styles = $content_result['styles'];
 $layout = $this->theme_controller->get_layout_settings();
 ob_start();
 include self::TEMPLATE_FILE;
 $rendered_template = (string) ob_get_clean();
 $template_styles =
 WP_Style_Engine::compile_css(
 array(
 'background-color' => $email_styles['color']['background'] ?? 'inherit',
 'color' => $email_styles['color']['text'] ?? 'inherit',
 'padding-top' => $email_styles['spacing']['padding']['top'] ?? '0px',
 'padding-bottom' => $email_styles['spacing']['padding']['bottom'] ?? '0px',
 'font-family' => $email_styles['typography']['fontFamily'] ?? 'inherit',
 'line-height' => $email_styles['typography']['lineHeight'] ?? '1.5',
 'font-size' => $email_styles['typography']['fontSize'] ?? 'inherit',
 'direction' => $rendering_context->get_text_direction(),
 'text-align' => $rendering_context->get_default_text_align(),
 ),
 'body, .email_layout_wrapper'
 );
 $template_styles .= WP_Style_Engine::compile_css(
 array(
 'direction' => $rendering_context->get_text_direction(),
 'text-align' => $rendering_context->get_default_text_align(),
 ),
 '.email_content_wrapper, .email_preheader'
 );
 $template_styles .= '.email_layout_wrapper { box-sizing: border-box;}';
 $template_styles .= file_get_contents( __DIR__ . '/' . self::TEMPLATE_STYLES_FILE );
 $template_styles = wp_strip_all_tags( (string) apply_filters( 'woocommerce_email_renderer_styles', $template_styles, $post ) );
 // Single CSS inlining pass: combine content and template styles, then inline all at once.
 $all_styles = '<style>' . $template_styles . $content_styles . '</style>';
 $rendered_template = $this->inline_css_styles( $all_styles . $rendered_template );
 // Postprocess after CSS inlining (border normalization, CSS variable replacement, etc.).
 $rendered_template = $this->process_manager->postprocess( $rendered_template );
 $rendered_template = $this->apply_html_attributes( $rendered_template, $rendering_context );
 // This is a workaround to support link :hover in some clients. Ideally we would remove the ability to set :hover
 // however this is not possible using the color panel from Gutenberg.
 if ( isset( $email_styles['elements']['link'][':hover']['color']['text'] ) ) {
 $rendered_template = str_replace( '<!-- Forced Styles -->', '<style>a:hover { color: ' . esc_attr( $email_styles['elements']['link'][':hover']['color']['text'] ) . ' !important; }</style>', $rendered_template );
 }
 return array(
 'html' => $rendered_template,
 'text' => $this->render_text_version( $rendered_template ),
 );
 } finally {
 $this->content_renderer->restore_rendering_context( $previous_rendering_context );
 }
 }
 private function inline_css_styles( $template ) {
 return $this->css_inliner->from_html( $template )->inline_css()->render();
 }
 private function apply_html_attributes( string $template, Rendering_Context $rendering_context ): string {
 $processor = new \WP_HTML_Tag_Processor( $template );
 if ( ! $processor->next_tag( array( 'tag_name' => 'html' ) ) ) {
 return $template;
 }
 $language = $rendering_context->get_language();
 if ( $language ) {
 $processor->set_attribute( 'lang', str_replace( '_', '-', $language ) );
 }
 $processor->set_attribute( 'dir', $rendering_context->get_text_direction() );
 return $processor->get_updated_html();
 }
 private function render_text_version( $template ) {
 $template = ( mb_detect_encoding( $template, 'UTF-8', true ) ) ? $template : mb_convert_encoding( $template, 'UTF-8', mb_list_encodings() );
 // Ensure template is a string before processing.
 if ( ! is_string( $template ) ) {
 return '';
 }
 // Preserve personalization tags by temporarily replacing them with unique placeholders.
 $template = $this->preserve_personalization_tags( $template );
 $result = Html2Text::convert( (string) $template, array( 'ignore_errors' => true ) );
 if ( ! $result ) {
 return '';
 }
 // Restore personalization tags from placeholders.
 $result = $this->restore_personalization_tags( $result );
 return $result;
 }
 private function preserve_personalization_tags( string $template ): string {
 $all_registered_tags = $this->personalization_tags_registry->get_all();
 $this->personalization_tag_placeholders = array();
 $counter = 0;
 $base_tokens = array(); // All the tokens used in the email, e.g. [woocommerce/customer-username].
 $token_prefixes = array(); // All the used prefixes, e.g. woocommerce, mailpoet, etc.
 foreach ( $all_registered_tags as $tag ) {
 $token = $tag->get_token(); // E.g. [woocommerce/customer-username].
 $base_tokens[ $token ] = true;
 // Remove brackets for regex matching, escape for regex.
 $token_prefixes[] = preg_quote( substr( $token, 1, -1 ), '/' );
 }
 if ( empty( $token_prefixes ) ) {
 return $template;
 }
 // Match all of the code comments that look like a personalization tags.
 $pattern = '/<!--\[(' . implode( '|', $token_prefixes ) . ')(?:\s+[^\]]*)?\]-->/';
 $template = preg_replace_callback(
 $pattern,
 function ( $matches ) use ( &$counter, $base_tokens ) {
 // $matches[1] is the token without brackets, add brackets for lookup.
 $base_token = '[' . $matches[1] . ']';
 if ( isset( $base_tokens[ $base_token ] ) ) {
 $placeholder = 'PERSONALIZATION_TAG_PLACEHOLDER_' . $counter;
 $this->personalization_tag_placeholders[ $placeholder ] = $matches[0];
 ++$counter;
 return $placeholder;
 }
 return $matches[0];
 },
 $template
 );
 return $template ?? '';
 }
 private function restore_personalization_tags( string $text ): string {
 if ( empty( $this->personalization_tag_placeholders ) ) {
 return $text;
 }
 foreach ( $this->personalization_tag_placeholders as $placeholder => $html_comment ) {
 $text = str_replace( $placeholder, $html_comment, $text );
 }
 return $text;
 }
}
