Passed
Push — master ( 311548...93d467 )
by Evgeniy
02:02
created

MessageBodyRenderer::withView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 32
    public function __construct(View $view, MessageBodyTemplate $template)
42
    {
43 32
        $this->view = $view;
44 32
        $this->template = $template;
45 32
    }
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 12
    public function addToMessage(
70
        MessageInterface $message,
71
        $view,
72
        array $viewParameters = [],
73
        array $layoutParameters = []
74
    ): MessageInterface {
75 12
        if (is_string($view)) {
76 4
            $html = $this->renderHtml($view, $viewParameters, $layoutParameters);
77 4
            return $message->withHtmlBody($html)->withTextBody($this->generateTextBodyFromHtml($html));
78
        }
79
80 12
        if (!is_array($view) || (!isset($view['html']) && !isset($view['text']))) {
81 7
            throw new RuntimeException(
82 7
                'The "$view" parameter must be a string or array with at least one "text" or "html" key.',
83
            );
84
        }
85
86 5
        if (isset($view['html'])) {
87 4
            $html = $this->renderHtml($view['html'], $viewParameters, $layoutParameters);
88 4
            $message = $message->withHtmlBody($html);
89
        }
90
91 5
        if (isset($view['text'])) {
92 3
            $text = $this->renderText($view['text'], $viewParameters, $layoutParameters);
93 3
            $message = $message->withTextBody($text);
94
        }
95
96 5
        if (isset($html) && !isset($text)) {
97 2
            $message = $message->withTextBody($this->generateTextBodyFromHtml($html));
98
        }
99
100 5
        return $message;
101
    }
102
103
    /**
104
     * Renders the HTML view specified with optional parameters and layout.
105
     *
106
     * @param string $view The view name of the view file.
107
     * @param array $viewParameters The parameters (name-value pairs)
108
     * that will be extracted and available in the view file.
109
     * @param array $layoutParameters The parameters (name-value pairs)
110
     * that will be extracted and available in the layout file.
111
     *
112
     * @throws Throwable If an error occurred during rendering.
113
     *
114
     * @see View::render()
115
     *
116
     * @return string The rendering HTML result.
117
     */
118 6
    public function renderHtml(string $view, array $viewParameters = [], array $layoutParameters = []): string
119
    {
120 6
        $content = $this->view->render($view, $viewParameters, $this->template);
121
122 6
        if ($this->template->getHtmlLayout() === '') {
123 5
            return $content;
124
        }
125
126 1
        $layoutParameters['content'] = $content;
127 1
        return $this->view->render($this->template->getHtmlLayout(), $layoutParameters, $this->template);
128
    }
129
130
    /**
131
     * Renders the TEXT view specified with optional parameters and layout.
132
     *
133
     * @param string $view The view name of the view file.
134
     * @param array $viewParameters The parameters (name-value pairs)
135
     * that will be extracted and available in the view file.
136
     * @param array $layoutParameters The parameters (name-value pairs)
137
     * that will be extracted and available in the layout file.
138
     *
139
     * @throws Throwable If an error occurred during rendering.
140
     *
141
     * @see View::render()
142
     *
143
     * @return string The rendering TEXT result.
144
     */
145 5
    public function renderText(string $view, array $viewParameters = [], array $layoutParameters = []): string
146
    {
147 5
        $content = $this->view->render($view, $viewParameters, $this->template);
148
149 5
        if ($this->template->getTextLayout() === '') {
150 4
            return $content;
151
        }
152
153 1
        $layoutParameters['content'] = $content;
154 1
        return $this->view->render($this->template->getTextLayout(), $layoutParameters, $this->template);
155
    }
156
157
    /**
158
     * Returns a new instance with the specified view.
159
     *
160
     * @param View $view The view instance.
161
     *
162
     * @return self The new instance.
163
     */
164 1
    public function withView(View $view): self
165
    {
166 1
        $new = clone $this;
167 1
        $new->view = $view;
168 1
        return $new;
169
    }
170
171
    /**
172
     * Returns a new instance with the specified message body template.
173
     *
174
     * @param MessageBodyTemplate $template The message body template.
175
     *
176
     * @return self The new instance.
177
     */
178 2
    public function withTemplate(MessageBodyTemplate $template): self
179
    {
180 2
        $new = clone $this;
181 2
        $new->template = $template;
182 2
        return $new;
183
    }
184
185
    /**
186
     * Generates a TEXT body from an HTML body.
187
     *
188
     * @param string $html The HTML body.
189
     *
190
     * @return string The TEXT body.
191
     */
192 4
    private function generateTextBodyFromHtml(string $html): string
193
    {
194 4
        if (preg_match('~<body[^>]*>(.*?)</body>~is', $html, $match)) {
195 1
            $html = $match[1];
196
        }
197
        // remove style and script
198 4
        $html = preg_replace('~<((style|script))[^>]*>(.*?)</\1>~is', '', $html);
199
        // strip all HTML tags and decode HTML entities
200 4
        $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5);
201
        // improve whitespace
202 4
        $text = preg_replace("~^[ \t]+~m", '', trim($text));
203 4
        return preg_replace('~\R\R+~mu', "\n\n", $text);
204
    }
205
}
206