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