773 lines
22 KiB
PHP
773 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Email form action.
|
|
*
|
|
* Sends form submissions via wp_mail(). Never uses user data in the From email
|
|
* address; From name may use sanitized merge tags.
|
|
*
|
|
* @package GenerateBlocksPro
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Email form action class.
|
|
*/
|
|
class GenerateBlocks_Pro_Form_Action_Email {
|
|
|
|
/**
|
|
* Hard cap on recipient count per submission. emailTo is admin-controlled
|
|
* (manage_options to write form meta), so submitters can't reach this. The
|
|
* cap protects against misconfiguration — pasting a giant CSV — amplifying
|
|
* outbound mail per submission.
|
|
*/
|
|
const MAX_RECIPIENTS = 10;
|
|
|
|
/**
|
|
* Stored value that explicitly disables the Reply-To header.
|
|
*/
|
|
const REPLY_TO_NONE = '__none';
|
|
|
|
/**
|
|
* Register the email action with the registry.
|
|
*/
|
|
public static function init() {
|
|
GenerateBlocks_Pro_Form_Action_Registry::register( 'email', [ __CLASS__, 'send' ] );
|
|
}
|
|
|
|
/**
|
|
* Validate the email action settings before delivery starts.
|
|
*
|
|
* @param array $form_settings The form block settings.
|
|
* @return true|WP_Error
|
|
*/
|
|
public static function validate_settings( $form_settings ) {
|
|
$to = self::parse_recipients(
|
|
! empty( $form_settings['emailTo'] ) ? $form_settings['emailTo'] : get_option( 'admin_email' )
|
|
);
|
|
|
|
if ( empty( $to ) ) {
|
|
return new WP_Error( 'invalid_recipient', 'Invalid email recipient.' );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Send the form submission via email.
|
|
*
|
|
* @param array $sanitized_data The sanitized form data (field_name => ['value' => ..., 'label' => ..., 'type' => ...]).
|
|
* @param array $form_settings The form block settings.
|
|
* @param array $context Request context (post_id, page_url).
|
|
* @return true|WP_Error True on success, WP_Error on failure.
|
|
*/
|
|
public static function send( $sanitized_data, $form_settings, $context = [] ) {
|
|
$validation = self::validate_settings( $form_settings );
|
|
|
|
if ( is_wp_error( $validation ) ) {
|
|
return $validation;
|
|
}
|
|
|
|
// Determine recipients — supports comma-separated addresses.
|
|
$to = self::parse_recipients(
|
|
! empty( $form_settings['emailTo'] ) ? $form_settings['emailTo'] : get_option( 'admin_email' )
|
|
);
|
|
|
|
// Build subject with site name for context.
|
|
$site_name = get_bloginfo( 'name' );
|
|
|
|
if (
|
|
isset( $form_settings['emailSubject'] )
|
|
&& is_scalar( $form_settings['emailSubject'] )
|
|
&& '' !== (string) $form_settings['emailSubject']
|
|
) {
|
|
$subject = (string) $form_settings['emailSubject'];
|
|
} else {
|
|
$subject = sprintf(
|
|
/* translators: %s: site name */
|
|
__( '[%s] New Form Submission', 'generateblocks-pro' ),
|
|
$site_name
|
|
);
|
|
}
|
|
|
|
$subject = GenerateBlocks_Pro_Form_Sanitizer::sanitize_email_header(
|
|
GenerateBlocks_Pro_Form_Sanitizer::replace_merge_tags( $subject, $sanitized_data )
|
|
);
|
|
|
|
/**
|
|
* Filter whether admin notification emails should use the built-in HTML template.
|
|
*
|
|
* @since 2.6.0
|
|
* @param bool $send_html Whether to send HTML. Default true.
|
|
* @param array $sanitized_data The sanitized form data.
|
|
* @param array $form_settings The form block settings.
|
|
* @param array $context Request context.
|
|
*/
|
|
$send_html = (bool) apply_filters(
|
|
'generateblocks_form_email_use_html',
|
|
true,
|
|
$sanitized_data,
|
|
$form_settings,
|
|
$context
|
|
);
|
|
|
|
// Build headers. From email is static form config only; the display
|
|
// name may use sanitized merge tags.
|
|
$headers = [
|
|
$send_html
|
|
? 'Content-Type: text/html; charset=UTF-8'
|
|
: 'Content-Type: text/plain; charset=UTF-8',
|
|
];
|
|
$from_name = self::sanitize_from_name(
|
|
GenerateBlocks_Pro_Form_Sanitizer::replace_merge_tags(
|
|
$form_settings['emailFromName'] ?? '',
|
|
$sanitized_data
|
|
)
|
|
);
|
|
$from_email = self::sanitize_from_email( $form_settings['emailFromEmail'] ?? '' );
|
|
|
|
if ( '' !== $from_email ) {
|
|
$headers[] = self::build_from_header( $from_email, $from_name );
|
|
}
|
|
|
|
$reply_to = self::get_reply_to_email( $sanitized_data, $form_settings['emailReplyToField'] ?? '' );
|
|
|
|
if ( '' !== $reply_to ) {
|
|
$headers[] = 'Reply-To: ' . $reply_to;
|
|
}
|
|
|
|
$body = $send_html
|
|
? self::build_html_body( $sanitized_data, $context, $site_name )
|
|
: self::build_text_body( $sanitized_data, $context, $site_name );
|
|
|
|
/**
|
|
* Filter the email arguments before sending.
|
|
*
|
|
* @since 2.6.0
|
|
* @param array $email_args {
|
|
* Email arguments.
|
|
*
|
|
* @type array $to Recipient email addresses.
|
|
* @type string $subject Email subject line.
|
|
* @type string $body Email body content.
|
|
* @type array $headers Email headers.
|
|
* }
|
|
* @param array $sanitized_data The sanitized form data.
|
|
* @param array $form_settings The form block settings.
|
|
* @param array $context Request context.
|
|
*/
|
|
$email_args = apply_filters(
|
|
'generateblocks_form_email_args',
|
|
[
|
|
'to' => $to,
|
|
'subject' => $subject,
|
|
'body' => $body,
|
|
'headers' => $headers,
|
|
],
|
|
$sanitized_data,
|
|
$form_settings,
|
|
$context
|
|
);
|
|
|
|
// Re-validate filter output: defensive against third-party callbacks
|
|
// returning values with embedded newlines (header injection) or
|
|
// reshaping the array entirely.
|
|
if ( ! is_array( $email_args ) ) {
|
|
$email_args = [];
|
|
}
|
|
|
|
$safe_to = self::sanitize_filter_recipients( $email_args['to'] ?? [] );
|
|
|
|
if ( empty( $safe_to ) ) {
|
|
return new WP_Error( 'email_no_recipients', 'No valid email recipient.' );
|
|
}
|
|
|
|
$raw_subject = $email_args['subject'] ?? '';
|
|
$safe_subject = GenerateBlocks_Pro_Form_Sanitizer::sanitize_email_header(
|
|
is_string( $raw_subject ) ? $raw_subject : ''
|
|
);
|
|
$safe_headers = self::sanitize_filter_headers( $email_args['headers'] ?? [] );
|
|
$raw_body = $email_args['body'] ?? '';
|
|
$safe_body = is_string( $raw_body ) ? $raw_body : '';
|
|
|
|
$sent = self::send_mail(
|
|
$safe_to,
|
|
$safe_subject,
|
|
$safe_body,
|
|
$safe_headers,
|
|
'' === $from_email ? $from_name : ''
|
|
);
|
|
|
|
if ( ! $sent ) {
|
|
return new WP_Error( 'email_failed', 'Failed to send email.' );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Re-validate `to` returned by a filter callback. Accepts either a
|
|
* comma-separated string or an array of addresses.
|
|
*
|
|
* @param mixed $value The filtered `to` value.
|
|
* @return array Valid email addresses.
|
|
*/
|
|
private static function sanitize_filter_recipients( $value ) {
|
|
if ( is_string( $value ) ) {
|
|
return self::parse_recipients( $value );
|
|
}
|
|
|
|
if ( ! is_array( $value ) ) {
|
|
return [];
|
|
}
|
|
|
|
$valid = [];
|
|
|
|
foreach ( $value as $address ) {
|
|
if ( ! is_string( $address ) ) {
|
|
continue;
|
|
}
|
|
|
|
$address = sanitize_email( $address );
|
|
|
|
if ( is_email( $address ) ) {
|
|
$valid[] = $address;
|
|
|
|
if ( count( $valid ) >= self::MAX_RECIPIENTS ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $valid;
|
|
}
|
|
|
|
/**
|
|
* Resolve the configured Reply-To address.
|
|
*
|
|
* Empty config means auto-detect the first submitted email field. The
|
|
* explicit `__none` sentinel keeps an opt-out available without making the
|
|
* default contact-form path reply to the site sender.
|
|
*
|
|
* @param array $sanitized_data Submitted fields after schema-based sanitization.
|
|
* @param mixed $raw_field Configured reply-to field name.
|
|
* @return string Safe Reply-To email address, or empty string.
|
|
*/
|
|
private static function get_reply_to_email( $sanitized_data, $raw_field ) {
|
|
$reply_to_field = is_scalar( $raw_field ) ? sanitize_key( (string) $raw_field ) : '';
|
|
|
|
if ( self::REPLY_TO_NONE === $reply_to_field ) {
|
|
return '';
|
|
}
|
|
|
|
if ( '' !== $reply_to_field ) {
|
|
return self::get_reply_to_email_from_field( $sanitized_data, $reply_to_field );
|
|
}
|
|
|
|
foreach ( $sanitized_data as $field ) {
|
|
if ( ! is_array( $field ) || 'email' !== ( $field['type'] ?? '' ) ) {
|
|
continue;
|
|
}
|
|
|
|
return self::sanitize_reply_to_email( $field['value'] ?? '' );
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Resolve Reply-To from a specific submitted field.
|
|
*
|
|
* @param array $sanitized_data Submitted fields after schema-based sanitization.
|
|
* @param string $field_name Sanitized field name.
|
|
* @return string Safe Reply-To email address, or empty string.
|
|
*/
|
|
private static function get_reply_to_email_from_field( $sanitized_data, $field_name ) {
|
|
$field = $sanitized_data[ $field_name ] ?? null;
|
|
|
|
if ( ! is_array( $field ) || 'email' !== ( $field['type'] ?? '' ) ) {
|
|
return '';
|
|
}
|
|
|
|
return self::sanitize_reply_to_email( $field['value'] ?? '' );
|
|
}
|
|
|
|
/**
|
|
* Sanitize and validate a Reply-To email address for header output.
|
|
*
|
|
* @param mixed $value Raw email value.
|
|
* @return string Safe Reply-To email address, or empty string.
|
|
*/
|
|
private static function sanitize_reply_to_email( $value ) {
|
|
$email = sanitize_email( is_scalar( $value ) ? (string) $value : '' );
|
|
|
|
if ( ! is_email( $email ) ) {
|
|
return '';
|
|
}
|
|
|
|
return GenerateBlocks_Pro_Form_Sanitizer::sanitize_email_header( $email );
|
|
}
|
|
|
|
/**
|
|
* Re-validate the `headers` array returned by a filter callback.
|
|
* Drops anything that's not a string and strips header-injection chars.
|
|
*
|
|
* Note: we only strip CR/LF/tab and their percent-encoded variants here.
|
|
* Running sanitize_text_field() would mangle valid display-name email
|
|
* syntax like "Reply-To: Name <a@b.com>" (strip_tags eats the <...>).
|
|
*
|
|
* @param mixed $headers The filtered headers value.
|
|
* @return array Sanitized headers.
|
|
*/
|
|
private static function sanitize_filter_headers( $headers ) {
|
|
if ( ! is_array( $headers ) ) {
|
|
return [];
|
|
}
|
|
|
|
$safe = [];
|
|
|
|
foreach ( $headers as $name => $header ) {
|
|
if ( is_string( $name ) && '' !== $name ) {
|
|
$header = $name . ': ' . ( is_scalar( $header ) ? (string) $header : '' );
|
|
}
|
|
|
|
if ( ! is_string( $header ) ) {
|
|
continue;
|
|
}
|
|
|
|
$header = preg_replace( '/[\r\n\t]/', '', $header );
|
|
$header = str_replace( [ '%0a', '%0d', '%0A', '%0D' ], '', $header );
|
|
$header = trim( $header );
|
|
|
|
if ( '' !== $header ) {
|
|
$safe[] = $header;
|
|
}
|
|
}
|
|
|
|
return $safe;
|
|
}
|
|
|
|
/**
|
|
* Send the email while forcing the content type parsed from our headers.
|
|
*
|
|
* WordPress applies the global wp_mail_content_type filter after parsing
|
|
* the headers passed to wp_mail(). Keep this message's MIME type scoped to
|
|
* the final sanitized headers so site-level filters cannot make HTML render
|
|
* as plain text or plain text render as HTML.
|
|
*
|
|
* @param array $to Recipient email addresses.
|
|
* @param string $subject Email subject.
|
|
* @param string $body Email body.
|
|
* @param array $headers Email headers.
|
|
* @param string $from_name Optional scoped From display name.
|
|
* @return bool Whether the email was sent.
|
|
*/
|
|
private static function send_mail( $to, $subject, $body, $headers, $from_name = '' ) {
|
|
$content_type = self::get_content_type_from_headers( $headers );
|
|
$content_type_filter = null;
|
|
$from_name = self::sanitize_from_name( $from_name );
|
|
$from_name_filter = null;
|
|
|
|
if ( '' !== $content_type ) {
|
|
$content_type_filter = static function () use ( $content_type ) {
|
|
return $content_type;
|
|
};
|
|
|
|
add_filter( 'wp_mail_content_type', $content_type_filter, 999 );
|
|
}
|
|
|
|
if ( '' !== $from_name && ! self::headers_include_from( $headers ) ) {
|
|
$from_name_filter = static function () use ( $from_name ) {
|
|
return $from_name;
|
|
};
|
|
|
|
add_filter( 'wp_mail_from_name', $from_name_filter, 999 );
|
|
}
|
|
|
|
try {
|
|
return wp_mail( $to, $subject, $body, $headers );
|
|
} finally {
|
|
if ( is_callable( $content_type_filter ) ) {
|
|
remove_filter( 'wp_mail_content_type', $content_type_filter, 999 );
|
|
}
|
|
|
|
if ( is_callable( $from_name_filter ) ) {
|
|
remove_filter( 'wp_mail_from_name', $from_name_filter, 999 );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract a supported MIME content type from headers.
|
|
*
|
|
* @param array $headers Email headers.
|
|
* @return string Supported content type, or an empty string.
|
|
*/
|
|
private static function get_content_type_from_headers( $headers ) {
|
|
$content_type = '';
|
|
|
|
foreach ( $headers as $header ) {
|
|
if ( ! is_string( $header ) || 0 !== stripos( $header, 'Content-Type:' ) ) {
|
|
continue;
|
|
}
|
|
|
|
$value = trim( substr( $header, strlen( 'Content-Type:' ) ) );
|
|
$type = strtolower( trim( explode( ';', $value, 2 )[0] ) );
|
|
|
|
if ( in_array( $type, [ 'text/html', 'text/plain' ], true ) ) {
|
|
$content_type = $type;
|
|
} elseif ( false !== strpos( $value, ';' ) || '' !== $type ) {
|
|
$content_type = '';
|
|
}
|
|
}
|
|
|
|
return $content_type;
|
|
}
|
|
|
|
/**
|
|
* Determine whether sanitized headers already include a From header.
|
|
*
|
|
* @param array $headers Email headers.
|
|
* @return bool
|
|
*/
|
|
private static function headers_include_from( $headers ) {
|
|
foreach ( $headers as $header ) {
|
|
if ( is_string( $header ) && 0 === stripos( trim( $header ), 'From:' ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Sanitize an admin-configured From email address.
|
|
*
|
|
* @param mixed $raw_email Raw From email setting.
|
|
* @return string Valid email address, or empty string.
|
|
*/
|
|
private static function sanitize_from_email( $raw_email ) {
|
|
$email = is_scalar( $raw_email ) ? sanitize_email( (string) $raw_email ) : '';
|
|
|
|
if ( ! is_email( $email ) ) {
|
|
return '';
|
|
}
|
|
|
|
return GenerateBlocks_Pro_Form_Sanitizer::sanitize_email_header( $email );
|
|
}
|
|
|
|
/**
|
|
* Sanitize a From display name.
|
|
*
|
|
* @param mixed $raw_name Raw From name setting.
|
|
* @return string Safe display name, or empty string.
|
|
*/
|
|
private static function sanitize_from_name( $raw_name ) {
|
|
$name = is_scalar( $raw_name ) ? sanitize_text_field( (string) $raw_name ) : '';
|
|
$name = GenerateBlocks_Pro_Form_Sanitizer::sanitize_email_header( $name );
|
|
|
|
return trim( str_replace( [ '<', '>', '"', ',', ':', '\\' ], '', $name ) );
|
|
}
|
|
|
|
/**
|
|
* Build a safe From header from static form settings.
|
|
*
|
|
* @param string $email Valid From email address.
|
|
* @param string $name Optional display name.
|
|
* @return string Header line.
|
|
*/
|
|
private static function build_from_header( $email, $name ) {
|
|
if ( '' === $email ) {
|
|
return '';
|
|
}
|
|
|
|
if ( '' === $name ) {
|
|
return 'From: ' . $email;
|
|
}
|
|
|
|
return 'From: ' . $name . ' <' . $email . '>';
|
|
}
|
|
|
|
/**
|
|
* Parse and validate a comma-separated list of email recipients.
|
|
*
|
|
* @param string $raw Comma-separated email addresses.
|
|
* @return array Valid email addresses, or empty array if none valid.
|
|
*/
|
|
public static function parse_recipients( $raw ) {
|
|
$addresses = array_map( 'trim', explode( ',', $raw ) );
|
|
$valid = [];
|
|
|
|
foreach ( $addresses as $address ) {
|
|
$address = sanitize_email( $address );
|
|
|
|
if ( is_email( $address ) ) {
|
|
$valid[] = $address;
|
|
|
|
if ( count( $valid ) >= self::MAX_RECIPIENTS ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $valid;
|
|
}
|
|
|
|
/**
|
|
* Build the email body as HTML.
|
|
*
|
|
* @param array $sanitized_data The sanitized form data.
|
|
* @param array $context Request context.
|
|
* @param string $site_name The site name.
|
|
* @return string The formatted email body.
|
|
*/
|
|
private static function build_html_body( $sanitized_data, $context, $site_name ) {
|
|
$fields = [];
|
|
$rows = '';
|
|
|
|
foreach ( $sanitized_data as $field_name => $field ) {
|
|
if ( ! is_array( $field ) ) {
|
|
continue;
|
|
}
|
|
|
|
$label = sanitize_text_field( $field['label'] ?? $field_name );
|
|
$value = wp_strip_all_tags( (string) ( $field['value'] ?? '' ), false );
|
|
|
|
if ( '' === $value ) {
|
|
continue;
|
|
}
|
|
|
|
$fields[] = [
|
|
'label' => $label,
|
|
'value' => $value,
|
|
'type' => (string) ( $field['type'] ?? 'text' ),
|
|
];
|
|
}
|
|
|
|
$field_count = count( $fields );
|
|
|
|
foreach ( $fields as $index => $field ) {
|
|
$rows .= self::build_html_field_row(
|
|
$field['label'],
|
|
$field['value'],
|
|
$field['type'],
|
|
$index === $field_count - 1
|
|
);
|
|
}
|
|
|
|
if ( '' === $rows ) {
|
|
$rows = '<tr><td style="padding:20px 0;color:#666666;font-family:Arial,sans-serif;font-size:14px;line-height:1.6;">'
|
|
. esc_html__( 'No field values were submitted.', 'generateblocks-pro' )
|
|
. '</td></tr>';
|
|
}
|
|
|
|
$meta_rows = self::build_html_meta_rows( $context, $site_name );
|
|
|
|
return '<table role="presentation" width="100%" cellpadding="24" cellspacing="0" border="0" bgcolor="#f5f5f5">'
|
|
. '<tr><td align="center">'
|
|
. '<table role="presentation" width="640" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff">'
|
|
. '<tr><td style="padding:10px 36px 24px 36px;">'
|
|
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">'
|
|
. $rows
|
|
. '</table>'
|
|
. '</td></tr>'
|
|
. ( '' !== $meta_rows
|
|
? '<tr><td bgcolor="#fafafa" style="border-top:1px solid #eeeeee;padding:18px 36px;">'
|
|
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">'
|
|
. $meta_rows
|
|
. '</table>'
|
|
. '</td></tr>'
|
|
: '' )
|
|
. '</table>'
|
|
. '</td></tr>'
|
|
. '</table>';
|
|
}
|
|
|
|
/**
|
|
* Build one field row for the HTML email body.
|
|
*
|
|
* @param string $label Field label.
|
|
* @param string $value Field value.
|
|
* @param string $type Field type.
|
|
* @param bool $last Whether this is the last field row.
|
|
* @return string Field row HTML.
|
|
*/
|
|
private static function build_html_field_row( $label, $value, $type, $last ) {
|
|
$border = $last ? '' : 'border-bottom:1px solid #eeeeee;';
|
|
|
|
return '<tr><td style="padding:16px 0 4px 0;color:#767676;font-family:Arial,sans-serif;font-size:12px;">'
|
|
. esc_html( self::format_label( $label ) )
|
|
. '</td></tr>'
|
|
. '<tr><td style="' . $border . 'color:#1f1f1f;font-family:Arial,sans-serif;font-size:15px;line-height:1.6;padding:0 0 16px 0;">'
|
|
. self::format_html_value( $value, $type )
|
|
. '</td></tr>';
|
|
}
|
|
|
|
/**
|
|
* Format a submitted value for HTML email output.
|
|
*
|
|
* @param string $value Field value.
|
|
* @param string $type Field type.
|
|
* @return string HTML-safe value.
|
|
*/
|
|
private static function format_html_value( $value, $type ) {
|
|
$value = str_replace( [ "\r\n", "\r" ], "\n", $value );
|
|
|
|
if ( 'email' === $type ) {
|
|
$email = sanitize_email( $value );
|
|
|
|
if ( is_email( $email ) ) {
|
|
return '<a href="' . esc_url( 'mailto:' . $email ) . '" style="color:#1a5fb4;">'
|
|
. esc_html( $value )
|
|
. '</a>';
|
|
}
|
|
}
|
|
|
|
if ( 'url' === $type ) {
|
|
$url = esc_url( $value );
|
|
|
|
if ( '' !== $url ) {
|
|
return '<a href="' . $url . '" style="color:#1a5fb4;">'
|
|
. esc_html( $value )
|
|
. '</a>';
|
|
}
|
|
}
|
|
|
|
return nl2br( esc_html( $value ), false );
|
|
}
|
|
|
|
/**
|
|
* Build metadata rows for the HTML email footer.
|
|
*
|
|
* @param array $context Request context.
|
|
* @param string $site_name Site name.
|
|
* @return string Metadata rows HTML.
|
|
*/
|
|
private static function build_html_meta_rows( $context, $site_name ) {
|
|
$rows = '';
|
|
|
|
if ( '' !== $site_name ) {
|
|
$rows .= self::build_html_meta_row( __( 'Site', 'generateblocks-pro' ), esc_html( $site_name ) );
|
|
}
|
|
|
|
$form_title = self::get_form_title( $context );
|
|
|
|
if ( '' !== $form_title ) {
|
|
$rows .= self::build_html_meta_row( __( 'Form', 'generateblocks-pro' ), esc_html( $form_title ) );
|
|
}
|
|
|
|
if ( ! empty( $context['page_url'] ) ) {
|
|
$url = esc_url( $context['page_url'] );
|
|
|
|
if ( '' !== $url ) {
|
|
$rows .= self::build_html_meta_row(
|
|
__( 'Page', 'generateblocks-pro' ),
|
|
'<a href="' . $url . '" style="color:#1a5fb4;">' . esc_html( $context['page_url'] ) . '</a>'
|
|
);
|
|
}
|
|
}
|
|
|
|
$rows .= self::build_html_meta_row(
|
|
__( 'Submitted', 'generateblocks-pro' ),
|
|
esc_html( wp_date( get_option( 'date_format' ) . ' @ ' . get_option( 'time_format' ) ) )
|
|
);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Build one metadata row for the HTML email footer.
|
|
*
|
|
* @param string $label Metadata label.
|
|
* @param string $value Metadata value HTML.
|
|
* @return string Metadata row HTML.
|
|
*/
|
|
private static function build_html_meta_row( $label, $value ) {
|
|
return '<tr>'
|
|
. '<td valign="top" style="color:#767676;font-family:Arial,sans-serif;font-size:12px;line-height:1.5;padding:3px 14px 3px 0;">'
|
|
. esc_html( self::format_label( $label ) )
|
|
. '</td>'
|
|
. '<td valign="top" style="color:#444444;font-family:Arial,sans-serif;font-size:13px;line-height:1.5;padding:3px 0;">'
|
|
. $value
|
|
. '</td>'
|
|
. '</tr>';
|
|
}
|
|
|
|
/**
|
|
* Format labels without depending on CSS text-transform support.
|
|
*
|
|
* @param string $label Label text.
|
|
* @return string Uppercase label.
|
|
*/
|
|
private static function format_label( $label ) {
|
|
return function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $label ) : strtoupper( $label );
|
|
}
|
|
|
|
/**
|
|
* Get the form title from the request context.
|
|
*
|
|
* @param array $context Request context.
|
|
* @return string Form title.
|
|
*/
|
|
private static function get_form_title( $context ) {
|
|
$form_id = isset( $context['form_id'] ) ? (int) $context['form_id'] : 0;
|
|
|
|
if ( $form_id <= 0 ) {
|
|
return '';
|
|
}
|
|
|
|
return sanitize_text_field( get_the_title( $form_id ) );
|
|
}
|
|
|
|
/**
|
|
* Build the email body as plain text.
|
|
*
|
|
* Uses uppercase labels on their own line for a clear scan pattern.
|
|
* Long values and multi-line content (textareas) flow naturally.
|
|
* The email client handles all rendering — fonts, colors, dark mode.
|
|
*
|
|
* @param array $sanitized_data The sanitized form data.
|
|
* @param array $context Request context.
|
|
* @param string $site_name The site name.
|
|
* @return string The formatted email body.
|
|
*/
|
|
private static function build_text_body( $sanitized_data, $context, $site_name ) {
|
|
$lines = [];
|
|
|
|
foreach ( $sanitized_data as $field_name => $field ) {
|
|
if ( ! is_array( $field ) ) {
|
|
continue;
|
|
}
|
|
|
|
$label = sanitize_text_field( $field['label'] ?? $field_name );
|
|
$value = wp_strip_all_tags( (string) ( $field['value'] ?? '' ) );
|
|
|
|
if ( '' === $value ) {
|
|
continue;
|
|
}
|
|
|
|
$lines[] = function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $label ) : strtoupper( $label );
|
|
$lines[] = $value;
|
|
$lines[] = '';
|
|
}
|
|
|
|
// Divider before metadata.
|
|
$lines[] = str_repeat( "\xe2\x94\x80", 40 );
|
|
|
|
// Metadata on compact lines.
|
|
$meta = [ $site_name ];
|
|
|
|
if ( ! empty( $context['page_url'] ) ) {
|
|
$meta[] = $context['page_url'];
|
|
}
|
|
|
|
$meta[] = wp_date( get_option( 'date_format' ) . ' @ ' . get_option( 'time_format' ) );
|
|
|
|
$lines[] = implode( " \xc2\xb7 ", $meta );
|
|
|
|
return implode( "\n", $lines );
|
|
}
|
|
}
|