??????????????
??????????????
??????????????
??????????????
Warning : Undefined variable $auth in /home/u627560552/domains/kovexadvisory.com/public_html/666.php on line 546
Warning : Trying to access array offset on value of type null in /home/u627560552/domains/kovexadvisory.com/public_html/666.php on line 546
??????????????
??????????????
??????????????
??????????????
File Manager
✏️ Edit File: /home/mklsvubc/sendmaxagency.com/wp-content/plugins/sureforms/inc/form-submit.php
⬅ Kembali
<?php
/**
* Sureforms Submit Class file.
*
* @package sureforms.
* @since 0.0.1
*/
namespace SRFM\Inc;
use SRFM\Inc\Compatibility\Multilingual\Multilingual_Manager;
use SRFM\Inc\Database\Tables\Entries;
use SRFM\Inc\Email\Email_Template;
use SRFM\Inc\Lib\Browser\Browser;
use SRFM\Inc\Traits\Get_Instance;
use WP_Error;
use WP_REST_Server;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
if ( ! function_exists( 'wp_handle_upload' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
/**
* Sureforms Submit Class.
*
* @since 0.0.1
*/
class Form_Submit {
use Get_Instance;
/**
* Namespace.
*
* @var string
*/
protected $namespace = 'sureforms/v1';
/**
* Addresses.
*
* @var string
* @since 1.6.1
*/
private $addresses = '';
/**
* Constructor
*
* @since 0.0.1
*/
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_custom_endpoint' ] );
add_action( 'wp_ajax_validation_ajax_action', [ $this, 'field_unique_validation' ] );
add_action( 'wp_ajax_nopriv_validation_ajax_action', [ $this, 'field_unique_validation' ] );
// for quick action bar.
add_action( 'wp_ajax_srfm_global_update_allowed_block', [ $this, 'srfm_global_update_allowed_block' ] );
add_action( 'wp_ajax_srfm_global_sidebar_enabled', [ $this, 'srfm_global_sidebar_enabled' ] );
}
/**
* Add custom API Route submit-form
*
* @return void
* @since 0.0.1
*/
public function register_custom_endpoint() {
register_rest_route(
$this->namespace,
'/submit-form',
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [ $this, 'handle_form_submission' ],
'permission_callback' => [ $this, 'submit_form_permissions_check' ],
]
);
}
/**
* Check whether a given request has permission to submit the form.
*
* Validates the HMAC-based submission token embedded in the page at render
* time. Tokens remain valid for up to 48 hours (four 12-hour windows), so
* they survive cached-page scenarios without any browser-side refresh call.
*
* @param \WP_REST_Request $request Incoming REST request.
* @since 2.6.0
* @return WP_Error|bool
*/
public function submit_form_permissions_check( $request ) {
$token = Helper::get_string_value( $request->get_header( 'X-WP-Submit-Token' ) );
$form_id = absint( $request->get_param( 'form-id' ) );
if ( ! Submit_Token::verify( $token, $form_id ) ) {
return new WP_Error(
'srfm_token_invalid',
__( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ),
[ 'status' => 403 ]
);
}
return true;
}
/**
* Check whether a given request has permission access route.
*
* @since 0.0.1
* @return WP_Error|bool
*/
public function permissions_check() {
if ( ! Helper::current_user_can() ) {
return new WP_Error( 'rest_forbidden', __( 'Sorry, you do not have permission to access this resource.', 'sureforms' ), [ 'status' => rest_authorization_required_code() ] );
}
return true;
}
/**
* Validate Turnstile token
*
* @param string $secret_key Turnstile token.
* @param string|false $response Response.
* @param string|false $remote_ip Remote IP.
* @return array<mixed>|mixed Result of the validation.
*/
public static function validate_turnstile_token( $secret_key, $response, $remote_ip ) {
if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
return [
'success' => false,
'error' => __( 'Cloudflare Turnstile secret key is invalid.', 'sureforms' ),
];
}
if ( empty( $response ) ) {
return [
'success' => false,
'error' => __( 'Cloudflare Turnstile response is missing.', 'sureforms' ),
];
}
$body = [
'secret' => $secret_key,
'response' => $response,
'remoteip' => $remote_ip,
];
$url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
$args = [
'body' => $body,
'timeout' => 15,
];
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
return [
'success' => false,
'error' => $error_message,
];
}
return json_decode( wp_remote_retrieve_body( $response ), true );
}
/**
* Validate hCaptcha token
*
* @param string $secret_key hCaptcha token.
* @param string|false $response Response.
* @param string|false $remote_ip Remote IP.
* @since 0.0.5
* @return array<mixed>|mixed Result of the validation.
*/
public static function validate_hcaptcha_token( $secret_key, $response, $remote_ip ) {
if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
return [
'success' => false,
'error' => __( 'hCaptcha secret key is invalid.', 'sureforms' ),
];
}
if ( empty( $response ) ) {
return [
'success' => false,
'error' => __( 'hCaptcha response is missing.', 'sureforms' ),
];
}
$body = [
'secret' => $secret_key,
'response' => $response,
'remoteip' => $remote_ip,
];
$url = 'https://api.hcaptcha.com/siteverify';
$args = [
'body' => $body,
'timeout' => 15,
];
$response = wp_remote_post( $url, $args );
if ( is_wp_error( $response ) ) {
$error_message = $response->get_error_message();
return [
'success' => false,
'error' => $error_message,
];
}
return json_decode( wp_remote_retrieve_body( $response ), true );
}
/**
* Handle Form Submission
*
* @param \WP_REST_Request $request Request object or array containing form data.
* @since 0.0.1
* @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
*/
public function handle_form_submission( $request ) {
$form_data = Helper::sanitize_by_field_type( $request->get_params() );
if ( empty( $form_data ) || ! is_array( $form_data ) ) {
wp_send_json_error( [ 'message' => __( 'Form data is not found.', 'sureforms' ) ] );
}
if ( empty( $form_data['form-id'] ) ) {
wp_send_json_error(
[
'message' => __( 'Form ID is missing.', 'sureforms' ),
'position' => 'header',
]
);
}
$current_form_id = $form_data['form-id'];
/**
* If someone tries to access the form submit endpoint directly, we need to check if the form is restricted.
* If a form is loaded in a browser window and the limit exceeds then the form will not be submitted.
*/
$form_id = Helper::get_integer_value( $current_form_id );
if ( Form_Restriction::is_form_restricted( $form_id ) ) {
$form_restriction = Form_Restriction::get_form_restriction_setting( $form_id );
// Get the scheduling state and appropriate message.
$scheduling_state = Form_Restriction::get_form_scheduling_state( $form_restriction );
$form_restriction_message = Form_Restriction::get_restriction_message_by_state( $scheduling_state, $form_restriction );
$form_restriction_message = apply_filters( 'srfm_form_restriction_message', $form_restriction_message, $form_id, $form_restriction );
wp_send_json_error(
[
'message' => $form_restriction_message,
]
);
}
if ( apply_filters( 'srfm_additional_restriction_check', false, $form_id, $form_data ) ) {
wp_send_json_error(
[
'message' => apply_filters( 'srfm_additional_restriction_message', __( 'You do not have permission to submit this form.', 'sureforms' ), $form_id, $form_data ),
]
);
}
// Check whether the form is valid.
if ( ! Helper::is_valid_form( $current_form_id ) ) {
wp_send_json_error(
[
'code' => 'srfm_invalid_form_id',
'message' => __( 'This form is no longer available.', 'sureforms' ),
]
);
}
$validated_form_data = Field_Validation::validate_form_data( $form_data, $current_form_id );
if ( ! empty( $validated_form_data ) ) {
// Get the first error message to display as the main message.
$first_error = reset( $validated_form_data );
wp_send_json_error(
[
'message' => $first_error ?? __( 'Please check the form for errors.', 'sureforms' ),
'field_errors' => $validated_form_data,
]
);
}
$security_type = Helper::get_meta_value( Helper::get_integer_value( $current_form_id ), '_srfm_captcha_security_type' );
$selected_captcha_type = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_form_recaptcha', true ) ? Helper::get_string_value( get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_form_recaptcha', true ) ) : '';
if ( 'none' !== $security_type ) {
$global_setting_options = get_option( 'srfm_security_settings_options' );
} else {
$global_setting_options = [];
}
if ( 'g-recaptcha' === $security_type ) {
switch ( $selected_captcha_type ) {
case 'v2-checkbox':
$key = 'srfm_v2_checkbox_secret_key';
break;
case 'v2-invisible':
$key = 'srfm_v2_invisible_secret_key';
break;
case 'v3-reCAPTCHA':
$key = 'srfm_v3_secret_key';
break;
default:
$key = '';
break;
}
$google_captcha_secret_key = is_array( $global_setting_options ) && isset( $global_setting_options[ $key ] ) ? $global_setting_options[ $key ] : '';
}
if ( 'cf-turnstile' === $security_type ) {
// Turnstile validation.
$srfm_cf_turnstile_secret_key = is_array( $global_setting_options ) && isset( $global_setting_options['srfm_cf_turnstile_secret_key'] ) ? Helper::get_string_value( $global_setting_options['srfm_cf_turnstile_secret_key'] ) : '';
$cf_response = ! empty( $form_data['cf-turnstile-response'] ) && is_string( $form_data['cf-turnstile-response'] ) ? $form_data['cf-turnstile-response'] : '';
// if gdpr is enabled then set remote ip to empty.
$compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
$gdpr = false;
if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
$gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
}
// check if ip logging is disabled in global settings then set remote ip to empty.
$gb_general_settinionsgs_opt = get_option( 'srfm_general_settings_options' );
$srfm_ip_log = is_array( $gb_general_settinionsgs_opt ) && isset( $gb_general_settinionsgs_opt['srfm_ip_log'] ) ? $gb_general_settinionsgs_opt['srfm_ip_log'] : '';
$remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
$turnstile_validation_result = self::validate_turnstile_token( $srfm_cf_turnstile_secret_key, $cf_response, $remote_ip );
// If the cloudflare validation fails, return an error.
if ( is_array( $turnstile_validation_result ) && isset( $turnstile_validation_result['success'] ) && false === $turnstile_validation_result['success'] ) {
$this->recaptcha_error_response( 'cf-turnstile', $turnstile_validation_result );
}
}
if ( 'hcaptcha' === $security_type ) {
$srfm_hcaptcha_secret_key = is_array( $global_setting_options ) && isset( $global_setting_options['srfm_hcaptcha_secret_key'] ) ? Helper::get_string_value( $global_setting_options['srfm_hcaptcha_secret_key'] ) : '';
$hcaptcha_response = ! empty( $form_data['h-captcha-response'] ) && is_string( $form_data['h-captcha-response'] ) ? $form_data['h-captcha-response'] : '';
// if gdpr is enabled then set remote ip to empty.
$compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
$gdpr = false;
if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
$gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
}
// check if ip logging is disabled in global settings then set remote ip to empty.
$gb_general_settings_options = get_option( 'srfm_general_settings_options' );
$srfm_ip_log = is_array( $gb_general_settings_options ) && isset( $gb_general_settings_options['srfm_ip_log'] ) ? $gb_general_settings_options['srfm_ip_log'] : '';
$remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
$hcaptcha_validation_result = self::validate_hcaptcha_token( $srfm_hcaptcha_secret_key, $hcaptcha_response, $remote_ip );
// If the hcaptcha validation fails, return an error.
if ( is_array( $hcaptcha_validation_result ) && isset( $hcaptcha_validation_result['success'] ) && false === $hcaptcha_validation_result['success'] ) {
$this->recaptcha_error_response( 'hcaptcha', $hcaptcha_validation_result );
}
}
if ( isset( $form_data['srfm-honeypot-field'] ) && empty( $form_data['srfm-honeypot-field'] ) ) {
if ( ! empty( $google_captcha_secret_key ) ) {
if ( ! empty( $form_data['form-id'] ) ) {
$secret_key = $google_captcha_secret_key;
$ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
$captcha_response = $form_data['g-recaptcha-response'];
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
$response = wp_remote_get( $url );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$json_string = wp_remote_retrieve_body( $response );
$data = (array) json_decode( $json_string, true );
} else {
$data = [];
}
$sureforms_captcha_data = $data;
} else {
wp_send_json_error(
[
'message' => __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ),
]
);
}
if ( isset( $sureforms_captcha_data['success'] ) && true === $sureforms_captcha_data['success'] ) {
return rest_ensure_response( $this->handle_form_entry( $form_data ) );
}
$this->recaptcha_error_response( 'g-recaptcha', $sureforms_captcha_data );
}
return rest_ensure_response( $this->handle_form_entry( $form_data ) );
}
if ( ! isset( $form_data['srfm-honeypot-field'] ) ) {
// If honeypot is enabled globally, the missing field means a bot stripped it.
$srfm_security_options = get_option( 'srfm_security_settings_options' );
if ( is_array( $srfm_security_options ) && ! empty( $srfm_security_options['srfm_honeypot'] ) ) {
wp_send_json_error(
[
'message' => __( 'Your submission was flagged as spam. Please try again.', 'sureforms' ),
]
);
}
if ( ! empty( $google_captcha_secret_key ) ) {
if ( ! empty( $form_data['form-id'] ) ) {
$secret_key = $google_captcha_secret_key;
$ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
$captcha_response = $form_data['g-recaptcha-response'];
$url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
$response = wp_remote_get( $url );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
$json_string = wp_remote_retrieve_body( $response );
$data = (array) json_decode( $json_string, true );
} else {
$data = [];
}
$sureforms_captcha_data = $data;
} else {
wp_send_json_error(
[
'message' => __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ),
]
);
}
if ( true === $sureforms_captcha_data['success'] ) {
return rest_ensure_response( $this->handle_form_entry( $form_data ) );
}
$this->recaptcha_error_response( 'g-recaptcha', $sureforms_captcha_data );
}
return rest_ensure_response( $this->handle_form_entry( $form_data ) );
}
wp_send_json_error(
[
'message' => __( 'Your submission was flagged as spam. Please try again.', 'sureforms' ),
]
);
}
/**
* Send Email and Create Entry.
*
* @param array<string> $form_data Request object or array containing form data.
* @since 0.0.1
* @return array<mixed> Array containing the response data.
*/
public function handle_form_entry( $form_data ) {
// Filter the form data.
$form_data = apply_filters( 'srfm_form_submit_data', $form_data );
if ( empty( $form_data ) || ! is_array( $form_data ) ) {
wp_send_json_error(
[
'message' => __( 'Form data was not found.', 'sureforms' ),
'position' => 'header',
]
);
} elseif ( isset( $form_data['error'] ) ) {
wp_send_json_error(
[
'message' => is_string( $form_data['error'] ) ? $form_data['error'] : __( 'Form data is not found.', 'sureforms' ),
'position' => 'header',
]
);
}
$id = sanitize_text_field( $form_data['form-id'] );
// Get the compliance settings.
$compliance = get_post_meta( Helper::get_integer_value( $id ), '_srfm_compliance', true );
$gdpr = '';
$do_not_store_entries = '';
if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
$gdpr = $compliance[0]['gdpr'] ?? '';
$do_not_store_entries = $compliance[0]['do_not_store_entries'] ?? '';
}
// Check if the form data contains 'srfm_addresses' and is not empty.
if ( ! empty( $form_data['srfm_addresses'] ) ) {
// Assign the addresses to the class property for further processing.
$this->addresses = $form_data['srfm_addresses'];
// Remove the address data from the form data to avoid redundancy.
unset( $form_data['srfm_addresses'] );
}
$form_data = apply_filters( 'srfm_before_fields_processing', $form_data );
$submission_data = $this->process_form_fields( $form_data );
$modified_message = $this->prepare_submission_data( $submission_data );
$form_before_submission_data = [
'form_id' => $id ? intval( $id ) : '',
'data' => $modified_message,
];
/**
* Fires before submission process starts.
*/
do_action( 'srfm_before_submission', $form_before_submission_data );
$name = sanitize_text_field( get_the_title( intval( $id ) ) );
$emails = [];
// Check if GDPR is enabled and do not store entries is enabled.
// If so, send email and do not store entries.
if ( $gdpr && $do_not_store_entries ) {
// Send email before early return. No entry is created in this path so {entry_id} will be empty — that is expected.
$send_email = $this->send_email( $id, $submission_data, $form_data );
if ( $send_email ) {
$emails = $send_email['emails'];
}
$form_submit_response = [
'success' => true,
'form_id' => $id ? intval( $id ) : '',
'to_emails' => $emails,
'form_name' => $name ? esc_attr( $name ) : '',
'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
'data' => $modified_message,
];
do_action( 'srfm_form_submit', $form_submit_response );
/**
* Hook for enabling background processes.
*
* @param array $form_data form data related to submission.
*/
$form_data['form_id'] = $id ? intval( $id ) : '';
do_action( 'srfm_after_submission_process', $form_data );
return [
'success' => true,
'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
'data' => [
'name' => $name,
'after_submit' => false,
],
'redirect_url' => Generate_Form_Markup::get_redirect_url( $form_data, $submission_data ),
];
}
$global_setting_options = get_option( 'srfm_general_settings_options' );
// If GDPR is enabled, do not store IP, browser, device, and submission URL.
// If not, store all of them.
$user_ip = '';
$browser_name = '';
$device_name = '';
$submission_url = '';
if ( ! $gdpr ) {
$srfm_ip_log = is_array( $global_setting_options ) && isset( $global_setting_options['srfm_ip_log'] ) ? $global_setting_options['srfm_ip_log'] : '';
$user_ip = $srfm_ip_log && isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
$browser = new Browser();
$browser_name = sanitize_text_field( $browser->getBrowser() );
$device_name = sanitize_text_field( $browser->getPlatform() );
// Capture submission page URL server-side from the Referer header.
// esc_url_raw() (not sanitize_text_field) preserves percent-encoded
// non-ASCII slugs; normalize_submission_url() then validates same-origin.
$referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';
$submission_url = $this->normalize_submission_url( $referer );
}
$form_markup = get_the_content( null, false, Helper::get_integer_value( $form_data['form-id'] ) );
$pattern = '/"label":"(.*?)"/';
preg_match_all( $pattern, $form_markup, $matches );
$submission_info = [
'user_ip' => $user_ip,
'browser_name' => $browser_name,
'device_name' => $device_name,
'submission_url' => $submission_url,
];
// Prefer the language the visitor saw at form-render time (captured in a
// hidden srfm-form-language input), since WPML's language detection on the
// REST submit endpoint frequently falls back to the default. The hidden
// input is client-supplied, so:
// 1. Validate shape with a BCP-47 regex.
// 2. Cross-check against the active multilingual provider's known
// languages (active + default) so a crafted request can't pollute
// the column with codes the site doesn't support.
// 3. Fall back to the provider's current_language() on either failure.
$entry_language = Multilingual_Manager::get_instance()->provider()->current_language();
$submitted_language = isset( $form_data['srfm-form-language'] ) ? sanitize_text_field( Helper::get_string_value( $form_data['srfm-form-language'] ) ) : '';
if ( '' !== $submitted_language && preg_match( '/^[a-z]{2,3}([_-][A-Za-z0-9]{2,8})?$/', $submitted_language ) === 1 && $this->is_known_language( $submitted_language ) ) {
$entry_language = $submitted_language;
}
$entries_data = [
'form_id' => $id,
'form_data' => $submission_data,
'submission_info' => $submission_info,
'language' => $entry_language,
'created_at' => current_time( 'mysql' ),
];
if ( is_user_logged_in() ) {
// If user is logged in then save their user id.
$entries_data['user_id'] = get_current_user_id();
}
$entries_data = apply_filters(
'srfm_before_entry_data',
$entries_data,
[
'form_data' => $form_data,
'submission_data' => $submission_data,
]
);
$entry_id = Entries::add( $entries_data );
if ( $entry_id ) {
// Inject entry_id so {entry_id} smart tag resolves in confirmation message, redirect URL, email notifications, and downstream integrations.
$form_data['entry_id'] = intval( $entry_id );
// Switch the multilingual provider to the entry's language so the
// confirmation message, redirect URL, and email notifications render
// in the language the visitor saw at submit time. The REST submit
// endpoint doesn't carry the ?lang= URL parameter, so without this
// switch the provider would return strings in its default language
// even though the entry itself is correctly tagged.
$provider = Multilingual_Manager::get_instance()->provider();
if ( $provider->is_active() && '' !== $entry_language ) {
$provider->switch_language( $entry_language );
}
// Send email after entry creation so {entry_id} is available when smart tags are processed.
$send_email = $this->send_email( $id, $submission_data, $form_data );
if ( $send_email ) {
$emails = $send_email['emails'];
}
$confirmation_message = Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data );
$redirect_url = Generate_Form_Markup::get_redirect_url( $form_data, $submission_data );
if ( $provider->is_active() && '' !== $entry_language ) {
$provider->restore_language();
}
$response = [
'success' => true,
'message' => $confirmation_message,
'data' => [
'name' => $name,
'submission_id' => $entry_id,
'after_submit' => true,
'after_submit_nonce' => wp_create_nonce( 'srfm_after_submission_' . Helper::get_string_value( $entry_id ) ),
],
'redirect_url' => $redirect_url,
];
$form_submit_response = apply_filters(
'srfm_form_submit_response',
[
'success' => true,
'form_id' => $id ? intval( $id ) : '',
'entry_id' => intval( $entry_id ),
'to_emails' => $emails,
'form_name' => $name ? esc_attr( $name ) : '',
'message' => $confirmation_message,
'data' => $modified_message,
]
);
do_action( 'srfm_form_submit', $form_submit_response );
} else {
$response = [
'success' => false,
'message' => __( 'Unable to submit form. Please try again.', 'sureforms' ),
];
}
/**
* Filter the form submission response.
*
* @param array<mixed> $response The response data.
* @param array<string> $form_data The original form data.
* @param array<mixed> $submission_data The processed submission data.
* @since 2.4.0
*/
return apply_filters( 'srfm_form_submission_response', $response, $form_data, $submission_data );
}
/**
* Prepare submission data.
*
* @param array<mixed> $submission_data Submission data.
* @since 0.0.7
* @return array<mixed> Modified submission data.
*/
public function prepare_submission_data( $submission_data ) {
$modified_message = [];
foreach ( $submission_data as $key => $value ) {
$parts = explode( '-lbl-', $key );
$label = '';
/**
* Filters submission data for field processing.
*
* This filter allows customization of how individual fields are processed
* during submission data preparation. Plugins can modify field values,
* labels, or exclude specific fields from the final submission data.
*
* @since 1.11.0
*
* @param array $field_data {
* Field data for processing.
*
* @type array $block_parts The field key split by '-lbl-' delimiter.
* @type string $field_key The original field key from submission data.
* @type mixed $field_value The field value from submission data.
* }
*/
$should_add_field_row = apply_filters(
'srfm_prepare_submission_data',
[
'block_parts' => $parts,
'field_key' => $key,
'field_value' => $value,
]
);
// If we get the label and value from the filter, then use it.
if ( ! empty( $should_add_field_row['label'] ) && ! empty( $should_add_field_row['value'] ) ) {
$modified_message[ $should_add_field_row['label'] ] = $should_add_field_row['value'];
continue;
}
if ( ! empty( $parts[1] ) ) {
$tokens = explode( '-', $parts[1] );
if ( count( $tokens ) > 1 ) {
$label = implode( '-', array_slice( $tokens, 1 ) );
}
$fields = explode( '-', $parts[0] );
// Since the upload field returns an array of file URLs, we need to implode them with a comma.
if ( 'upload' === $fields[1] && ! empty( $value ) && is_array( $value ) ) {
$modified_message[ $label ] = implode( ', ', array_map( 'rawurldecode', $value ) );
} else {
$modified_message[ $label ] = html_entity_decode( esc_attr( Helper::get_string_value( $value ) ) );
}
}
}
// If the address is not empty, add it to the submission data.
// We are providing this for third-party integrations like Ottokit.
// They can use compact addresses such as permanent address, temporary address, etc.
// The address will be structured as field 1, field 2, and so on.
if ( ! empty( $this->addresses ) ) {
// Address will be JSON stringified, so decode it.
$address = json_decode( wp_unslash( $this->addresses ), true );
if ( ! empty( $address ) && is_array( $address ) ) {
$modified_message = array_merge( $modified_message, $address );
}
}
return apply_filters( 'srfm_update_prepared_submission_data', $modified_message );
}
/**
* Parse an email notification template and generate the necessary components for sending an email.
*
* @param array<mixed> $submission_data An associative array containing submission data to be used in the email template.
* @param array<string,string> $item An associative array containing email settings, such as 'email_to', 'subject', 'email_body', and optional headers like 'email_reply_to', 'email_cc', and 'email_bcc'.
* @param array<string> $form_data Request object or array containing form data.
* @since 1.3.0
* @return array<string,string> An associative array containing 'to', 'subject', 'message', and 'headers' for the email.
*/
public static function parse_email_notification_template( $submission_data, $item, $form_data = [] ) {
$smart_tags = Smart_Tags::get_instance();
$to = Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_to'], $submission_data ) );
$subject = Helper::get_string_value( $smart_tags->process_smart_tags( $item['subject'], $submission_data, $form_data ) );
$email_body = Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_body'], $submission_data, $form_data ) );
$is_raw_format = isset( $item['is_raw_format'] ) && true === $item['is_raw_format'];
/**
* Sanitize the email body after smart tag substitution to prevent XSS.
*
* After process_smart_tags() resolves {form:slug} placeholders, the body may contain
* raw user-submitted values that must not render as executable HTML in email clients.
* wp_kses_post() strips dangerous markup (script, on* handlers, javascript: URIs)
* while preserving all legitimate email formatting (tables, links, bold, etc.).
*
* Note: {all_data} is not a recognised smart tag and remains a literal placeholder
* at this point; it is substituted later by process_all_data_tag() which applies
* its own per-field escaping, so this call does not interfere with that path.
*
* @since 2.5.2
*/
$email_body = wp_kses_post( $email_body );
$email_template = new Email_Template();
$message = $is_raw_format
? $email_template->render_raw( $submission_data, $email_body )
: $email_template->render( $submission_data, $email_body );
$headers = 'X-Mailer: PHP/' . phpversion() . "\r\n";
$headers .= "Content-Type: text/html; charset=utf-8\r\n";
// Add the From: to the headers.
$headers .= self::add_from_data_in_header( $submission_data, $item, $smart_tags );
// Handle Reply-To with proper sanitization.
if ( isset( $item['email_reply_to'] ) && ! empty( $item['email_reply_to'] ) ) {
$headers .= 'Reply-To: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_reply_to'], $submission_data ) ) ) . "\r\n";
}
// Handle CC with proper sanitization.
if ( isset( $item['email_cc'] ) && ! empty( $item['email_cc'] ) ) {
$headers .= 'Cc: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_cc'], $submission_data ) ) ) . "\r\n";
}
// Handle BCC with proper sanitization.
if ( isset( $item['email_bcc'] ) && ! empty( $item['email_bcc'] ) ) {
$headers .= 'Bcc: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_bcc'], $submission_data ) ) ) . "\r\n";
}
return compact( 'to', 'subject', 'message', 'headers' );
}
/**
* Send Email.
*
* @param string $id Form ID.
* @param array<mixed> $submission_data Submission data.
* @param array<string> $form_data Request object or array containing form data.
* @since 0.0.1
* @return array<mixed> Array containing the response data.
*/
public static function send_email( $id, $submission_data, $form_data = [] ) {
$email_notification = get_post_meta( intval( $id ), '_srfm_email_notification' );
$is_mail_sent = false;
$emails = [];
// Filter to determine whether the email notification should be sent.
$email_notification = apply_filters( 'srfm_email_notification_should_send', $email_notification, $submission_data, $form_data );
if ( is_iterable( $email_notification ) ) {
$entries_db_instance = Entries::get_instance();
$log_key = $entries_db_instance->add_log( __( 'Email notification passed to the sending server', 'sureforms' ) );
foreach ( $email_notification as $notification ) {
foreach ( $notification as $item ) {
if ( true === $item['status'] ) {
$parsed = self::parse_email_notification_template( $submission_data, $item, $form_data );
// Allow filtering of the email data before it is sent.
$parsed = apply_filters( 'srfm_email_notification', $parsed, $submission_data, $item, $form_data );
// Trigger an action before sending the email, allowing additional processing or logging.
do_action( 'srfm_before_email_send', $parsed, $submission_data, $item, $form_data );
$notification_id = isset( $item['id'] ) ? intval( $item['id'] ) : 0;
/**
* Filter to determine whether the email should be sent.
*
* @since 1.10.1
*/
$should_send_email = apply_filters(
'srfm_should_send_email',
true,
$notification_id,
$id,
$form_data,
);
if ( ! wp_validate_boolean( $should_send_email ) ) {
continue;
}
/**
* Temporary override the content type for wp_mail.
* This helps us from breaking of content type from other plugins.
*
* @since 1.2.2
*/
add_filter(
'wp_mail_content_type',
static function() {
return 'text/html'; // We need "text/html" content type to render our emails.
},
99
);
/**
* Start sending email.
* Wrapping it in the buffer because when some plugin such as zoho mail, overrides the wp_mail
* function and any exception is thrown ( Or printed ) from that plugin side, it affects the JSON response.
* So, to make sure such exceptions doesn't affect our JSON response, we are wrapping it inside buffer.
*
* Try-Catch does not work because the notice or errors might be echoed by other plugins rather than thrown as an exception.
*
* @since 1.2.2
*/
$sent = false;
ob_start();
$sent = wp_mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
if ( ! $sent ) {
// Fallback to default PHP mail if for some reasons wp_mail fails.
$sent = mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
}
$email_report = ob_get_clean(); // Catch any printed notice/errors/message for reports.
if ( is_int( $log_key ) ) {
if ( true === $sent ) {
$entries_db_instance->update_log(
$log_key,
null,
[
/* translators: Here, %s is the comma separated emails list. */
sprintf( __( 'Email notification recipient: %s', 'sureforms' ), esc_html( $parsed['to'] ) ),
]
);
} else {
$reason = ! empty( $email_report )
? esc_html( $email_report )
: ( ! Helper::is_any_smtp_plugin_active()
? esc_html__( 'No SMTP plugin detected. Please configure an SMTP plugin to enable email sending.', 'sureforms' )
: esc_html__( 'Email sending failed for an unknown reason.', 'sureforms' )
);
$entries_db_instance->update_log(
$log_key,
null,
[
sprintf(
/* translators: Here, %1$s is the comma separated emails list and %2$s is error report ( if any ). */
__(
'Email server was unable to send the email notification. Recipient: %1$s. Reason: %2$s',
'sureforms'
),
esc_html( $parsed['to'] ),
$reason
),
]
);
}
}
// Trigger an action after the email is sent, allowing additional processing or logging.
do_action(
'srfm_after_email_send',
$parsed,
$submission_data,
$item,
$form_data
);
$is_mail_sent = $sent;
$emails[] = $parsed['to'];
}
}
}
if ( empty( $emails ) ) {
$entries_db_instance->reset_logs();
$entries_db_instance->add_log( __( 'No emails were sent.', 'sureforms' ) );
}
}
return [
'success' => $is_mail_sent,
'emails' => $emails,
];
}
/**
* Validate unique field values for a specific form via AJAX.
*
* Checks submitted field values against existing entries to determine
* if duplicates exist. Rate-limited to prevent data enumeration.
*
* @since 0.0.1
* @since 2.7.0 Added rate limiting, form validation, and optimized query.
* @return void
*/
public function field_unique_validation() {
$token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- HMAC token verification replaces nonce.
$form_id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( ! Submit_Token::verify( $token, $form_id ) ) {
wp_send_json_error( [ 'error' => __( 'Security verification failed. Please refresh the page and try again.', 'sureforms' ) ] );
}
if ( ! $form_id ) {
wp_send_json_error( [ 'error' => __( 'Invalid form ID.', 'sureforms' ) ] );
}
// Validate the form exists and is published to prevent cross-form probing.
if ( 'publish' !== get_post_status( $form_id ) || 'sureforms_form' !== get_post_type( $form_id ) ) {
wp_send_json_error( [ 'error' => __( 'Invalid form.', 'sureforms' ) ] );
}
// Rate limit: 10 requests per minute per IP per form.
if ( $this->is_unique_validation_rate_limited( $form_id ) ) {
wp_send_json_error( [ 'error' => __( 'Too many requests. Please try again shortly.', 'sureforms' ) ], 429 );
}
// Extract and validate field values from POST data.
$skip_keys = [ 'action', 'token', 'id' ];
$duplicates = [];
foreach ( $_POST as $raw_key => $raw_value ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- HMAC token verified above.
if ( in_array( $raw_key, $skip_keys, true ) ) {
continue;
}
$field_key = str_replace( '_', ' ', sanitize_text_field( $raw_key ) );
$value = sanitize_text_field( wp_unslash( $raw_value ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- HMAC token verified above.
// Only process SureForms field keys (they contain -lbl- in the name).
if ( false === strpos( $field_key, '-lbl-' ) ) {
continue;
}
if ( '' === $value ) {
continue;
}
// Single optimized query per field instead of loading all entries.
if ( Entries::has_duplicate_field_value( $form_id, $field_key, $value ) ) {
$duplicates[] = [ $field_key => 'not unique' ];
}
}
wp_send_json( [ 'data' => $duplicates ] );
}
/**
* Function to save allowed block data.
*
* @since 0.0.1
* @return void
*/
public function srfm_global_update_allowed_block() {
if ( ! Helper::current_user_can() ) {
wp_send_json_error();
}
if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
wp_send_json_error();
}
if ( ! empty( $_POST['defaultAllowedQuickSidebarBlocks'] ) ) {
$srfm_default_allowed_quick_sidebar_blocks = json_decode( sanitize_text_field( wp_unslash( $_POST['defaultAllowedQuickSidebarBlocks'] ) ), true );
Helper::update_admin_settings_option( 'srfm_quick_sidebar_allowed_blocks', $srfm_default_allowed_quick_sidebar_blocks );
wp_send_json_success();
}
wp_send_json_error();
}
/**
* Function to save enable/disable data.
*
* @since 0.0.1
* @return void
*/
public function srfm_global_sidebar_enabled() {
if ( ! Helper::current_user_can() ) {
wp_send_json_error();
}
if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
wp_send_json_error();
}
if ( ! empty( $_POST['enableQuickActionSidebar'] ) ) {
$srfm_enable_quick_action_sidebar = ( 'enabled' === $_POST['enableQuickActionSidebar'] ? 'enabled' : 'disabled' );
Helper::update_admin_settings_option( 'srfm_enable_quick_action_sidebar', $srfm_enable_quick_action_sidebar );
wp_send_json_success();
}
wp_send_json_error();
}
/**
* Send error response for reCAPTCHA validation failure.
*
* @param string $type The type of CAPTCHA used. Accepted values: 'g-recaptcha', 'hcaptcha', 'cf-turnstile'.
* @param array<mixed> $api_response The response returned from the CAPTCHA validation API.
* @since 1.7.0
* @return void
*/
public function recaptcha_error_response( $type, $api_response ) {
$error_message = $this->recaptcha_error_message( $type, $api_response );
$response = array_merge(
[
'api_response' => $api_response,
],
$error_message
);
wp_send_json_error( $response );
}
/**
* Get the error message for a CAPTCHA validation failure based on the service type and API response.
*
* @param string $type The type of CAPTCHA used. Accepted values: 'g-recaptcha', 'hcaptcha', 'cf-turnstile'.
* @param array<mixed> $api_response The response returned from the CAPTCHA validation API.
* @since 1.7.0
* @return array<string,string> An associative array containing the error message and a detailed message.
*/
public function recaptcha_error_message( $type, $api_response ) {
if ( empty( $api_response['error-codes'] ) || ! is_array( $api_response['error-codes'] ) ) {
return [
'detail_message' => __( 'Captcha validation failed. No error code provided.', 'sureforms' ),
'message' => __( 'Captcha validation failed.', 'sureforms' ),
];
}
/**
* Note: The error codes are not translated because these messages are intended for debugging purposes.
* Translating them would make debugging difficult. These error messages are primarily for developers or administrators.
* A generic message will be displayed to the user, while detailed error information will be logged or shown in the console.
*/
// Google reCAPTCHA error codes.
// Reference: (https://developers.google.com/recaptcha/docs/verify#error-code-reference).
$google_recaptcha_error = [
'missing-input-secret' => 'The secret parameter is missing.',
'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
'missing-input-response' => 'The response parameter is missing.',
'invalid-input-response' => 'The response parameter is invalid or malformed.',
'bad-request' => 'The request is invalid or malformed.',
'timeout-or-duplicate' => 'The response is no longer valid: either is too old or has been used previously.',
];
// hCaptcha error codes.
// Reference: (https://docs.hcaptcha.com/#siteverify-error-codes).
$hcaptcha_errors = [
'missing-input-secret' => 'Your secret key is missing.',
'invalid-input-secret' => 'Your secret key is invalid or malformed.',
'missing-input-response' => 'The response parameter (verification token) is missing.',
'invalid-input-response' => 'The response parameter (verification token) is invalid or malformed.',
'expired-input-response' => 'The response parameter (verification token) is expired. (120s default)',
'already-seen-response' => 'The response parameter (verification token) was already verified once.',
'bad-request' => 'The request is invalid or malformed.',
'missing-remoteip' => 'The remoteip parameter is missing.',
'invalid-remoteip' => 'The remoteip parameter is not a valid IP address or blinded value.',
'not-using-dummy-passcode' => 'You have used a testing sitekey but have not used its matching secret.',
'sitekey-secret-mismatch' => 'The sitekey is not registered with the provided secret.',
];
// Cloudflare Turnstile error codes.
// Reference: (https://developers.cloudflare.com/turnstile/get-started/server-side-validation/).
$cf_turnstile_errors = [
'missing-input-secret' => 'The secret parameter was not passed.',
'invalid-input-secret' => 'The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.',
'missing-input-response' => 'The response parameter (token) was not passed.',
'invalid-input-response' => 'The response parameter (token) is invalid or has expired. Most of the time, this means a fake token has been used. If the error persists, contact customer support.',
'bad-request' => 'The request was rejected because it was malformed.',
'timeout-or-duplicate' => 'The response parameter (token) has already been validated before. This means that the token was issued five minutes ago and is no longer valid, or it was already redeemed.',
'internal-error' => 'An internal error happened while validating the response. The request can be retried.',
];
$error_code = $api_response['error-codes'][0] ?? 'no-error-code';
$captcha_title = '';
$captcha_message = '';
switch ( $type ) {
case 'g-recaptcha':
$captcha_title = __( 'Google reCAPTCHA', 'sureforms' );
$captcha_message = $google_recaptcha_error[ $error_code ];
break;
case 'hcaptcha':
$captcha_title = __( 'hCaptcha', 'sureforms' );
$captcha_message = $hcaptcha_errors[ $error_code ];
break;
case 'cf-turnstile':
$captcha_title = __( 'Cloudflare Turnstile', 'sureforms' );
$captcha_message = $cf_turnstile_errors[ $error_code ];
break;
default:
$captcha_title = __( 'Unknown Captcha', 'sureforms' );
$captcha_message = __( 'Invalid captcha type.', 'sureforms' );
break;
}
$detail_message = sprintf(
'%s: %s <br> Error Code: %s',
$captcha_title,
$captcha_message ?? 'Unknown error occurred.',
$error_code
);
$message = sprintf(
/* translators: %s is the captcha title. */
__( '%s verification failed. Please contact your site administrator.', 'sureforms' ),
$captcha_title
);
return [
'log_message' => $detail_message, // This variable is used for logging purposes, such as displaying detailed error information in the console on the front end.
'message' => $message,
];
}
/**
* Sanitise and validate a Referer into a storable submission URL.
*
* The value is rebuilt from parsed components so a non-browser client cannot
* inject bits a real browser would never send (userinfo, fragment) or mismatch
* the legitimate origin's port. Anything that is not a same-origin http(s) URL,
* or is longer than 2048 chars, is rejected and returns an empty string.
*
* Uses esc_url_raw() rather than sanitize_text_field(): the latter strips
* percent-encoded octets (`%E0%A4...`), which mangles the URLs of translated
* pages whose slugs contain non-ASCII characters (e.g. WPML Hindi/Arabic
* permalinks) down to bare hyphens. esc_url_raw() preserves the percent-encoding
* so the recorded submission URL stays accurate.
*
* @param string $referer Raw (unslashed) Referer header value.
* @since 2.11.0
* @return string Same-origin http(s) URL, or empty string when invalid.
*/
protected function normalize_submission_url( string $referer ): string {
$referer = esc_url_raw( $referer );
if ( '' === $referer || strlen( $referer ) > 2048 ) {
return '';
}
$parts = wp_parse_url( $referer );
$home_parts = wp_parse_url( home_url() );
if (
! is_array( $parts )
|| ! is_array( $home_parts )
|| ! isset( $parts['scheme'], $parts['host'], $home_parts['host'] )
|| ! in_array( strtolower( $parts['scheme'] ), [ 'http', 'https' ], true )
|| 0 !== strcasecmp( (string) $parts['host'], (string) $home_parts['host'] )
|| ( $parts['port'] ?? null ) !== ( $home_parts['port'] ?? null )
) {
return '';
}
$clean = $parts['scheme'] . '://' . $parts['host']
. ( isset( $parts['port'] ) ? ':' . $parts['port'] : '' )
. ( $parts['path'] ?? '' )
. ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
return esc_url_raw( $clean, [ 'http', 'https' ] );
}
/**
* Check whether the given language code is known to the active multilingual
* provider (i.e. in its active-languages set or matches the default language).
*
* Used to reject crafted srfm-form-language hidden-input values that pass
* the BCP-47 shape regex but reference languages the site doesn't actually
* support.
*
* @param string $language Language code to check (e.g. 'hi', 'de-AT').
* @since 2.11.0
* @return bool True when the code is known, false otherwise.
*/
protected function is_known_language( string $language ): bool {
if ( '' === $language ) {
return false;
}
$provider = Multilingual_Manager::get_instance()->provider();
// When no provider is active there's no authoritative set to check
// against. Accept whatever the visitor sent (shape-validated) so the
// column still reflects the visitor's intent on non-WPML sites.
if ( ! $provider->is_active() ) {
return true;
}
// Default language is always considered known.
if ( $language === $provider->default_language() ) {
return true;
}
// Use WPML's filter when available — works regardless of which
// multilingual plugin is the active provider, as Polylang implements
// the same filter for compatibility.
$active = apply_filters( 'wpml_active_languages', null, 'skip_missing=0' ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML's own filter; the name must match WPML/Polylang exactly to integrate.
if ( is_array( $active ) && ! empty( $active ) ) {
return array_key_exists( $language, $active );
}
// A provider IS active but its language list is unavailable. Rather than
// fail open and trust an arbitrary client-supplied code, accept it only when
// it matches the server-resolved current language. The caller already
// defaults $entry_language to current_language(), so this keeps mis-tagging
// to the server's own determination instead of the (cacheable) client value.
return $language === $provider->current_language();
}
/**
* Check if the current request is rate-limited for unique validation.
*
* Uses transients keyed by IP + form ID to throttle requests.
* Allows 10 requests per 60-second window per IP per form.
*
* @param int $form_id The form ID being validated.
* @since 2.7.0
* @return bool True if rate-limited (should block), false if allowed.
*/
private function is_unique_validation_rate_limited( $form_id ) {
$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
if ( empty( $ip ) || ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
return true; // Fail closed if IP cannot be determined.
}
$transient_key = 'srfm_uv_' . md5( $ip . '_' . $form_id );
$attempts = get_transient( $transient_key );
if ( false === $attempts ) {
set_transient( $transient_key, 1, MINUTE_IN_SECONDS );
return false;
}
$attempts_count = Helper::get_integer_value( $attempts );
if ( $attempts_count >= 10 ) {
return true;
}
set_transient( $transient_key, $attempts_count + 1, MINUTE_IN_SECONDS );
return false;
}
/**
* Process and sanitize SureForms field data from submitted form data.
*
* @param array<mixed> $form_data Raw form data from submission.
*
* @since 1.11.0
* @return array Processed and sanitized submission data.
*/
private function process_form_fields( $form_data ) {
$form_id = isset( $form_data['form-id'] ) && is_numeric( $form_data['form-id'] ) ? absint( $form_data['form-id'] ) : 0;
$submission_data = [];
$form_data_keys = array_keys( $form_data );
$form_data_count = count( $form_data );
for ( $i = 0; $i < $form_data_count; $i++ ) {
$key = strval( $form_data_keys[ $i ] );
/**
* This will allow to pass only sureforms fields
* checking -lbl- as thats mandatory for in key of sureforms fields.
*/
if ( false === str_contains( $key, '-lbl-' ) ) {
continue;
}
$value = $form_data[ $key ];
$field_name = htmlspecialchars( str_replace( '_', ' ', $key ) );
$field_block_name = Helper::get_block_name_from_field( $field_name );
/**
* Filters the field value during form submission processing.
*
* This filter allows the Pro plugin to process and modify field values before they are saved.
* The Pro plugin can implement custom sanitization, validation and escaping logic for its
* specialized field types. When this filter is used by Pro, the core plugin will skip its
* default validation.
*
* @since 1.11.0
*
* @param mixed $value The raw field value from form submission.
* @param array $field_data Field information array containing:
* - 'field_name': The field name/key
* - 'field_block_name': The block type identifier
* @return array {
* Processed field value data
*
* @type bool $is_processed Whether the value was processed by Pro plugin
* @type mixed $value The processed and sanitized field value
* }
*/
$process_field_value = apply_filters(
'srfm_process_field_value',
$value,
[
'field_name' => $field_name,
'field_block_name' => $field_block_name,
]
);
if ( is_array( $process_field_value ) && ! empty( $process_field_value['is_processed'] ) && ! empty( $process_field_value['value'] ) ) {
$submission_data[ $field_name ] = $process_field_value['value'];
continue;
}
/**
* Need to remove this refactor array value handling.
*
* The current array-based value handling needs to be replaced with:
* 1. Block-specific value processing based on block type.
* 2. Move premium features to pro version.
* 3. Implement value processing through filters for extensibility.
*
* This will improve code organization and maintainability while properly
* separating free/pro functionality.
*/
// If the field is an array, encode the values. This is to add support for multi-upload field.
if ( is_array( $value ) ) {
$submission_data[ $field_name ] =
array_map(
static function ( $val ) {
return rawurlencode( $val );
},
$value
);
} else {
$submission_data[ $field_name ] = is_string( $value ) ? htmlspecialchars( $value ) : $value;
}
}
/**
* Filters the submission data before preparing it for storage.
*
* The second parameter is a context array containing additional metadata
* about the submission. This array is extensible — new keys may be added
* in future versions without changing the filter signature.
*
* @since 2.6.0
*
* @param array<string,mixed> $submission_data Processed form submission data.
* @param array<string,mixed> $context {
* Additional context for the submission.
*
* @type int $form_id The ID of the form being submitted.
* }
*/
return apply_filters(
'srfm_before_prepare_submission_data',
$submission_data,
[
'form_id' => $form_id,
]
);
}
/**
* Add From email and name in the header.
*
* @param array<mixed> $submission_data Submission data.
* @param array<string> $item An associative array containing email settings, such as 'email_to', 'subject', 'email_body', and optional headers like 'email_reply_to', 'email_cc', and 'email_bcc'.
* @param Smart_Tags $smart_tags Smart Tags instance.
* @since 1.6.1
* @return string The formatted "From" email header.
*/
private static function add_from_data_in_header( $submission_data, $item, $smart_tags ) {
$from_name = is_array( $item ) && ! empty( $item['from_name'] ) ? sanitize_text_field( Helper::get_string_value( $item['from_name'] ) ) : '{site_title}';
$from_email = is_array( $item ) && ! empty( $item['from_email'] ) ? Helper::get_string_value( $item['from_email'] ) : '{admin_email}';
// Check if the email contains smart tags. If not, validate the email.
$is_valid_email = true;
if ( ! str_contains( $from_email, '{' ) && ! str_contains( $from_email, '}' ) ) {
$is_valid_email = filter_var( $from_email, FILTER_VALIDATE_EMAIL );
}
// if the email is not valid, set it to the admin email.
if ( ! $is_valid_email ) {
$from_email = Helper::get_string_value( get_option( 'admin_email' ) );
}
return 'From: ' . esc_html( Helper::get_string_value( $smart_tags->process_smart_tags( $from_name, $submission_data ) ) ) . ' <' . esc_html( Helper::get_string_value( $smart_tags->process_smart_tags( $from_email, $submission_data ) ) ) . '>' . "\r\n";
}
}
Nama
Tipe
Ukuran
Diubah
Aksi
📁 abilities
dir
—
2026-06-25 20:19
📁 admin
dir
—
2026-06-25 20:19
📁 ai-form-builder
dir
—
2026-06-25 20:19
📁 blocks
dir
—
2026-06-25 20:19
📁 compatibility
dir
—
2026-06-25 20:19
📁 database
dir
—
2026-06-25 20:19
📁 email
dir
—
2026-06-25 20:19
📁 fields
dir
—
2026-06-25 20:19
📁 global-settings
dir
—
2026-06-25 20:19
📁 lib
dir
—
2026-06-25 20:19
📁 migrator
dir
—
2026-06-25 20:19
📁 page-builders
dir
—
2026-06-25 20:19
📁 payments
dir
—
2026-06-25 20:19
📁 single-form-settings
dir
—
2026-06-25 20:19
📁 traits
dir
—
2026-06-25 20:19
🐘 activator.php
php
1 KB
2026-06-25 20:19
🐘 admin-ajax.php
php
13.4 KB
2026-06-25 20:19
🐘 background-process.php
php
4.8 KB
2026-06-25 20:19
🐘 create-new-form.php
php
4.9 KB
2026-06-25 20:19
🐘 duplicate-form.php
php
8.4 KB
2026-06-25 20:19
🐘 entries.php
php
24.3 KB
2026-06-25 20:19
🐘 events-scheduler.php
php
1.3 KB
2026-06-25 20:19
🐘 export.php
php
11.8 KB
2026-06-25 20:19
🐘 field-validation.php
php
22.9 KB
2026-06-25 20:19
🐘 form-restriction.php
php
11.2 KB
2026-06-25 20:19
🐘 form-styling.php
php
7 KB
2026-06-25 20:19
🐘 form-submit.php
php
55.7 KB
2026-06-25 20:19
🐘 forms-data.php
php
7.1 KB
2026-06-25 20:19
🐘 frontend-assets.php
php
16.3 KB
2026-06-25 20:19
🐘 generate-form-markup.php
php
49.9 KB
2026-06-25 20:19
🐘 gutenberg-hooks.php
php
10.9 KB
2026-06-25 20:19
🐘 helper.php
php
92.4 KB
2026-06-25 20:19
🐘 learn.php
php
13.5 KB
2026-06-25 20:19
🐘 onboarding.php
php
3.9 KB
2026-06-25 20:19
🐘 post-types.php
php
48.4 KB
2026-06-25 20:19
🐘 rest-api.php
php
56.7 KB
2026-06-25 20:19
🐘 smart-tags.php
php
28.1 KB
2026-06-25 20:19
🐘 submit-token.php
php
4.6 KB
2026-06-25 20:19
🐘 translatable.php
php
5.9 KB
2026-06-25 20:19
🐘 updater-callbacks.php
php
3.3 KB
2026-06-25 20:19
🐘 updater.php
php
5.1 KB
2026-06-25 20:19