Email::buildPlainTextMessage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace GeminiLabs\Castor\Services;
4
5
use GeminiLabs\Castor\Helpers\Template;
6
7
class Email
8
{
9
    /**
10
     * @var Template
11
     */
12
    public $template;
13
14
    /**
15
     * @var array
16
     */
17
    protected $attachments;
18
19
    /**
20
     * @var array
21
     */
22
    protected $headers;
23
24
    /**
25
     * @var string
26
     */
27
    protected $message;
28
29
    /**
30
     * @var string
31
     */
32
    protected $subject;
33
34
    /**
35
     * @var string
36
     */
37
    protected $to;
38
39
    public function __construct(Template $template)
40
    {
41
        $this->template = $template;
42
    }
43
44
    /**
45
     * @return Email
46
     */
47
    public function compose(array $email)
48
    {
49
        $email = $this->normalize($email);
50
51
        $this->attachments = $email['attachments'];
52
        $this->headers = $this->buildHeaders($email);
53
        $this->message = $this->buildHtmlMessage($email);
54
        $this->subject = $email['subject'];
55
        $this->to = $email['to'];
56
57
        add_action('phpmailer_init', function ($phpmailer) {
58
            if ('text/plain' === $phpmailer->ContentType || !empty($phpmailer->AltBody)) {
59
                return;
60
            }
61
            $phpmailer->AltBody = $this->buildPlainTextMessage($phpmailer->Body);
62
        });
63
64
        return $this;
65
    }
66
67
    /**
68
     * @param bool $plaintext
69
     *
70
     * @return string|null
71
     */
72
    public function read($plaintext = false)
73
    {
74
        return $plaintext
75
            ? $this->buildPlainTextMessage($this->message)
76
            : $this->message;
77
    }
78
79
    /**
80
     * @return bool|null
81
     */
82
    public function send()
83
    {
84
        if (!$this->message || !$this->subject || !$this->to) {
85
            return;
86
        }
87
88
        $sent = wp_mail(
89
            $this->to,
90
            $this->subject,
91
            $this->message,
92
            $this->headers,
93
            $this->attachments
94
        );
95
96
        $this->reset();
97
98
        return $sent;
99
    }
100
101
    /**
102
     * @return array
103
     */
104
    protected function buildHeaders(array $email)
105
    {
106
        $allowed = [
107
            'bcc',
108
            'cc',
109
            'from',
110
            'reply-to',
111
        ];
112
113
        $headers = array_intersect_key($email, array_flip($allowed));
114
        $headers = array_filter($headers);
115
116
        foreach ($headers as $key => $value) {
117
            unset($headers[$key]);
118
            $headers[] = sprintf('%s: %s', $key, $value);
119
        }
120
121
        $headers[] = 'Content-Type: text/html';
122
123
        return apply_filters('castor/email/headers', $headers, $this);
124
    }
125
126
    /**
127
     * @return string
128
     */
129
    protected function buildHtmlMessage(array $email)
130
    {
131
        $body = $this->renderTemplate('index');
132
        $message = !empty($email['template'])
133
            ? $this->renderTemplate($email['template'], $email['template-tags'])
134
            : $email['message'];
135
136
        $message = $this->filterHtml($email['before'].$message.$email['after']);
137
        $message = str_replace('{message}', $message, $body);
138
139
        return apply_filters('castor/email/message', $message, 'html', $this);
140
    }
141
142
    /**
143
     * @param string $message
144
     *
145
     * @return string
146
     */
147
    protected function buildPlainTextMessage($message)
148
    {
149
        return apply_filters('castor/email/message', $this->stripHtmlTags($message), 'text', $this);
150
    }
151
152
    /**
153
     * @param string $message
154
     *
155
     * @return string
156
     */
157
    protected function filterHtml($message)
158
    {
159
        $message = strip_shortcodes($message);
160
        $message = wptexturize($message);
161
        $message = wpautop($message);
162
        $message = str_replace(['&lt;&gt; ', ']]>'], ['', ']]&gt;'], $message);
163
        $message = stripslashes($message);
164
        return $message;
165
    }
166
167
    /**
168
     * @return string
169
     */
170
    protected function getDefaultFrom()
171
    {
172
        return sprintf('%s <%s>',
173
            wp_specialchars_decode((string) get_option('blogname'), ENT_QUOTES),
174
            (string) get_option('admin_email')
175
        );
176
    }
177
178
    /**
179
     * @return array
180
     */
181
    protected function normalize($email)
182
    {
183
        $defaults = array_fill_keys([
184
            'after', 'attachments', 'bcc', 'before', 'cc', 'from', 'message', 'reply-to', 'subject',
185
            'template', 'template-tags', 'to',
186
        ], '');
187
        $defaults['from'] = $this->getDefaultFrom();
188
189
        $email = shortcode_atts($defaults, $email);
190
191
        foreach (['attachments', 'template-tags'] as $key) {
192
            $email[$key] = array_filter((array) $email[$key]);
193
        }
194
        if (empty($email['reply-to'])) {
195
            $email['reply-to'] = $email['from'];
196
        }
197
198
        return apply_filters('castor/email/compose', $email, $this);
199
    }
200
201
    /**
202
     * Override by adding the custom template to "templates/castor/" in your theme.
203
     *
204
     * @param string $templatePath
205
     *
206
     * @return void|string
207
     */
208
    protected function renderTemplate($templatePath, array $args = [])
209
    {
210
        $file = $this->template->get('castor/email/'.$templatePath);
211
212
        if (!file_exists($file)) {
213
            $file = sprintf('%s/templates/email/%s.php', dirname(dirname(__DIR__)), $templatePath);
214
        }
215
        if (!file_exists($file)) {
216
            return;
217
        }
218
219
        ob_start();
220
        include $file;
221
        $template = ob_get_clean();
222
223
        return $this->renderTemplateString($template, $args);
224
    }
225
226
    /**
227
     * @param string $template
228
     *
229
     * @return string
230
     */
231
    protected function renderTemplateString($template, array $args = [])
232
    {
233
        foreach ($args as $key => $value) {
234
            $template = str_replace('{'.$key.'}', $value, $template);
235
        }
236
        return trim($template);
237
    }
238
239
    /**
240
     * @return void
241
     */
242
    protected function reset()
243
    {
244
        $this->attachments = [];
245
        $this->headers = [];
246
        $this->message = null;
247
        $this->subject = null;
248
        $this->to = null;
249
    }
250
251
    /**
252
     * - remove invisible elements
253
     * - replace certain elements with a line-break
254
     * - replace certain table elements with a space
255
     * - add a placeholder for plain-text bullets to list elements
256
     * - strip all remaining HTML tags.
257
     * @return string
258
     */
259
    protected function stripHtmlTags($string)
260
    {
261
        $string = preg_replace('@<(embed|head|noembed|noscript|object|script|style)[^>]*?>.*?</\\1>@siu', '', $string);
262
        $string = preg_replace('@</(div|h[1-9]|p|pre|tr)@iu', "\r\n\$0", $string);
263
        $string = preg_replace('@</(td|th)@iu', ' $0', $string);
264
        $string = preg_replace('@<(li)[^>]*?>@siu', '$0-o-^-o-', $string);
265
        $string = wp_strip_all_tags($string);
266
        $string = wp_specialchars_decode($string, ENT_QUOTES);
267
        $string = preg_replace('/\v(?:[\v\h]+){2,}/', "\r\n\r\n", $string);
268
        $string = str_replace('-o-^-o-', ' - ', $string);
269
        return html_entity_decode($string, ENT_QUOTES, 'UTF-8');
270
    }
271
}
272