Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
MailTemplateRenderer
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
4 / 4
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 render
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 renderBlocks
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 htmlToPlainText
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Mail\Application\Service;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Template\Application\Service\CssInlinerService;
12use App\Core\Template\Application\Service\TemplateCompilerService;
13
14/**
15 * Mail Template and Body Renderer Service.
16 *
17 * Replaces placeholder tokens, compiles visual block trees, and inlines CSS
18 * for universal email client compatibility (Outlook, Gmail, Apple Mail).
19 *
20 * @package App\Modules\Mail\Application\Service
21 */
22final class MailTemplateRenderer
23{
24    private TemplateCompilerService $compiler;
25    private CssInlinerService $cssInliner;
26
27    /**
28     * MailTemplateRenderer constructor.
29     *
30     * @param TemplateCompilerService|null $compiler Visual block and Twig compiler service.
31     * @param CssInlinerService|null       $cssInliner CSS inliner for email body safety.
32     */
33    public function __construct(
34        ?TemplateCompilerService $compiler = null,
35        ?CssInlinerService $cssInliner = null,
36    ) {
37        $this->compiler = $compiler ?? new TemplateCompilerService();
38        $this->cssInliner = $cssInliner ?? new CssInlinerService();
39    }
40
41    /**
42     * Renders string content by replacing placeholder keys with values or evaluating Twig AST.
43     *
44     * @param string               $content   Content template with {{ key }} placeholders or Twig syntax.
45     * @param array<string, mixed> $params    Replacement map key => value or nested context.
46     * @param bool                 $inlineCss Whether to inline `<style>` blocks into inline style attributes.
47     * @return string Rendered content string.
48     */
49    public function render(string $content, array $params = [], bool $inlineCss = true): string
50    {
51        $rendered = $this->compiler->render($content, $params);
52
53        return $inlineCss ? $this->cssInliner->inline($rendered) : $rendered;
54    }
55
56    /**
57     * Compiles and renders a visual block tree (JSON or array).
58     *
59     * @param array<string, mixed>|string $blocksData Raw JSON string or block tree array.
60     * @param array<string, mixed>        $params     Context variables.
61     * @param bool                        $inlineCss  Whether to inline CSS.
62     * @return string Compiled and rendered email HTML.
63     */
64    public function renderBlocks(array|string $blocksData, array $params = [], bool $inlineCss = true): string
65    {
66        $html = $this->compiler->compileBlocks($blocksData);
67
68        return $this->render($html, $params, $inlineCss);
69    }
70
71    /**
72     * Converts rich HTML into clean plain-text fallback.
73     *
74     * @param string $html HTML body string.
75     * @return string Plain-text alternative string.
76     */
77    public function htmlToPlainText(string $html): string
78    {
79        $text = (string)preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $html);
80        $text = (string)preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $text);
81        $text = str_ireplace(['<br>', '<br/>', '<br />'], "\n", $text);
82        $text = str_ireplace(['</p>', '</div>'], "\n\n", $text);
83        $text = strip_tags($text);
84
85        return trim((string)preg_replace("/[\r\n]{3,}/", "\n\n", $text));
86    }
87}