Test Failed
Pull Request — master (#24)
by Evgeniy
07:58
created

MessageBodyRenderer::generateTextBodyFromHtml()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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