PluginProbe
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… / 2.11.4
Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… v2.11.4
2.11.11 2.11.10 2.11.9 2.11.7 2.11.8 2.11.6 2.11.5 2.11.4 2.11.3 2.11.1 2.11.2 2.11.0 2.10.5 2.10.4 2.10.3 2.10.2 2.10.1 2.10.0 2.9.9 2.9.8 2.9.6 2.9.7 2.9.5 2.9.4 2.9.3 All 86 releases
vigilante / includes / class-email-template.php

class-email-template.php in Vigilant – 100% Free Security Suite: Firewall, 2FA, Login, Headers, Scanner… 2.11.4, at includes/class-email-template.php

397 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email Template Helper
4 *
5 * Centralized HTML email builder for consistent styling
6 * across all plugin notifications.
7 *
8 * @package Vigilante
9 */
10
11 // Prevent direct access
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Class Vigilante_Email_Template
18 *
19 * Provides reusable HTML email components
20 */
21 class Vigilante_Email_Template {
22
23 /**
24 * Send an HTML email using the standard template
25 *
26 * Uses direct headers instead of wp_mail filters to prevent
27 * filter contamination between consecutive wp_mail() calls.
28 *
29 * @param string|array $to Recipient(s).
30 * @param string $subject Email subject.
31 * @param string $title Header title.
32 * @param string $body Body HTML (use helper methods to build).
33 * @param bool $alert Whether this is an alert (red header accent).
34 * @param string $from_name Optional custom From name.
35 * @return bool
36 */
37 public static function send( $to, $subject, $title, $body, $alert = false, $from_name = '' ) {
38 $html = self::wrap( $title, $body, $alert );
39 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
40
41 // Set From header directly (avoids wp_mail_from_name filter pollution)
42 if ( ! empty( $from_name ) ) {
43 // Use WordPress default from email (same logic as wp_mail core)
44 $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
45 if ( 'www.' === substr( $sitename, 0, 4 ) ) {
46 $sitename = substr( $sitename, 4 );
47 }
48 $from_email = 'wordpress@' . $sitename;
49
50 /**
51 * Filters the from email for Vigilante emails.
52 *
53 * @param string $from_email Default from email.
54 */
55 $from_email = apply_filters( 'vigilante_email_from', $from_email );
56
57 $headers[] = 'From: ' . sanitize_text_field( $from_name ) . ' <' . sanitize_email( $from_email ) . '>';
58 }
59
60 $result = wp_mail( $to, $subject, $html, $headers );
61
62 if ( ! $result && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
63 $recipient = is_array( $to ) ? implode( ', ', $to ) : $to;
64 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging
65 error_log( 'Vigilant email failed: to=' . $recipient . ' subject=' . $subject );
66 }
67
68 return $result;
69 }
70
71 /**
72 * Get centralized admin notification recipients
73 *
74 * Reads from the email settings section and builds the
75 * recipient list. All admin notifications should use this
76 * method instead of resolving recipients individually.
77 *
78 * @return array Array of valid email addresses.
79 */
80 public static function get_admin_recipients() {
81 $options = get_option( 'vigilante_options', array() );
82 $email_settings = isset( $options['email'] ) ? $options['email'] : array();
83
84 $recipients = array();
85
86 // Include WordPress admin email if enabled (default: true)
87 $send_to_admin = isset( $email_settings['send_to_admin_email'] )
88 ? (bool) $email_settings['send_to_admin_email']
89 : true;
90
91 if ( $send_to_admin ) {
92 $admin_email = get_option( 'admin_email' );
93 if ( ! empty( $admin_email ) && is_email( $admin_email ) ) {
94 $recipients[] = $admin_email;
95 }
96 }
97
98 // Parse additional recipients (supports array and legacy string format)
99 $additional_raw = isset( $email_settings['additional_recipients'] )
100 ? $email_settings['additional_recipients']
101 : array();
102
103 // Normalize to array
104 if ( is_string( $additional_raw ) ) {
105 // Legacy string format or corrupted data: split by newlines, commas, semicolons
106 $additional_list = preg_split( '/[\r\n,;]+/', trim( $additional_raw ) );
107 } else {
108 $additional_list = (array) $additional_raw;
109 }
110
111 foreach ( $additional_list as $line ) {
112 $email = sanitize_email( trim( $line ) );
113 if ( ! empty( $email ) && is_email( $email ) && ! in_array( $email, $recipients, true ) ) {
114 $recipients[] = $email;
115 }
116 }
117
118 // Fallback: only use admin email if settings were never configured
119 // (send_to_admin_email key doesn't exist at all, not just false)
120 if ( empty( $recipients ) && ! array_key_exists( 'send_to_admin_email', $email_settings ) ) {
121 $fallback = get_option( 'admin_email' );
122 if ( ! empty( $fallback ) && is_email( $fallback ) ) {
123 $recipients[] = $fallback;
124 }
125 }
126
127 /**
128 * Filters the admin notification recipients.
129 *
130 * Allows developers to modify the recipient list for
131 * all administrative email notifications.
132 *
133 * @param array $recipients Array of email addresses.
134 */
135 return apply_filters( 'vigilante_notification_recipients', $recipients );
136 }
137
138 /**
139 * Send a neutral test email to the configured recipients.
140 *
141 * Shared by every "Send test email" button (Notification settings, File
142 * Integrity, Audit Alerts) so they all verify the same delivery path.
143 *
144 * @return bool True if the email was handed to wp_mail, false otherwise.
145 */
146 public static function send_test() {
147 $recipients = self::get_admin_recipients();
148 if ( empty( $recipients ) ) {
149 return false;
150 }
151
152 $subject = sprintf(
153 /* translators: %s: site name */
154 __( '[Vigilant] Test email from %s', 'vigilante' ),
155 wp_specialchars_decode( get_bloginfo( 'name' ) )
156 );
157
158 $body = self::success_box(
159 __( 'This is a test email from Vigilant. If you can read this, your notification recipients are set correctly and email delivery works.', 'vigilante' )
160 );
161 $body .= self::data_table(
162 array(
163 __( 'Recipients', 'vigilante' ) => implode( ', ', $recipients ),
164 )
165 );
166
167 return self::send( $recipients, $subject, __( 'Test email', 'vigilante' ), $body, false );
168 }
169
170 /**
171 * Wrap body content in the standard email shell
172 *
173 * @param string $title Header title.
174 * @param string $body Body HTML.
175 * @param bool $alert Red accent header.
176 * @return string Full HTML email.
177 */
178 public static function wrap( $title, $body, $alert = false ) {
179 $site_name = get_bloginfo( 'name' );
180 $header_bg = $alert ? '#d63638' : '#1d2327';
181
182 $html = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>';
183 $html .= '<body style="margin:0;padding:0;background:#f0f0f1;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,Oxygen-Sans,Ubuntu,Cantarell,\'Helvetica Neue\',sans-serif;-webkit-text-size-adjust:100%;">';
184 $html .= '<table width="100%" cellpadding="0" cellspacing="0" style="background:#f0f0f1;padding:32px 0;">';
185 $html .= '<tr><td align="center">';
186 $html .= '<table width="560" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:6px;overflow:hidden;border:1px solid #c3c4c7;">';
187
188 // Header
189 $html .= '<tr><td style="background:' . esc_attr( $header_bg ) . ';padding:18px 28px;">';
190 $html .= '<table width="100%" cellpadding="0" cellspacing="0"><tr>';
191 $html .= '<td style="color:#ffffff;font-size:14px;font-weight:600;">' . esc_html__( 'Vigilant', 'vigilante' ) . '</td>';
192 $html .= '<td align="right" style="color:rgba(255,255,255,0.6);font-size:12px;">' . esc_html( $site_name ) . '</td>';
193 $html .= '</tr></table>';
194 $html .= '</td></tr>';
195
196 // Title
197 if ( ! empty( $title ) ) {
198 $html .= '<tr><td style="padding:24px 28px 0;">';
199 $html .= '<h1 style="margin:0;font-size:18px;font-weight:600;color:#1d2327;line-height:1.4;">' . esc_html( $title ) . '</h1>';
200 $html .= '</td></tr>';
201 }
202
203 // Body
204 $html .= '<tr><td style="padding:16px 28px 28px;">';
205 $html .= $body;
206 $html .= '</td></tr>';
207
208 // Footer
209 $html .= '<tr><td style="padding:16px 28px;background:#f6f7f7;border-top:1px solid #dcdcde;">';
210 $html .= '<p style="color:#787c82;font-size:11px;margin:0;text-align:center;line-height:1.5;">';
211 $html .= esc_html( $site_name ) . ' &mdash; ' . esc_url( home_url() );
212 $html .= '</p></td></tr>';
213
214 $html .= '</table></td></tr></table></body></html>';
215
216 return $html;
217 }
218
219 // =========================================================================
220 // Body content helpers
221 // =========================================================================
222
223 /**
224 * Paragraph
225 *
226 * @param string $text Text content (will be escaped).
227 * @param string $color Text color.
228 * @param bool $bold Whether to bold.
229 * @return string
230 */
231 public static function p( $text, $color = '#1d2327', $bold = false ) {
232 $weight = $bold ? 'font-weight:600;' : '';
233 return '<p style="color:' . esc_attr( $color ) . ';font-size:14px;line-height:1.6;margin:0 0 14px;' . $weight . '">' . esc_html( $text ) . '</p>';
234 }
235
236 /**
237 * Raw HTML paragraph (for content with links etc.)
238 *
239 * @param string $html HTML content (caller must escape).
240 * @param string $color Text color.
241 * @return string
242 */
243 public static function p_raw( $html, $color = '#1d2327' ) {
244 return '<p style="color:' . esc_attr( $color ) . ';font-size:14px;line-height:1.6;margin:0 0 14px;">' . $html . '</p>';
245 }
246
247 /**
248 * Small text paragraph
249 *
250 * @param string $text Text content.
251 * @return string
252 */
253 public static function small( $text ) {
254 return '<p style="color:#787c82;font-size:12px;line-height:1.5;margin:0 0 14px;">' . esc_html( $text ) . '</p>';
255 }
256
257 /**
258 * Data table (key-value pairs)
259 *
260 * @param array $rows Associative array of label => value.
261 * @return string
262 */
263 public static function data_table( $rows ) {
264 $html = '<table cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:0 0 16px;font-size:13px;">';
265 foreach ( $rows as $label => $value ) {
266 $html .= '<tr>';
267 $html .= '<td style="padding:6px 12px 6px 0;color:#787c82;white-space:nowrap;vertical-align:top;">' . esc_html( $label ) . '</td>';
268 $html .= '<td style="padding:6px 0;color:#1d2327;word-break:break-all;">' . esc_html( $value ) . '</td>';
269 $html .= '</tr>';
270 }
271 $html .= '</table>';
272 return $html;
273 }
274
275 /**
276 * Highlighted info box (blue left border)
277 *
278 * @param string $text Text content.
279 * @return string
280 */
281 public static function info_box( $text ) {
282 return '<div style="background:#f0f6fc;border-left:4px solid #2271b1;border-radius:0 4px 4px 0;padding:12px 16px;margin:0 0 16px;">'
283 . '<p style="color:#1d2327;font-size:13px;line-height:1.5;margin:0;">' . esc_html( $text ) . '</p></div>';
284 }
285
286 /**
287 * Warning box (orange left border)
288 *
289 * @param string $text Text content.
290 * @return string
291 */
292 public static function warning_box( $text ) {
293 return '<div style="background:#fdf8e8;border-left:4px solid #dba617;border-radius:0 4px 4px 0;padding:12px 16px;margin:0 0 16px;">'
294 . '<p style="color:#1d2327;font-size:13px;line-height:1.5;margin:0;">' . esc_html( $text ) . '</p></div>';
295 }
296
297 /**
298 * Alert/error box (red left border)
299 *
300 * @param string $text Text content.
301 * @return string
302 */
303 public static function alert_box( $text ) {
304 return '<div style="background:#fcf0f1;border-left:4px solid #d63638;border-radius:0 4px 4px 0;padding:12px 16px;margin:0 0 16px;">'
305 . '<p style="color:#1d2327;font-size:13px;line-height:1.5;margin:0;font-weight:500;">' . esc_html( $text ) . '</p></div>';
306 }
307
308 /**
309 * Success box (green left border)
310 *
311 * @param string $text Text content.
312 * @return string
313 */
314 public static function success_box( $text ) {
315 return '<div style="background:#edfaef;border-left:4px solid #00a32a;border-radius:0 4px 4px 0;padding:12px 16px;margin:0 0 16px;">'
316 . '<p style="color:#1d2327;font-size:13px;line-height:1.5;margin:0;">' . esc_html( $text ) . '</p></div>';
317 }
318
319 /**
320 * Highlighted code/value display (e.g., verification codes)
321 *
322 * @param string $code Code or value to display.
323 * @param string $label Optional label above the code.
324 * @return string
325 */
326 public static function code_box( $code, $label = '' ) {
327 $html = '<div style="text-align:center;margin:0 0 16px;">';
328 if ( ! empty( $label ) ) {
329 $html .= '<p style="color:#787c82;font-size:13px;margin:0 0 8px;">' . esc_html( $label ) . '</p>';
330 }
331 $html .= '<div style="background:#f0f6fc;border:2px solid #2271b1;border-radius:8px;padding:14px 24px;display:inline-block;">';
332 $html .= '<span style="font-size:28px;font-weight:700;letter-spacing:6px;font-family:Consolas,Monaco,monospace;color:#1d2327;">';
333 $html .= esc_html( $code );
334 $html .= '</span></div></div>';
335 return $html;
336 }
337
338 /**
339 * URL display box (for login URLs, links, etc.)
340 *
341 * Unlike code_box, uses normal font size and word-break
342 * to handle long URLs without overflowing.
343 *
344 * @param string $url URL to display.
345 * @param string $label Optional label above the URL.
346 * @return string
347 */
348 public static function url_box( $url, $label = '' ) {
349 $html = '<div style="text-align:center;margin:0 0 16px;">';
350 if ( ! empty( $label ) ) {
351 $html .= '<p style="color:#787c82;font-size:13px;margin:0 0 8px;">' . esc_html( $label ) . '</p>';
352 }
353 $html .= '<div style="background:#f0f6fc;border:2px solid #2271b1;border-radius:8px;padding:14px 20px;">';
354 $html .= '<a href="' . esc_url( $url ) . '" style="font-size:16px;font-weight:600;font-family:Consolas,Monaco,monospace;color:#2271b1;text-decoration:none;word-break:break-all;">';
355 $html .= esc_html( $url );
356 $html .= '</a></div></div>';
357 return $html;
358 }
359
360 /**
361 * CTA button
362 *
363 * @param string $url Button URL.
364 * @param string $text Button text.
365 * @param string $bg Background color.
366 * @return string
367 */
368 public static function button( $url, $text, $bg = '#2271b1' ) {
369 return '<p style="text-align:center;margin:20px 0 16px;">'
370 . '<a href="' . esc_url( $url ) . '" style="display:inline-block;background:' . esc_attr( $bg ) . ';color:#ffffff;text-decoration:none;padding:10px 28px;border-radius:4px;font-size:14px;font-weight:600;">'
371 . esc_html( $text ) . '</a></p>';
372 }
373
374 /**
375 * Simple unordered list
376 *
377 * @param array $items List items (HTML allowed, caller must escape).
378 * @return string
379 */
380 public static function ul( $items ) {
381 $html = '<ul style="color:#1d2327;font-size:13px;line-height:1.6;margin:0 0 16px;padding-left:20px;">';
382 foreach ( $items as $item ) {
383 $html .= '<li style="margin-bottom:4px;">' . wp_kses( $item, array( 'a' => array( 'href' => array() ), 'strong' => array(), 'code' => array() ) ) . '</li>';
384 }
385 $html .= '</ul>';
386 return $html;
387 }
388
389 /**
390 * Horizontal divider
391 *
392 * @return string
393 */
394 public static function hr() {
395 return '<hr style="border:none;border-top:1px solid #dcdcde;margin:20px 0;">';
396 }
397 }