'; } $result = wp_mail( $to, $subject, $html, $headers ); if ( ! $result && defined( 'WP_DEBUG' ) && WP_DEBUG ) { $recipient = is_array( $to ) ? implode( ', ', $to ) : $to; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug logging error_log( 'Vigilant email failed: to=' . $recipient . ' subject=' . $subject ); } return $result; } /** * Get centralized admin notification recipients * * Reads from the email settings section and builds the * recipient list. All admin notifications should use this * method instead of resolving recipients individually. * * @return array Array of valid email addresses. */ public static function get_admin_recipients() { $options = get_option( 'vigilante_options', array() ); $email_settings = isset( $options['email'] ) ? $options['email'] : array(); $recipients = array(); // Include WordPress admin email if enabled (default: true) $send_to_admin = isset( $email_settings['send_to_admin_email'] ) ? (bool) $email_settings['send_to_admin_email'] : true; if ( $send_to_admin ) { $admin_email = get_option( 'admin_email' ); if ( ! empty( $admin_email ) && is_email( $admin_email ) ) { $recipients[] = $admin_email; } } // Parse additional recipients (supports array and legacy string format) $additional_raw = isset( $email_settings['additional_recipients'] ) ? $email_settings['additional_recipients'] : array(); // Normalize to array if ( is_string( $additional_raw ) ) { // Legacy string format or corrupted data: split by newlines, commas, semicolons $additional_list = preg_split( '/[\r\n,;]+/', trim( $additional_raw ) ); } else { $additional_list = (array) $additional_raw; } foreach ( $additional_list as $line ) { $email = sanitize_email( trim( $line ) ); if ( ! empty( $email ) && is_email( $email ) && ! in_array( $email, $recipients, true ) ) { $recipients[] = $email; } } // Fallback: only use admin email if settings were never configured // (send_to_admin_email key doesn't exist at all, not just false) if ( empty( $recipients ) && ! array_key_exists( 'send_to_admin_email', $email_settings ) ) { $fallback = get_option( 'admin_email' ); if ( ! empty( $fallback ) && is_email( $fallback ) ) { $recipients[] = $fallback; } } /** * Filters the admin notification recipients. * * Allows developers to modify the recipient list for * all administrative email notifications. * * @param array $recipients Array of email addresses. */ return apply_filters( 'vigilante_notification_recipients', $recipients ); } /** * Send a neutral test email to the configured recipients. * * Shared by every "Send test email" button (Notification settings, File * Integrity, Audit Alerts) so they all verify the same delivery path. * * @return bool True if the email was handed to wp_mail, false otherwise. */ public static function send_test() { $recipients = self::get_admin_recipients(); if ( empty( $recipients ) ) { return false; } $subject = sprintf( /* translators: %s: site name */ __( '[Vigilant] Test email from %s', 'vigilante' ), wp_specialchars_decode( get_bloginfo( 'name' ) ) ); $body = self::success_box( __( 'This is a test email from Vigilant. If you can read this, your notification recipients are set correctly and email delivery works.', 'vigilante' ) ); $body .= self::data_table( array( __( 'Recipients', 'vigilante' ) => implode( ', ', $recipients ), ) ); return self::send( $recipients, $subject, __( 'Test email', 'vigilante' ), $body, false ); } /** * Wrap body content in the standard email shell * * @param string $title Header title. * @param string $body Body HTML. * @param bool $alert Red accent header. * @return string Full HTML email. */ public static function wrap( $title, $body, $alert = false ) { $site_name = get_bloginfo( 'name' ); $header_bg = $alert ? '#d63638' : '#1d2327'; $html = '
'; $html .= ''; $html .= '| '; $html .= ' |
' . esc_html( $text ) . '
'; } /** * Raw HTML paragraph (for content with links etc.) * * @param string $html HTML content (caller must escape). * @param string $color Text color. * @return string */ public static function p_raw( $html, $color = '#1d2327' ) { return '' . $html . '
'; } /** * Small text paragraph * * @param string $text Text content. * @return string */ public static function small( $text ) { return '' . esc_html( $text ) . '
'; } /** * Data table (key-value pairs) * * @param array $rows Associative array of label => value. * @return string */ public static function data_table( $rows ) { $html = '| ' . esc_html( $label ) . ' | '; $html .= '' . esc_html( $value ) . ' | '; $html .= '
' . esc_html( $text ) . '
' . esc_html( $text ) . '
' . esc_html( $text ) . '
' . esc_html( $text ) . '
' . esc_html( $label ) . '
'; } $html .= '' . esc_html( $label ) . '
'; } $html .= '' . '' . esc_html( $text ) . '
'; } /** * Simple unordered list * * @param array $items List items (HTML allowed, caller must escape). * @return string */ public static function ul( $items ) { $html = '