Test Failed
Push — develop ( 67a476...bfa7dc )
by Paul
15:31
created

Email::test()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1.0005

Importance

Changes 0
Metric Value
eloc 11
c 0
b 0
f 0
dl 0
loc 14
ccs 11
cts 12
cp 0.9167
rs 9.9
cc 1
nc 1
nop 0
crap 1.0005
1
<?php
2
3
namespace GeminiLabs\SiteReviews\Modules;
4
5
use GeminiLabs\SiteReviews\Arguments;
6
use GeminiLabs\SiteReviews\Contracts\EmailContract;
7
use GeminiLabs\SiteReviews\Contracts\PluginContract;
8
use GeminiLabs\SiteReviews\Contracts\TemplateContract;
9
use GeminiLabs\SiteReviews\Database\OptionManager;
10
use GeminiLabs\SiteReviews\Defaults\DefaultsAbstract;
11
use GeminiLabs\SiteReviews\Defaults\EmailDefaults;
12
use GeminiLabs\SiteReviews\Helpers\Str;
13
use GeminiLabs\SiteReviews\Modules\Html\Template;
14
15
class Email implements EmailContract
16
{
17
    public array $attachments = [];
18
    public array $data = [];
19
    public array $email = [];
20
    public array $headers = [];
21
    public string $message = '';
22
    public array $recipients = [];
23
    public string $subject = '';
24
25
    public function app(): PluginContract
26
    {
27
        return glsr();
28
    }
29
30
    public function compose(array $email, array $data = []): EmailContract
31
    {
32
        $this->data = $data;
33
        $this->email = $this->normalize($email);
34
        $this->attachments = $this->email['attachments'];
35
        $this->headers = $this->buildHeaders();
36
        $this->message = $this->buildHtmlMessage();
37
        $this->recipients = $this->email['recipients'];
38 1
        $this->subject = $this->email['subject'];
39
        add_action('phpmailer_init', [$this, 'buildPlainTextMessage']);
40 1
        return $this;
41
    }
42
43 1
    public function data(): Arguments
44
    {
45 1
        return glsr()->args($this->data);
46 1
    }
47 1
48 1
    public function defaults(): DefaultsAbstract
49 1
    {
50 1
        return glsr(EmailDefaults::class);
51 1
    }
52 1
53 1
    public function logMailError(\WP_Error $error): void
54
    {
55
        glsr_log()
56
            ->error('[wp_mail] Email was not sent: '.$error->get_error_message())
57
            ->debug([
58
                'headers' => $this->headers,
59
                'attachments' => $this->attachments,
60
                'email' => $this->email,
61 1
            ]);
62
    }
63 1
64
    public function read(string $format = ''): string
65
    {
66
        if ('plaintext' === $format) {
67
            $message = $this->stripHtmlTags($this->message);
68
            return $this->app()->filterString('email/message', $message, 'text', $this);
69
        }
70
        return $this->message;
71
    }
72
73
    public function send(): bool
74
    {
75
        if (!$this->validate()) {
76
            glsr_log()->warning(sprintf('The email is missing the %s', Str::naturalJoin($missing)));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $missing seems to be never defined.
Loading history...
77
            return false;
78
        }
79
        add_action('wp_mail_failed', [$this, 'logMailError']);
80
        $sent = wp_mail(
81
            $this->recipients,
82 1
            $this->subject,
83
            $this->message,
84 1
            $this->headers,
85 1
            $this->attachments
86 1
        );
87 1
        remove_action('wp_mail_failed', [$this, 'logMailError']);
88 1
        $this->reset();
89 1
        return $sent;
90 1
    }
91
92
    public function test(): bool
93
    {
94 1
        $recipient = get_bloginfo('admin_email');
95 1
        $subject = "[TEST EMAIL] {$this->subject}";
96 1
        add_action('wp_mail_failed', [$this, 'logMailError']);
97 1
        $sent = wp_mail(
98 1
            $recipient,
99 1
            $subject,
100 1
            $this->message,
101 1
            $this->headers,
102 1
            $this->attachments
103 1
        );
104 1
        remove_action('wp_mail_failed', [$this, 'logMailError']);
105
        return $sent;
106
    }
107 1
108
    public function template(): TemplateContract
109 1
    {
110
        return glsr(Template::class);
111
    }
112
113
    /**
114
     * @action phpmailer_init
115 1
     */
116
    public function buildPlainTextMessage($phpmailer): void
117 1
    {
118
        if (empty($this->email)) {
119
            return;
120 1
        }
121
        if ('text/plain' === $phpmailer->ContentType || !empty($phpmailer->AltBody)) {
122
            return;
123 1
        }
124 1
        $message = $this->stripHtmlTags($phpmailer->Body);
125
        $phpmailer->AltBody = $this->app()->filterString('email/message', $message, 'text', $this);
126
    }
127 1
128
    protected function buildHeaders(): array
129 1
    {
130 1
        $allowed = [
131 1
            'bcc', 'cc', 'from', 'reply-to',
132 1
        ];
133 1
        $headers = array_intersect_key($this->email, array_flip($allowed));
134 1
        $headers = array_filter($headers);
135 1
        foreach ($headers as $key => $value) {
136 1
            unset($headers[$key]);
137
            $headers[] = "{$key}: {$value}";
138 1
        }
139 1
        $headers[] = 'Content-Type: text/html';
140
        return $this->app()->filterArray('email/headers', $headers, $this);
141
    }
142 1
143
    protected function buildHtmlMessage(): string
144 1
    {
145 1
        $message = $this->buildMessage();
146 1
        $message = $this->email['before'].$message.$this->email['after'];
147 1
        $message = strip_shortcodes($message);
148 1
        $message = wptexturize($message);
149 1
        $message = wpautop($message);
150 1
        $message = str_replace('&lt;&gt; ', '', $message);
151 1
        $message = str_replace(']]>', ']]&gt;', $message);
152 1
        $context = wp_parse_args(['message' => $message], $this->email['template-tags']);
153 1
        $template = $this->email['template'];
154 1
        $message = $this->template()->build("templates/emails/{$template}", [
155 1
            'context' => $context,
156 1
        ]);
157
        return $this->app()->filterString('email/message', stripslashes($message), 'html', $this);
158
    }
159 1
160
    protected function buildMessage(): string
161 1
    {
162
        if (!empty($this->email['message'])) {
163
            return $this->email['message'];
164 1
        }
165 1
        $template = trim(glsr(OptionManager::class)->get('settings.general.notification_message'));
166 1
        if (!empty($template)) {
167 1
            $context = ['context' => $this->email['template-tags']];
168 1
            $templatePathForHook = 'notification_message';
169
            return $this->template()->interpolate($template, $templatePathForHook, $context);
170
        }
171
        return '';
172
    }
173 1
174
    protected function normalize(array $email = []): array
175 1
    {
176 1
        $email = $this->defaults()->restrict($email);
177
        $email = $this->app()->filterArray('email/compose', $email, $this);
178
        return $email;
179 1
    }
180
181 1
    protected function reset(): void
182 1
    {
183 1
        $this->attachments = [];
184 1
        $this->data = [];
185 1
        $this->email = [];
186 1
        $this->headers = [];
187 1
        $this->message = '';
188
        $this->recipients = [];
189
        $this->subject = '';
190 1
    }
191
192
    protected function stripHtmlTags($string): string
193 1
    {
194
        // remove invisible elements
195 1
        $string = preg_replace('@<(embed|head|noembed|noscript|object|script|style)[^>]*?>.*?</\\1>@siu', '', $string);
196 1
        // replace link elements
197 1
        $string = preg_replace_callback('@<a[^>]*href=("|\')(.*?)\1[^>]*>(.*?)<\/a>@iu', function ($matches) {
198 1
            $matches = array_map('trim', $matches);
199 1
            return ($matches[2] !== $matches[3])
200 1
                ? sprintf('%s (%s)', $matches[3], $matches[2])
201
                : $matches[2];
202 1
        }, $string);
203
        // replace certain elements with a line-break
204 1
        $string = preg_replace('@</(div|h[1-9]|p|pre|tr)@iu', "\r\n\$0", $string);
205
        // replace other elements with a space
206 1
        $string = preg_replace('@</(td|th)@iu', ' $0', $string);
207
        // add a placeholder for plain-text bullets to list elements
208 1
        $string = preg_replace('@<(li)[^>]*?>@siu', '$0-o-^-o-', $string);
209 1
        // strip all remaining HTML tags
210 1
        $string = wp_strip_all_tags($string);
211 1
        $string = wp_specialchars_decode($string, ENT_QUOTES);
212 1
        $string = preg_replace('/\v(?:[\v\h]+){2,}/u', "\r\n\r\n", $string);
213
        $string = str_replace('-o-^-o-', ' - ', $string);
214
        return html_entity_decode($string, ENT_QUOTES, 'UTF-8');
215
    }
216
217
    protected function validate(): bool
218
    {
219
        $required = [
220
            'message' => !empty($this->message),
221
            'recipient' => !empty($this->recipients),
222
            'subject' => !empty($this->subject),
223
        ];
224
        $missing = array_keys(array_diff($required, array_filter($required)));
225
        if (empty($missing)) {
226
            return true;
227
        }
228
        return false;
229
    }
230
}
231