Passed
Pull Request — master (#24)
by Alexander
02:20
created

MessageBodyRenderer::addToMessage()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 9

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 15
c 1
b 0
f 0
nc 10
nop 3
dl 0
loc 28
ccs 16
cts 16
cp 1
crap 9
rs 8.0555
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
use Yiisoft\View\ViewContextInterface;
11
12
use function html_entity_decode;
13
use function is_array;
14
use function is_string;
15
use function preg_match;
16
use function preg_replace;
17
use function strip_tags;
18
use function trim;
19
20
use const ENT_HTML5;
21
use const ENT_QUOTES;
22
23
/**
24
 * View renderer used to compose message body.
25
 */
26
final class MessageBodyRenderer implements ViewContextInterface
27
{
28
    /**
29
     * @var View The view instance.
30
     */
31
    private View $view;
32
33
    /**
34
     * @var string The directory containing view files for composing mail messages.
35
     */
36
    private string $viewPath;
37
38
    /**
39
     * @var string The HTML layout view name.
40
     *
41
     * It is the layout used to render HTML mail body. If the value is empty string, no layout will be applied.
42
     *
43
     * The property can take the following values:
44
     *
45
     * - a relative view name: a view file relative to {@see MessageBodyRenderer::$viewPath}, e.g., 'layouts/html'.
46
     * - an empty string: the layout is disabled.
47
     */
48
    private string $htmlLayout;
49
50
    /**
51
     * @var string The TEXT layout view name.
52
     *
53
     * This is the layout used to render TEXT mail body. If the value is empty string, no layout will be applied.
54
     *
55
     * The property can take the following values:
56
     *
57
     * - a relative view name: a view file relative to {@see MessageBodyRenderer::$viewPath}, e.g., 'layouts/text'.
58
     * - an empty string: the layout is disabled.
59
     */
60
    private string $textLayout;
61
62
    /**
63
     * @param View $view The view instance.
64
     * @param string $viewPath The directory containing view files for composing mail messages.
65
     * @param string $htmlLayout The HTML layout view name. It is the layout used to render HTML mail body.
66
     * @param string $textLayout The TEXT layout view name. This is the layout used to render TEXT mail body.
67
     */
68 31
    public function __construct(
69
        View $view,
70
        string $viewPath,
71
        string $htmlLayout = 'layouts/html',
72
        string $textLayout = 'layouts/text'
73
    ) {
74 31
        $this->view = $view;
75 31
        $this->viewPath = $viewPath;
76 31
        $this->htmlLayout = $htmlLayout;
77 31
        $this->textLayout = $textLayout;
78 31
    }
79
80 9
    public function getViewPath(): string
81
    {
82 9
        return $this->viewPath;
83
    }
84
85
    /**
86
     * Adds the rendered body to the message and returns it.
87
     *
88
     * @param MessageInterface $message The message to which the body will be added.
89
     * @param mixed $view The view to be used for rendering the message body.
90
     * This can be:
91
     * - a string, which represents the view name for rendering the HTML body of the email.
92
     *   In this case, the text body will be generated by applying `strip_tags()` to the HTML body.
93
     * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name
94
     *   for rendering the HTML body, while 'text' element is for rendering the text body.
95
     *   For example, `['html' => 'contact-html', 'text' => 'contact-text']`.
96
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
97
     *
98
     * @throws Throwable If an error occurred during rendering.
99
     *
100
     * @return MessageInterface The message with the added body.
101
     *
102
     * @psalm-suppress MixedArgument
103
     */
104 11
    public function addToMessage(MessageInterface $message, $view, array $parameters = []): MessageInterface
105
    {
106 11
        if (is_string($view)) {
107 4
            $html = $this->renderHtml($view, $parameters);
108 4
            return $message->withHtmlBody($html)->withTextBody($this->generateTextBodyFromHtml($html));
109
        }
110
111 11
        if (!is_array($view) || (!isset($view['html']) && !isset($view['text']))) {
112 7
            throw new RuntimeException(
113 7
                'The "$view" parameter must be a string or array with at least one "txt" or "html" key.',
114
            );
115
        }
116
117 4
        if (isset($view['html'])) {
118 4
            $html = $this->renderHtml($view['html'], $parameters);
119 4
            $message = $message->withHtmlBody($html);
120
        }
121
122 4
        if (isset($view['text'])) {
123 2
            $text = $this->renderText($view['text'], $parameters);
124 2
            $message = $message->withTextBody($text);
125
        }
126
127 4
        if (isset($html) && !isset($text)) {
128 2
            $message = $message->withTextBody($this->generateTextBodyFromHtml($html));
129
        }
130
131 4
        return $message;
132
    }
133
134
    /**
135
     * Renders the HTML view specified with optional parameters and layout.
136
     *
137
     * @param string $view The view name of the view file.
138
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
139
     *
140
     * @throws Throwable If an error occurred during rendering.
141
     *
142
     * @see View::render()
143
     *
144
     * @return string The rendering HTML result.
145
     */
146 6
    public function renderHtml(string $view, array $parameters = []): string
147
    {
148 6
        $content = $this->view->render($view, $parameters, $this);
149
150 6
        if ($this->htmlLayout === '') {
151 5
            return $content;
152
        }
153
154 1
        return $this->view->render($this->htmlLayout, ['content' => $content], $this);
155
    }
156
157
    /**
158
     * Renders the TEXT view specified with optional parameters and layout.
159
     *
160
     * @param string $view The view name of the view file.
161
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
162
     *
163
     * @throws Throwable If an error occurred during rendering.
164
     *
165
     * @see View::render()
166
     *
167
     * @return string The rendering TEXT result.
168
     */
169 4
    public function renderText(string $view, array $parameters = []): string
170
    {
171 4
        $content = $this->view->render($view, $parameters, $this);
172
173 4
        if ($this->textLayout === '') {
174 3
            return $content;
175
        }
176
177 1
        return $this->view->render($this->textLayout, ['content' => $content], $this);
178
    }
179
180
    /**
181
     * Generates a TEXT body from an HTML body.
182
     *
183
     * @param string $html The HTML body.
184
     *
185
     * @return string The TEXT body.
186
     */
187 4
    private function generateTextBodyFromHtml(string $html): string
188
    {
189 4
        if (preg_match('~<body[^>]*>(.*?)</body>~is', $html, $match)) {
190 1
            $html = $match[1];
191
        }
192
        // remove style and script
193 4
        $html = preg_replace('~<((style|script))[^>]*>(.*?)</\1>~is', '', $html);
194
        // strip all HTML tags and decode HTML entities
195 4
        $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5);
196
        // improve whitespace
197 4
        $text = preg_replace("~^[ \t]+~m", '', trim($text));
198 4
        return preg_replace('~\R\R+~mu', "\n\n", $text);
199
    }
200
}
201