Passed
Pull Request — master (#24)
by Evgeniy
02:08
created

MessageBodyRenderer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 4
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 1
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 32
    public function __construct(
63
        View $view,
64
        string $viewPath,
65
        string $htmlLayout = 'layouts/html',
66
        string $textLayout = 'layouts/text'
67
    ) {
68 32
        $this->view = $view;
69 32
        $this->viewPath = $viewPath;
70 32
        $this->htmlLayout = $htmlLayout;
71 32
        $this->textLayout = $textLayout;
72 32
    }
73
74 9
    public function getViewPath(): string
75
    {
76 9
        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 mixed $view The view to be used for rendering the message body.
84
     * This can be:
85
     * - a string, which represents the view name for rendering the HTML body of the email.
86
     *   In this case, the text body will be generated by applying `strip_tags()` to the HTML body.
87
     * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name
88
     *   for rendering the HTML body, while 'text' element is for rendering the text body.
89
     *   For example, `['html' => 'contact-html', 'text' => 'contact-text']`.
90
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
91
     *
92
     * @throws Throwable If an error occurred during rendering.
93
     *
94
     * @return MessageInterface The message with the added body.
95
     *
96
     * @psalm-suppress MixedArgument
97
     */
98 11
    public function addToMessage(MessageInterface $message, $view, array $parameters = []): MessageInterface
99
    {
100 11
        if (is_string($view)) {
101 4
            $html = $this->renderHtml($view, $parameters);
102 4
            return $message->withHtmlBody($html)->withTextBody($this->generateTextBodyFromHtml($html));
103
        }
104
105 11
        if (!is_array($view) || (!isset($view['html']) && !isset($view['text']))) {
106 7
            throw new RuntimeException(
107 7
                'The "$view" parameter must be a string or array with the "text" and "html" keys.',
108
            );
109
        }
110
111 4
        if (isset($view['html'])) {
112 4
            $html = $this->renderHtml($view['html'], $parameters);
113 4
            $message = $message->withHtmlBody($html);
114
        }
115
116 4
        if (isset($view['text'])) {
117 2
            $text = $this->renderText($view['text'], $parameters);
118 2
            $message = $message->withTextBody($text);
119
        }
120
121 4
        if (isset($html) && !isset($text)) {
122 2
            $message = $message->withTextBody($this->generateTextBodyFromHtml($html));
123
        }
124
125 4
        return $message;
126
    }
127
128
    /**
129
     * Renders the HTML view specified with optional parameters and layout.
130
     *
131
     * @param string $view The view name of the view file.
132
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
133
     *
134
     * @throws Throwable If an error occurred during rendering.
135
     *
136
     * @see View::render()
137
     *
138
     * @return string The rendering HTML result.
139
     */
140 6
    public function renderHtml(string $view, array $parameters = []): string
141
    {
142 6
        $content = $this->view->render($view, $parameters, $this);
143
144 6
        if ($this->htmlLayout === '') {
145 5
            return $content;
146
        }
147
148 1
        return $this->view->render($this->htmlLayout, ['content' => $content], $this);
149
    }
150
151
    /**
152
     * Renders the TEXT view specified with optional parameters and layout.
153
     *
154
     * @param string $view The view name of the view file.
155
     * @param array $parameters The parameters (name-value pairs) that will be extracted and available in the view file.
156
     *
157
     * @throws Throwable If an error occurred during rendering.
158
     *
159
     * @see View::render()
160
     *
161
     * @return string The rendering TEXT result.
162
     */
163 4
    public function renderText(string $view, array $parameters = []): string
164
    {
165 4
        $content = $this->view->render($view, $parameters, $this);
166
167 4
        if ($this->textLayout === '') {
168 3
            return $content;
169
        }
170
171 1
        return $this->view->render($this->textLayout, ['content' => $content], $this);
172
    }
173
174
    /**
175
     * Generates a TEXT body from an HTML body.
176
     *
177
     * @param string $html The HTML body.
178
     *
179
     * @return string The TEXT body.
180
     */
181 4
    private function generateTextBodyFromHtml(string $html): string
182
    {
183 4
        if (preg_match('~<body[^>]*>(.*?)</body>~is', $html, $match)) {
184 1
            $html = $match[1];
185
        }
186
        // remove style and script
187 4
        $html = preg_replace('~<((style|script))[^>]*>(.*?)</\1>~is', '', $html);
188
        // strip all HTML tags and decode HTML entities
189 4
        $text = html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5);
190
        // improve whitespace
191 4
        $text = preg_replace("~^[ \t]+~m", '', trim($text));
192 4
        return preg_replace('~\R\R+~mu', "\n\n", $text);
193
    }
194
}
195