Passed
Push — master ( a117dd...852f77 )
by
unknown
02:15
created

MessageBodyRenderer   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 203
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

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

8 Methods

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