MessageBodyRenderer   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 50
dl 0
loc 191
c 2
b 0
f 0
ccs 54
cts 54
cp 1
rs 10
wmc 19

8 Methods

Rating   Name   Duplication   Size   Complexity  
A withView() 0 5 1
B addToMessage() 0 34 9
A __construct() 0 4 1
A withTemplate() 0 5 1
A generateTextBodyFromHtml() 0 12 2
A withLocale() 0 5 1
A renderHtml() 0 14 2
A renderText() 0 14 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Mailer;
6
7
use RuntimeException;
8
use Throwable;
9
use Yiisoft\View\View;
10
11
use function html_entity_decode;
12
use function is_array;
13
use function is_string;
14
use function preg_match;
15
use function preg_replace;
16
use function strip_tags;
17
use function trim;
18
19
use const ENT_HTML5;
20
use const ENT_QUOTES;
21
22
/**
23
 * View renderer used to compose message body.
24
 */
25
final class MessageBodyRenderer
26
{
27
    /**
28
     * @param View $view The view instance.
29
     * @param MessageBodyTemplate $template The message body template instance.
30
     */
31
    public function __construct(
32
        private View $view,
33
        private MessageBodyTemplate $template
34
    ) {
35
    }
36
37
    /**
38
     * Adds the rendered body to the message and returns it.
39
     *
40
     * @param MessageInterface $message The message to which the body will be added.
41 48
     * @param mixed $view The view to be used for rendering the message body.
42
     * This can be:
43 48
     * - a string, which represents the view name for rendering the HTML body of the email.
44 48
     *   In this case, the text body will be generated by applying `strip_tags()` to the HTML body.
45
     * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name
46
     *   for rendering the HTML body, while 'text' element is for rendering the text body.
47
     *   For example, `['html' => 'contact-html', 'text' => 'contact-text']`.
48
     * @param array $viewParameters The parameters (name-value pairs)
49
     * that will be extracted and available in the view file.
50
     * @param array $layoutParameters The parameters (name-value pairs)
51
     * that will be extracted and available in the layout file.
52
     *
53
     * @throws Throwable If an error occurred during rendering.
54
     *
55
     * @return MessageInterface The message with the added body.
56
     *
57
     * @psalm-suppress MixedArgument
58
     */
59
    public function addToMessage(
60
        MessageInterface $message,
61
        mixed $view,
62
        array $viewParameters = [],
63
        array $layoutParameters = []
64
    ): MessageInterface {
65
        if (is_string($view)) {
66
            $html = $this->renderHtml($view, $viewParameters, $layoutParameters);
67
            return $message
68
                ->withHtmlBody($html)
69 13
                ->withTextBody($this->generateTextBodyFromHtml($html));
70
        }
71
72
        if (!is_array($view) || (!isset($view['html']) && !isset($view['text']))) {
73
            throw new RuntimeException(
74
                'The "$view" parameter must be a string or array with at least one "text" or "html" key.',
75 13
            );
76 5
        }
77 5
78 5
        if (isset($view['html'])) {
79 5
            $html = $this->renderHtml($view['html'], $viewParameters, $layoutParameters);
80
            $message = $message->withHtmlBody($html);
81
        }
82 13
83 7
        if (isset($view['text'])) {
84 7
            $text = $this->renderText($view['text'], $viewParameters, $layoutParameters);
85 7
            $message = $message->withTextBody($text);
86
        }
87
88 6
        if (isset($html) && !isset($text)) {
89 5
            $message = $message->withTextBody($this->generateTextBodyFromHtml($html));
90 5
        }
91
92
        return $message;
93 6
    }
94 4
95 4
    /**
96
     * Renders the HTML view specified with optional parameters and layout.
97
     *
98 6
     * @param string $view The view name of the view file.
99 2
     * @param array $viewParameters The parameters (name-value pairs)
100
     * that will be extracted and available in the view file.
101
     * @param array $layoutParameters The parameters (name-value pairs)
102 6
     * that will be extracted and available in the layout file.
103
     *
104
     * @throws Throwable If an error occurred during rendering.
105
     *
106
     * @see View::render()
107
     *
108
     * @return string The rendering HTML result.
109
     */
110
    public function renderHtml(string $view, array $viewParameters = [], array $layoutParameters = []): string
111
    {
112
        $content = $this->view
113
            ->withContext($this->template)
114
            ->render($view, $viewParameters);
115
116
        if ($this->template->getHtmlLayout() === '') {
117
            return $content;
118
        }
119
120 8
        $layoutParameters['content'] = $content;
121
        return $this->view
122 8
            ->withContext($this->template)
123 8
            ->render($this->template->getHtmlLayout(), $layoutParameters);
124 8
    }
125
126 8
    /**
127 7
     * Renders the TEXT view specified with optional parameters and layout.
128
     *
129
     * @param string $view The view name of the view file.
130 1
     * @param array $viewParameters The parameters (name-value pairs)
131 1
     * that will be extracted and available in the view file.
132 1
     * @param array $layoutParameters The parameters (name-value pairs)
133 1
     * that will be extracted and available in the layout file.
134
     *
135
     * @throws Throwable If an error occurred during rendering.
136
     *
137
     * @see View::render()
138
     *
139
     * @return string The rendering TEXT result.
140
     */
141
    public function renderText(string $view, array $viewParameters = [], array $layoutParameters = []): string
142
    {
143
        $content = $this->view
144
            ->withContext($this->template)
145
            ->render($view, $viewParameters);
146
147
        if ($this->template->getTextLayout() === '') {
148
            return $content;
149
        }
150
151 7
        $layoutParameters['content'] = $content;
152
        return $this->view
153 7
            ->withContext($this->template)
154 7
            ->render($this->template->getTextLayout(), $layoutParameters);
155 7
    }
156
157 7
    /**
158 6
     * Returns a new instance with the specified view.
159
     *
160
     * @param View $view The view instance.
161 1
     *
162 1
     * @return self The new instance.
163 1
     */
164 1
    public function withView(View $view): self
165
    {
166
        $new = clone $this;
167
        $new->view = $view;
168
        return $new;
169
    }
170
171
    /**
172
     * Returns a new instance with the specified message body template.
173
     *
174 1
     * @param MessageBodyTemplate $template The message body template.
175
     *
176 1
     * @return self The new instance.
177 1
     */
178 1
    public function withTemplate(MessageBodyTemplate $template): self
179
    {
180
        $new = clone $this;
181
        $new->template = $template;
182
        return $new;
183
    }
184
185
    /**
186
     * Returns a new instance with specified locale code.
187
     *
188 2
     * @param string $locale The locale code.
189
     */
190 2
    public function withLocale(string $locale): self
191 2
    {
192 2
        $new = clone $this;
193
        $new->view = $this->view->withLocale($locale);
194
        return $new;
195
    }
196
197
    /**
198
     * Generates a TEXT body from an HTML body.
199
     *
200
     * @param string $html The HTML body.
201
     *
202 3
     * @return string The TEXT body.
203
     */
204 3
    private function generateTextBodyFromHtml(string $html): string
205 3
    {
206 3
        if (preg_match('~<body[^>]*>(.*?)</body>~is', $html, $match)) {
207
            $html = $match[1];
208
        }
209
        // remove style and script
210
        $html = preg_replace('~<((style|script))[^>]*>(.*?)</\1>~is', '', $html);
211
        // strip all HTML tags and decode HTML entities
212
        $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5);
213
        // improve whitespace
214
        $text = preg_replace("~^[ \t]+~m", '', trim($text));
215
        return preg_replace('~\R\R+~mu', "\n\n", $text);
216 5
    }
217
}
218