: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$field_name = $field['name'] ?? '';
$field_val = empty( $field['value'] ) && ! is_numeric( $field['value'] ) ? esc_html__( '(empty)', 'wpforms-lite' ) : $field['value'];
// Add quantity for the field.
if ( wpforms_payment_has_quantity( $field, $this->form_data ) ) {
$field_val = wpforms_payment_format_quantity( $field );
// Set a default field name if empty.
if ( empty( $field_name ) && $field_name !== null ) {
$field_name = $this->get_default_field_name( $field['id'] );
$message .= '--- ' . $field_name . " ---\r\n\r\n";
$field_value = wpforms_decode_string( $field_val ) . "\r\n\r\n";
* Filter the field value before it is added to the email message.
* @param string $field_value Field value.
* @param array $field Field data.
* @param array $form_data Form data.
$field_value = apply_filters_deprecated( // phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
'wpforms_emails_notifications_plaintext_field_value',
[ $field_value, $field, $this->form_data ],
'1.8.7 of the WPForms plugin',
'wpforms_plaintext_field_value'
/** This filter is documented in /includes/emails/class-emails.php */
$field_value = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_plaintext_field_value',
// Append the filtered field value to the message.
$message .= $field_value;
* Process the HTML email message.
* @param bool $show_empty_fields Whether to display empty fields in the email.
private function process_html_message( $show_empty_fields = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity
* Filter the list of field types to display in the email.
* @param array $other_fields List of field types.
* @param array $form_data Form data.
$other_fields = apply_filters_deprecated( // phpcs:disable WPForms.Comments.ParamTagHooks.InvalidParamTagsQuantity
'wpforms_emails_notifications_display_other_fields',
[ [], $this->form_data ],
'1.8.5.2 of the WPForms plugin',
'wpforms_email_display_other_fields'
/** This filter is documented in /includes/emails/class-emails.php */
$other_fields = (array) apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_email_display_other_fields',
* Filter the form data before it is used to generate the email message.
* @since 1.8.9 The $fields parameter was added.
* @param array $form_data Form data.
* @param array $fields List of submitted fields.
$this->form_data = apply_filters( 'wpforms_emails_notifications_form_data', $this->form_data, $this->fields );
foreach ( $this->form_data['fields'] as $field ) {
$field_message = $this->get_field_html( $field, $show_empty_fields, $other_fields );
* Filter the field message before it is added to the email message.
* @since 1.8.9.3 The $notifications parameter was added.
* @param string $field_message Field message.
* @param array $field Field data.
* @param bool $show_empty_fields Whether to display empty fields in the email.
* @param array $other_fields List of field types.
* @param array $form_data Form data.
* @param array $fields List of submitted fields.
* @param Notifications $notifications Notifications instance.
$message .= apply_filters( 'wpforms_emails_notifications_field_message_html', $field_message, $field, $show_empty_fields, $other_fields, $this->form_data, $this->fields, $this );
* Get a single field HTML markup.
* @param array $field Field data.
* @param bool $show_empty_fields Whether to display empty fields in the email.
* @param array $other_fields List of field types.
public function get_field_html( array $field, bool $show_empty_fields, array $other_fields ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity
$field_type = ! empty( $field['type'] ) ? $field['type'] : '';
$field_id = $field['id'] ?? '';
// Check if the field is empty in $this->fields.
if ( empty( $this->fields[ $field_id ] ) ) {
// Check if the field type is in $other_fields, otherwise skip.
if ( empty( $other_fields ) || ! in_array( $field_type, $other_fields, true ) ) {
// Handle specific field types.
list( $field_name, $field_val ) = $this->process_special_field_values( $field );
// Handle fields that are not empty in $this->fields.
if ( ! $show_empty_fields && ( ! isset( $this->fields[ $field_id ]['value'] ) || (string) $this->fields[ $field_id ]['value'] === '' ) ) {
if ( $this->is_calculated_field_hidden( $field_id ) ) {
$field_name = $this->fields[ $field_id ]['name'] ?? '';
$field_val = empty( $this->fields[ $field_id ]['value'] ) && ! is_numeric( $this->fields[ $field_id ]['value'] ) ? '<em>' . esc_html__( '(empty)', 'wpforms-lite' ) . '</em>' : $this->fields[ $field_id ]['value'];
// Set a default field name if empty.
if ( empty( $field_name ) && $field_name !== null ) {
$field_name = $this->get_default_field_name( $field_id );
/** This filter is documented in src/SmartTags/SmartTag/FieldHtmlId.php.*/
$field_val = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_html_field_value',
$this->fields[ $field_id ] ?? $field,
// If it's not and RTE field - replace new lines with <br/> tags.
if ( $field_type !== 'richtext' ) {
$field_val = str_replace( [ "\r\n", "\r", "\n" ], '<br/>', $field_val );
// Replace the payment total value if an order summary is enabled.
// Ideally, it could be done through the `wpforms_html_field_value` filter,
// but needed data is missed there, e.g. entry data ($this->fields).
if ( $field_type === 'payment-total' && ! empty( $field['summary'] ) ) {
$field_val = $this->process_tag( '{order_summary}' );
// Append the field item to the message.
[ '{field_type}', '{field_name}', '{field_value}' ],
[ $field_type, $field_name, $field_val ],
* Check if a calculated field is hidden.
* @param int $field_id Field ID.
private function is_calculated_field_hidden( $field_id ): bool {
return ! empty( $this->form_data['fields'][ $field_id ]['calculation_is_enabled'] ) &&
! empty( $this->form_data['fields'][ $field_id ]['calculation_code_php'] ) &&
isset( $this->fields[ $field_id ]['visible'] )
&& ! $this->fields[ $field_id ]['visible'];
* @param string $input Smart tag.
private function process_tag( $input = '' ) {
return wpforms_process_smart_tags( $input, $this->form_data, $this->fields, $this->entry_id, 'notification' );
* Process special field types.
* This is used for fields such as Page Break, HTML, Content, etc.
* @param array $field Field data.
private function process_special_field_values( $field ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity
// Use a switch-case statement to handle specific field types.
switch ( $field['type'] ) {
$field_name = ! empty( $field['label'] ) ? str_repeat( '—', 3 ) . ' ' . $field['label'] . ' ' . str_repeat( '—', 3 ) : null;
$field_val = ! empty( $field['description'] ) ? $field['description'] : '';
// Skip if position is 'bottom'.
if ( ! empty( $field['position'] ) && $field['position'] === 'bottom' ) {
$title = ! empty( $field['title'] ) ? $field['title'] : esc_html__( 'Page Break', 'wpforms-lite' );
$field_name = str_repeat( '—', 6 ) . ' ' . $title . ' ' . str_repeat( '—', 6 );
// Skip if the field is conditionally hidden.
if ( $this->is_field_conditionally_hidden( $field['id'] ) ) {
$field_name = ! empty( $field['name'] ) ? $field['name'] : esc_html__( 'HTML / Code Block', 'wpforms-lite' );
$field_val = $field['code'];
// Skip if the field is conditionally hidden.
if ( $this->is_field_conditionally_hidden( $field['id'] ) ) {
$field_name = esc_html__( 'Content', 'wpforms-lite' );
$field_val = $field['content'];
return [ $field_name, $field_val ];
* Checks if conditional_logic is enabled and a field is conditionally hidden in the form.
* @param int $field_id The ID of the field to check.
private function is_field_conditionally_hidden( $field_id ) {
return ! empty( $this->form_data['fields'][ $field_id ]['conditionals'] ) && ! wpforms_conditional_logic_fields()->field_is_visible( $this->form_data, $field_id );
* Get the email reply to address.
* This method has been overridden to add support for the Reply-to Name.
public function get_reply_to_address() {
$reply_to = $this->__get( 'reply_to' );
if ( ! empty( $reply_to ) ) {
// Optional custom format with a Reply-to Name specified: John Doe <john@doe.com>
// - starts with anything,
// - ends with <anything> (expected to be an email, validated later).
$regex = '/^(.+) (<.+>)$/';
if ( preg_match( $regex, $reply_to, $matches ) ) {
$reply_to_name = $this->sanitize( $matches[1] );
$reply_to = trim( $matches[2], '<> ' );
$reply_to = $this->process_tag( $reply_to );
if ( ! is_email( $reply_to ) ) {
$reply_to = "$reply_to_name <{$reply_to}>";
* Filter the email reply-to address.
* @param string $reply_to Email reply-to address.
* @param object $this Instance of the Notifications class.
return apply_filters( 'wpforms_emails_notifications_get_reply_to_address', $reply_to, $this );
* This method has been overridden to add support for processing smart tags.
* @param string $input String to sanitize and process for smart tags.
public function sanitize( $input = '' ) {
return wpforms_decode_string( $this->process_tag( $input ) );
* Get the email content type.
* This method has been overridden to better declare email template assigned to each notification.
public function get_content_type() {
$content_type = 'text/html';
if ( Helpers::is_plain_text_template( $this->current_template ) ) {
$content_type = 'text/plain';
* Filter the email content type.
* @param string $content_type The email content type.
* @param Notifications $this An instance of the "Notifications" class.
$content_type = apply_filters( 'wpforms_emails_notifications_get_content_type', $content_type, $this );
$this->__set( 'content_type', $content_type );
// Return the content type.
* Check if all emails are disabled.
public function is_email_disabled() {
* Filter to control email disabling.
* The "Notifications" class is designed to mirror the properties and methods
* provided by the "WPForms_WP_Emails" class for backward compatibility.
* @param bool $is_disabled Whether to disable all emails.
* @param Notifications $this An instance of the "Notifications" class.
return (bool) apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_disable_all_emails',
* Get the default field name as a fallback.
* @param int $field_id Field ID.
private function get_default_field_name( $field_id ) {
return sprintf( /* translators: %1$d - field ID. */
esc_html__( 'Field ID #%1$s', 'wpforms-lite' ),
wpforms_validate_field_id( $field_id )
* Wrap the given content with a table row.
* This method has been added for styling purposes.
* @param string $content Processed smart tag content.
private function wrap_content_with_table_row( $content ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// If the content is empty, return it as is.
if ( empty( $content ) ) {
// Process the smart tags in the content.
$processed_content = $this->process_tag( $content );
// If the content doesn't contain any smart tags, wrap it in a table row, and return early.
// Don't go beyond this point if the content doesn't contain any smart tags.
if ( ! preg_match( '/{\w+}/', $processed_content ) ) {
return '<tr class="smart-tag"><td class="field-name field-value" colspan="2">' . $processed_content . '</td></tr>';
// Split the content into lines and remove empty lines.
$lines = array_filter( explode( "\n", $content ), 'strlen' );
// Initialize an empty string to store the modified content.
// Iterate through each line.
foreach ( $lines as $line ) {
$trimmed_line = $this->process_tag( trim( $line ) );
// Extract tags at the beginning of the line.
preg_match( '/^(?:\{[^}]+}\s*)+/i', $trimmed_line, $before_line_tags );
if ( ! empty( $before_line_tags[0] ) ) {
// Include the extracted tags at the beginning to the modified content.
$modified_content .= trim( $before_line_tags[0] );
// Remove the extracted tags from the trimmed line.
$trimmed_line = trim( substr( $trimmed_line, strlen( $before_line_tags[0] ) ) );
// Extract all smart tags from the remaining content.
preg_match_all( '/\{([^}]+)}/i', $trimmed_line, $after_line_tags );
// Remove the smart tags from the content.
$content_without_smart_tags = str_replace( $after_line_tags[0], '', $trimmed_line );
if ( ! empty( $content_without_smart_tags ) ) {
// Wrap the content without the smart tags in a new table row.
$modified_content .= '<tr class="smart-tag"><td class="field-name field-value" colspan="2">' . $content_without_smart_tags . '</td></tr>';
if ( ! empty( $after_line_tags[0] ) ) {
// Move all smart tags to the end of the line after the closing </tr> tag.
$modified_content .= implode( ' ', $after_line_tags[0] );