PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.0.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.0.2
2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 0.0.2 0.0.3 All 96 releases
sureforms / inc / form-submit.php

form-submit.php in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.0.2, at inc/form-submit.php

1,259 lines 44.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sureforms Submit Class file.
4 *
5 * @package sureforms.
6 * @since 0.0.1
7 */
8
9 namespace SRFM\Inc;
10
11 use SRFM\Inc\Database\Tables\Entries;
12 use SRFM\Inc\Email\Email_Template;
13 use SRFM\Inc\Lib\Browser\Browser;
14 use SRFM\Inc\Traits\Get_Instance;
15 use WP_Error;
16 use WP_REST_Server;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit; // Exit if accessed directly.
20 }
21
22 if ( ! function_exists( 'wp_handle_upload' ) ) {
23 require_once ABSPATH . 'wp-admin/includes/file.php';
24 }
25
26 /**
27 * Sureforms Submit Class.
28 *
29 * @since 0.0.1
30 */
31 class Form_Submit {
32 use Get_Instance;
33
34 /**
35 * Namespace.
36 *
37 * @var string
38 */
39 protected $namespace = 'sureforms/v1';
40
41 /**
42 * Addresses.
43 *
44 * @var string
45 * @since 1.6.1
46 */
47 private $addresses = '';
48
49 /**
50 * Constructor
51 *
52 * @since 0.0.1
53 */
54 public function __construct() {
55 add_action( 'rest_api_init', [ $this, 'register_custom_endpoint' ] );
56 add_action( 'wp_ajax_validation_ajax_action', [ $this, 'field_unique_validation' ] );
57 add_action( 'wp_ajax_nopriv_validation_ajax_action', [ $this, 'field_unique_validation' ] );
58 // for quick action bar.
59 add_action( 'wp_ajax_srfm_global_update_allowed_block', [ $this, 'srfm_global_update_allowed_block' ] );
60 add_action( 'wp_ajax_srfm_global_sidebar_enabled', [ $this, 'srfm_global_sidebar_enabled' ] );
61 }
62
63 /**
64 * Add custom API Route submit-form
65 *
66 * @return void
67 * @since 0.0.1
68 */
69 public function register_custom_endpoint() {
70 register_rest_route(
71 $this->namespace,
72 '/submit-form',
73 [
74 'methods' => WP_REST_Server::EDITABLE,
75 'callback' => [ $this, 'handle_form_submission' ],
76 'permission_callback' => [ $this, 'submit_form_permissions_check' ],
77 ]
78 );
79 }
80
81 /**
82 * Check whether a given request has permission access route.
83 *
84 * @param \WP_REST_Request $request Request object or array containing form data.
85 * @since 1.8.0
86 * @return WP_Error|bool
87 */
88 public function submit_form_permissions_check( $request ) {
89 $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
90
91 if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
92 wp_send_json_error(
93 [
94 'message' => __( 'Nonce verification failed.', 'sureforms' ),
95 ]
96 );
97 }
98
99 $form_data = Helper::sanitize_by_field_type( $request->get_params() );
100
101 if ( empty( $form_data ) || ! is_array( $form_data ) ) {
102 wp_send_json_error(
103 [
104 'message' => __( 'Form data is not found.', 'sureforms' ),
105 ]
106 );
107 }
108
109 if ( ! $form_data['form-id'] ) {
110 wp_send_json_error(
111 [
112 'message' => __( 'Form Id is missing.', 'sureforms' ),
113 'position' => 'header',
114 ]
115 );
116 }
117
118 return true;
119 }
120
121 /**
122 * Check whether a given request has permission access route.
123 *
124 * @since 0.0.1
125 * @return WP_Error|bool
126 */
127 public function permissions_check() {
128 if ( ! Helper::current_user_can() ) {
129 return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot access this route', 'sureforms' ), [ 'status' => rest_authorization_required_code() ] );
130 }
131 return true;
132 }
133
134 /**
135 * Validate Turnstile token
136 *
137 * @param string $secret_key Turnstile token.
138 * @param string|false $response Response.
139 * @param string|false $remote_ip Remote IP.
140 * @return array<mixed>|mixed Result of the validation.
141 */
142 public static function validate_turnstile_token( $secret_key, $response, $remote_ip ) {
143
144 if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
145 return [
146 'success' => false,
147 'error' => __( 'Cloudflare Turnstile secret key is invalid.', 'sureforms' ),
148 ];
149 }
150
151 if ( empty( $response ) ) {
152 return [
153 'success' => false,
154 'error' => __( 'Cloudflare Turnstile response is missing.', 'sureforms' ),
155 ];
156 }
157
158 $body = [
159 'secret' => $secret_key,
160 'response' => $response,
161 'remoteip' => $remote_ip,
162 ];
163
164 $url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
165
166 $args = [
167 'body' => $body,
168 'timeout' => 15,
169 ];
170
171 $response = wp_remote_post( $url, $args );
172
173 if ( is_wp_error( $response ) ) {
174 $error_message = $response->get_error_message();
175 return [
176 'success' => false,
177 'error' => $error_message,
178 ];
179 }
180
181 return json_decode( wp_remote_retrieve_body( $response ), true );
182 }
183
184 /**
185 * Validate hCaptcha token
186 *
187 * @param string $secret_key hCaptcha token.
188 * @param string|false $response Response.
189 * @param string|false $remote_ip Remote IP.
190 * @since 0.0.5
191 * @return array<mixed>|mixed Result of the validation.
192 */
193 public static function validate_hcaptcha_token( $secret_key, $response, $remote_ip ) {
194
195 if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
196 return [
197 'success' => false,
198 'error' => __( 'hCaptcha secret key is invalid.', 'sureforms' ),
199 ];
200 }
201
202 if ( empty( $response ) ) {
203 return [
204 'success' => false,
205 'error' => __( 'hCaptcha response is missing.', 'sureforms' ),
206 ];
207 }
208
209 $body = [
210 'secret' => $secret_key,
211 'response' => $response,
212 'remoteip' => $remote_ip,
213 ];
214
215 $url = 'https://api.hcaptcha.com/siteverify';
216
217 $args = [
218 'body' => $body,
219 'timeout' => 15,
220 ];
221
222 $response = wp_remote_post( $url, $args );
223
224 if ( is_wp_error( $response ) ) {
225 $error_message = $response->get_error_message();
226 return [
227 'success' => false,
228 'error' => $error_message,
229 ];
230 }
231
232 return json_decode( wp_remote_retrieve_body( $response ), true );
233 }
234
235 /**
236 * Handle Form Submission
237 *
238 * @param \WP_REST_Request $request Request object or array containing form data.
239 * @since 0.0.1
240 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
241 */
242 public function handle_form_submission( $request ) {
243 /**
244 * All checks are done in submit_form_permissions_check method:
245 * - Nonce verification
246 * - Form data validation
247 * - Form ID validation
248 *
249 * @since 1.8.0
250 */
251 $form_data = Helper::sanitize_by_field_type( $request->get_params() );
252
253 $current_form_id = $form_data['form-id'];
254
255 /**
256 * If someone tries to access the form submit endpoint directly, we need to check if the form is restricted.
257 * If a form is loaded in a browser window and the limit exceeds then the form will not be submitted.
258 */
259 $form_id = Helper::get_integer_value( $current_form_id );
260 if ( Form_Restriction::is_form_restricted( $form_id ) ) {
261 $form_restriction = Form_Restriction::get_form_restriction_setting( $form_id );
262 // If the form is restricted, return an error response.
263 $form_restriction_message = $form_restriction['message'] ?? Translatable::get_default_form_restriction_message();
264 wp_send_json_error(
265 [
266 'message' => $form_restriction_message,
267 ]
268 );
269 }
270
271 // Check whether the form is valid.
272 if ( ! Helper::is_valid_form( $current_form_id ) ) {
273 wp_send_json_error(
274 [
275 'code' => 'srfm_invalid_form_id',
276 'message' => __( 'Form does not exist.', 'sureforms' ),
277 ]
278 );
279 }
280
281 $validated_form_data = Field_Validation::validate_form_data( $form_data, $current_form_id );
282
283 if ( ! empty( $validated_form_data ) ) {
284 // Get the first error message to display as the main message.
285 $first_error = reset( $validated_form_data );
286
287 wp_send_json_error(
288 [
289 'message' => $first_error ?? __( 'Form data is not valid.', 'sureforms' ),
290 'field_errors' => $validated_form_data,
291 ]
292 );
293 }
294
295 $security_type = Helper::get_meta_value( Helper::get_integer_value( $current_form_id ), '_srfm_captcha_security_type' );
296 $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 ) ) : '';
297
298 if ( 'none' !== $security_type ) {
299 $global_setting_options = get_option( 'srfm_security_settings_options' );
300 } else {
301 $global_setting_options = [];
302 }
303
304 if ( 'g-recaptcha' === $security_type ) {
305 switch ( $selected_captcha_type ) {
306 case 'v2-checkbox':
307 $key = 'srfm_v2_checkbox_secret_key';
308 break;
309 case 'v2-invisible':
310 $key = 'srfm_v2_invisible_secret_key';
311 break;
312 case 'v3-reCAPTCHA':
313 $key = 'srfm_v3_secret_key';
314 break;
315 default:
316 $key = '';
317 break;
318 }
319
320 $google_captcha_secret_key = is_array( $global_setting_options ) && isset( $global_setting_options[ $key ] ) ? $global_setting_options[ $key ] : '';
321 }
322
323 if ( 'cf-turnstile' === $security_type ) {
324 // Turnstile validation.
325 $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'] ) : '';
326 $cf_response = ! empty( $form_data['cf-turnstile-response'] ) && is_string( $form_data['cf-turnstile-response'] ) ? $form_data['cf-turnstile-response'] : '';
327
328 // if gdpr is enabled then set remote ip to empty.
329 $compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
330 $gdpr = false;
331
332 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
333 $gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
334 }
335
336 // check if ip logging is disabled in global settings then set remote ip to empty.
337 $gb_general_settinionsgs_opt = get_option( 'srfm_general_settings_options' );
338 $srfm_ip_log = is_array( $gb_general_settinionsgs_opt ) && isset( $gb_general_settinionsgs_opt['srfm_ip_log'] ) ? $gb_general_settinionsgs_opt['srfm_ip_log'] : '';
339
340 $remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
341
342 $turnstile_validation_result = self::validate_turnstile_token( $srfm_cf_turnstile_secret_key, $cf_response, $remote_ip );
343
344 // If the cloudflare validation fails, return an error.
345 if ( is_array( $turnstile_validation_result ) && isset( $turnstile_validation_result['success'] ) && false === $turnstile_validation_result['success'] ) {
346 $this->recaptcha_error_response( 'cf-turnstile', $turnstile_validation_result );
347 }
348 }
349
350 if ( 'hcaptcha' === $security_type ) {
351 $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'] ) : '';
352 $hcaptcha_response = ! empty( $form_data['h-captcha-response'] ) && is_string( $form_data['h-captcha-response'] ) ? $form_data['h-captcha-response'] : '';
353
354 // if gdpr is enabled then set remote ip to empty.
355 $compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
356 $gdpr = false;
357
358 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
359 $gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
360 }
361
362 // check if ip logging is disabled in global settings then set remote ip to empty.
363 $gb_general_settings_options = get_option( 'srfm_general_settings_options' );
364 $srfm_ip_log = is_array( $gb_general_settings_options ) && isset( $gb_general_settings_options['srfm_ip_log'] ) ? $gb_general_settings_options['srfm_ip_log'] : '';
365
366 $remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
367 $hcaptcha_validation_result = self::validate_hcaptcha_token( $srfm_hcaptcha_secret_key, $hcaptcha_response, $remote_ip );
368
369 // If the hcaptcha validation fails, return an error.
370 if ( is_array( $hcaptcha_validation_result ) && isset( $hcaptcha_validation_result['success'] ) && false === $hcaptcha_validation_result['success'] ) {
371 $this->recaptcha_error_response( 'hcaptcha', $hcaptcha_validation_result );
372 }
373 }
374
375 if ( isset( $form_data['srfm-honeypot-field'] ) && empty( $form_data['srfm-honeypot-field'] ) ) {
376 if ( ! empty( $google_captcha_secret_key ) ) {
377 if ( isset( $form_data['sureforms_form_submit'] ) ) {
378 $secret_key = $google_captcha_secret_key;
379 $ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
380 $captcha_response = $form_data['g-recaptcha-response'];
381 $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
382
383 $response = wp_remote_get( $url );
384
385 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
386 $json_string = wp_remote_retrieve_body( $response );
387 $data = (array) json_decode( $json_string, true );
388 } else {
389 $data = [];
390 }
391 $sureforms_captcha_data = $data;
392
393 } else {
394 wp_send_json_error(
395 [
396 'message' => __( 'reCAPTCHA error: Submit nonce is not available.', 'sureforms' ),
397 ]
398 );
399 }
400 if ( isset( $sureforms_captcha_data['success'] ) && true === $sureforms_captcha_data['success'] ) {
401 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
402 }
403
404 $this->recaptcha_error_response( 'g-recaptcha', $sureforms_captcha_data );
405 }
406
407 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
408 }
409
410 if ( ! isset( $form_data['srfm-honeypot-field'] ) ) {
411 if ( ! empty( $google_captcha_secret_key ) ) {
412 if ( isset( $form_data['sureforms_form_submit'] ) ) {
413 $secret_key = $google_captcha_secret_key;
414 $ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
415 $captcha_response = $form_data['g-recaptcha-response'];
416 $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
417
418 $response = wp_remote_get( $url );
419
420 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
421 $json_string = wp_remote_retrieve_body( $response );
422 $data = (array) json_decode( $json_string, true );
423 } else {
424 $data = [];
425 }
426 $sureforms_captcha_data = $data;
427
428 } else {
429 wp_send_json_error(
430 [
431 'message' => __( 'reCAPTCHA error: Submit nonce is not available.', 'sureforms' ),
432 ]
433 );
434 }
435 if ( true === $sureforms_captcha_data['success'] ) {
436 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
437 }
438
439 $this->recaptcha_error_response( 'g-recaptcha', $sureforms_captcha_data );
440 }
441
442 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
443 }
444
445 wp_send_json_error(
446 [
447 'message' => __( 'Spam Detected', 'sureforms' ),
448 ]
449 );
450 }
451
452 /**
453 * Send Email and Create Entry.
454 *
455 * @param array<string> $form_data Request object or array containing form data.
456 * @since 0.0.1
457 * @return array<mixed> Array containing the response data.
458 */
459 public function handle_form_entry( $form_data ) {
460 // Filter the form data.
461 $form_data = apply_filters( 'srfm_form_submit_data', $form_data );
462 if ( empty( $form_data ) || ! is_array( $form_data ) ) {
463 wp_send_json_error(
464 [
465 'message' => __( 'Form data is not found.', 'sureforms' ),
466 'position' => 'header',
467 ]
468 );
469 } elseif ( isset( $form_data['error'] ) ) {
470 wp_send_json_error(
471 [
472 'message' => is_string( $form_data['error'] ) ? $form_data['error'] : __( 'Form data is not found.', 'sureforms' ),
473 'position' => 'header',
474 ]
475 );
476 }
477
478 $id = sanitize_text_field( $form_data['form-id'] );
479
480 // Get the compliance settings.
481 $compliance = get_post_meta( Helper::get_integer_value( $id ), '_srfm_compliance', true );
482 $gdpr = '';
483 $do_not_store_entries = '';
484
485 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
486 $gdpr = $compliance[0]['gdpr'] ?? '';
487 $do_not_store_entries = $compliance[0]['do_not_store_entries'] ?? '';
488 }
489
490 // Check if the form data contains 'srfm_addresses' and is not empty.
491 if ( ! empty( $form_data['srfm_addresses'] ) ) {
492 // Assign the addresses to the class property for further processing.
493 $this->addresses = $form_data['srfm_addresses'];
494 // Remove the address data from the form data to avoid redundancy.
495 unset( $form_data['srfm_addresses'] );
496 }
497
498 $form_data = apply_filters( 'srfm_before_fields_processing', $form_data );
499
500 $submission_data = $this->process_form_fields( $form_data );
501
502 $modified_message = $this->prepare_submission_data( $submission_data );
503
504 $form_before_submission_data = [
505 'form_id' => $id ? intval( $id ) : '',
506 'data' => $modified_message,
507 ];
508
509 /**
510 * Fires before submission process starts.
511 */
512 do_action( 'srfm_before_submission', $form_before_submission_data );
513
514 $name = sanitize_text_field( get_the_title( intval( $id ) ) );
515 $send_email = $this->send_email( $id, $submission_data, $form_data );
516 $emails = [];
517
518 if ( $send_email ) {
519 $emails = $send_email['emails'];
520 }
521
522 // Check if GDPR is enabled and do not store entries is enabled.
523 // If so, send email and do not store entries.
524 if ( $gdpr && $do_not_store_entries ) {
525
526 $form_submit_response = [
527 'success' => true,
528 'form_id' => $id ? intval( $id ) : '',
529 'to_emails' => $emails,
530 'form_name' => $name ? esc_attr( $name ) : '',
531 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
532 'data' => $modified_message,
533 ];
534
535 do_action( 'srfm_form_submit', $form_submit_response );
536
537 /**
538 * Hook for enabling background processes.
539 *
540 * @param array $form_data form data related to submission.
541 */
542 $form_data['form_id'] = $id ? intval( $id ) : '';
543 do_action( 'srfm_after_submission_process', $form_data );
544
545 return [
546 'success' => true,
547 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
548 'data' => [
549 'name' => $name,
550 'after_submit' => false,
551 ],
552 'redirect_url' => Generate_Form_Markup::get_redirect_url( $form_data, $submission_data ),
553 ];
554
555 }
556
557 $global_setting_options = get_option( 'srfm_general_settings_options' );
558
559 // If GDPR is enabled, do not store IP, browser, and device info.
560 // If not, store IP, browser, and device info.
561 $user_ip = '';
562 $browser_name = '';
563 $device_name = '';
564 if ( ! $gdpr ) {
565 $srfm_ip_log = is_array( $global_setting_options ) && isset( $global_setting_options['srfm_ip_log'] ) ? $global_setting_options['srfm_ip_log'] : '';
566
567 $user_ip = $srfm_ip_log && isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
568 $browser = new Browser();
569 $browser_name = sanitize_text_field( $browser->getBrowser() );
570 $device_name = sanitize_text_field( $browser->getPlatform() );
571 }
572
573 $form_markup = get_the_content( null, false, Helper::get_integer_value( $form_data['form-id'] ) );
574 $pattern = '/"label":"(.*?)"/';
575 preg_match_all( $pattern, $form_markup, $matches );
576 $submission_info = [
577 'user_ip' => $user_ip,
578 'browser_name' => $browser_name,
579 'device_name' => $device_name,
580 ];
581 $entries_data = [
582 'form_id' => $id,
583 'form_data' => $submission_data,
584 'submission_info' => $submission_info,
585 'created_at' => current_time( 'mysql' ),
586 ];
587 if ( is_user_logged_in() ) {
588 // If user is logged in then save their user id.
589 $entries_data['user_id'] = get_current_user_id();
590 }
591
592 $entries_data = apply_filters(
593 'srfm_before_entry_data',
594 $entries_data,
595 [
596 'form_data' => $form_data,
597 'submission_data' => $submission_data,
598 ]
599 );
600
601 $entry_id = Entries::add( $entries_data );
602 if ( $entry_id ) {
603
604 $confirmation_message = Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data );
605
606 $response = [
607 'success' => true,
608 'message' => $confirmation_message,
609 'data' => [
610 'name' => $name,
611 'submission_id' => $entry_id,
612 'after_submit' => true,
613 'after_submit_nonce' => wp_create_nonce( 'srfm_after_submission_' . Helper::get_string_value( $entry_id ) ),
614 ],
615 'redirect_url' => Generate_Form_Markup::get_redirect_url( $form_data, $submission_data ),
616 ];
617
618 $form_submit_response = apply_filters(
619 'srfm_form_submit_response',
620 [
621 'success' => true,
622 'form_id' => $id ? intval( $id ) : '',
623 'entry_id' => intval( $entry_id ),
624 'to_emails' => $emails,
625 'form_name' => $name ? esc_attr( $name ) : '',
626 'message' => $confirmation_message,
627 'data' => $modified_message,
628 ]
629 );
630
631 do_action( 'srfm_form_submit', $form_submit_response );
632 } else {
633 $response = [
634 'success' => false,
635 'message' => __( 'Error submitting form', 'sureforms' ),
636 ];
637 }
638
639 return $response;
640 }
641
642 /**
643 * Prepare submission data.
644 *
645 * @param array<mixed> $submission_data Submission data.
646 * @since 0.0.7
647 * @return array<mixed> Modified submission data.
648 */
649 public function prepare_submission_data( $submission_data ) {
650 $modified_message = [];
651 foreach ( $submission_data as $key => $value ) {
652 $parts = explode( '-lbl-', $key );
653 $label = '';
654
655 /**
656 * Filters submission data for field processing.
657 *
658 * This filter allows customization of how individual fields are processed
659 * during submission data preparation. Plugins can modify field values,
660 * labels, or exclude specific fields from the final submission data.
661 *
662 * @since 1.11.0
663 *
664 * @param array $field_data {
665 * Field data for processing.
666 *
667 * @type array $block_parts The field key split by '-lbl-' delimiter.
668 * @type string $field_key The original field key from submission data.
669 * @type mixed $field_value The field value from submission data.
670 * }
671 */
672 $should_add_field_row = apply_filters(
673 'srfm_prepare_submission_data',
674 [
675 'block_parts' => $parts,
676 'field_key' => $key,
677 'field_value' => $value,
678 ]
679 );
680
681 // If we get the label and value from the filter, then use it.
682 if ( ! empty( $should_add_field_row['label'] ) && ! empty( $should_add_field_row['value'] ) ) {
683 $modified_message[ $should_add_field_row['label'] ] = $should_add_field_row['value'];
684 continue;
685 }
686
687 if ( ! empty( $parts[1] ) ) {
688 $tokens = explode( '-', $parts[1] );
689 if ( count( $tokens ) > 1 ) {
690 $label = implode( '-', array_slice( $tokens, 1 ) );
691 }
692
693 $fields = explode( '-', $parts[0] );
694
695 // Since the upload field returns an array of file URLs, we need to implode them with a comma.
696 if ( 'upload' === $fields[1] && ! empty( $value ) && is_array( $value ) ) {
697 $modified_message[ $label ] = urldecode( implode( ', ', $value ) );
698 } else {
699 $modified_message[ $label ] = html_entity_decode( esc_attr( Helper::get_string_value( $value ) ) );
700 }
701 }
702 }
703
704 // If the address is not empty, add it to the submission data.
705 // We are providing this for third-party integrations like Ottokit.
706 // They can use compact addresses such as permanent address, temporary address, etc.
707 // The address will be structured as field 1, field 2, and so on.
708 if ( ! empty( $this->addresses ) ) {
709 // Address will be JSON stringified, so decode it.
710 $address = json_decode( wp_unslash( $this->addresses ), true );
711 if ( ! empty( $address ) && is_array( $address ) ) {
712 $modified_message = array_merge( $modified_message, $address );
713 }
714 }
715
716 return apply_filters( 'srfm_update_prepared_submission_data', $modified_message );
717 }
718
719 /**
720 * Parse an email notification template and generate the necessary components for sending an email.
721 *
722 * @param array<mixed> $submission_data An associative array containing submission data to be used in the email template.
723 * @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'.
724 * @param array<string> $form_data Request object or array containing form data.
725 * @since 1.3.0
726 * @return array<string,string> An associative array containing 'to', 'subject', 'message', and 'headers' for the email.
727 */
728 public static function parse_email_notification_template( $submission_data, $item, $form_data = [] ) {
729 $smart_tags = Smart_Tags::get_instance();
730
731 $to = Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_to'], $submission_data ) );
732 $subject = Helper::get_string_value( $smart_tags->process_smart_tags( $item['subject'], $submission_data, $form_data ) );
733 $email_body = Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_body'], $submission_data, $form_data ) );
734 $email_template = new Email_Template();
735 $message = $email_template->render( $submission_data, $email_body );
736 $headers = 'X-Mailer: PHP/' . phpversion() . "\r\n";
737 $headers .= "Content-Type: text/html; charset=utf-8\r\n";
738
739 // Add the From: to the headers.
740 $headers .= self::add_from_data_in_header( $submission_data, $item, $smart_tags );
741
742 // Handle Reply-To with proper sanitization.
743 if ( isset( $item['email_reply_to'] ) && ! empty( $item['email_reply_to'] ) ) {
744 $headers .= 'Reply-To: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_reply_to'], $submission_data ) ) ) . "\r\n";
745 }
746
747 // Handle CC with proper sanitization.
748 if ( isset( $item['email_cc'] ) && ! empty( $item['email_cc'] ) ) {
749 $headers .= 'Cc: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_cc'], $submission_data ) ) ) . "\r\n";
750 }
751
752 // Handle BCC with proper sanitization.
753 if ( isset( $item['email_bcc'] ) && ! empty( $item['email_bcc'] ) ) {
754 $headers .= 'Bcc: ' . Helper::sanitize_email_header( Helper::get_string_value( $smart_tags->process_smart_tags( $item['email_bcc'], $submission_data ) ) ) . "\r\n";
755 }
756
757 return compact( 'to', 'subject', 'message', 'headers' );
758 }
759
760 /**
761 * Send Email.
762 *
763 * @param string $id Form ID.
764 * @param array<mixed> $submission_data Submission data.
765 * @param array<string> $form_data Request object or array containing form data.
766 * @since 0.0.1
767 * @return array<mixed> Array containing the response data.
768 */
769 public static function send_email( $id, $submission_data, $form_data = [] ) {
770 $email_notification = get_post_meta( intval( $id ), '_srfm_email_notification' );
771 $is_mail_sent = false;
772 $emails = [];
773
774 // Filter to determine whether the email notification should be sent.
775 $email_notification = apply_filters( 'srfm_email_notification_should_send', $email_notification, $submission_data, $form_data );
776
777 if ( is_iterable( $email_notification ) ) {
778 $entries_db_instance = Entries::get_instance();
779 $log_key = $entries_db_instance->add_log( __( 'Email notification passed to the sending server', 'sureforms' ) );
780
781 foreach ( $email_notification as $notification ) {
782 foreach ( $notification as $item ) {
783 if ( true === $item['status'] ) {
784
785 $parsed = self::parse_email_notification_template( $submission_data, $item, $form_data );
786
787 // Allow filtering of the email data before it is sent.
788 $parsed = apply_filters( 'srfm_email_notification', $parsed, $submission_data, $item, $form_data );
789
790 // Trigger an action before sending the email, allowing additional processing or logging.
791 do_action( 'srfm_before_email_send', $parsed, $submission_data, $item, $form_data );
792
793 $notification_id = isset( $item['id'] ) ? intval( $item['id'] ) : 0;
794
795 /**
796 * Filter to determine whether the email should be sent.
797 *
798 * @since 1.10.1
799 */
800 $should_send_email = apply_filters(
801 'srfm_should_send_email',
802 true,
803 $notification_id,
804 $id,
805 $form_data,
806 );
807
808 if ( ! wp_validate_boolean( $should_send_email ) ) {
809 continue;
810 }
811
812 /**
813 * Temporary override the content type for wp_mail.
814 * This helps us from breaking of content type from other plugins.
815 *
816 * @since 1.2.2
817 */
818 add_filter(
819 'wp_mail_content_type',
820 static function() {
821 return 'text/html'; // We need "text/html" content type to render our emails.
822 },
823 99
824 );
825
826 /**
827 * Start sending email.
828 * Wrapping it in the buffer because when some plugin such as zoho mail, overrides the wp_mail
829 * function and any exception is thrown ( Or printed ) from that plugin side, it affects the JSON response.
830 * So, to make sure such exceptions doesn't affect our JSON response, we are wrapping it inside buffer.
831 *
832 * Try-Catch does not work because the notice or errors might be echoed by other plugins rather than thrown as an exception.
833 *
834 * @since 1.2.2
835 */
836 $sent = false;
837 ob_start();
838 $sent = wp_mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
839 if ( ! $sent ) {
840 // Fallback to default PHP mail if for some reasons wp_mail fails.
841 $sent = mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
842 }
843 $email_report = ob_get_clean(); // Catch any printed notice/errors/message for reports.
844
845 if ( is_int( $log_key ) ) {
846 if ( true === $sent ) {
847 $entries_db_instance->update_log(
848 $log_key,
849 null,
850 [
851 /* translators: Here, %s is the comma separated emails list. */
852 sprintf( __( 'Email notification recipient: %s', 'sureforms' ), esc_html( $parsed['to'] ) ),
853 ]
854 );
855 } else {
856 $reason = ! empty( $email_report )
857 ? esc_html( $email_report )
858 : ( ! Helper::is_any_smtp_plugin_active()
859 ? esc_html__( 'No SMTP plugin detected. Please configure one to enable email sending.', 'sureforms' )
860 : esc_html__( 'The failure occurred due to an undetermined cause.', 'sureforms' )
861 );
862
863 $entries_db_instance->update_log(
864 $log_key,
865 null,
866 [
867 sprintf(
868 /* translators: Here, %1$s is the comma separated emails list and %2$s is error report ( if any ). */
869 __(
870 'Email server was unable to send the email notification. Recipient: %1$s. Reason: %2$s',
871 'sureforms'
872 ),
873 esc_html( $parsed['to'] ),
874 $reason
875 ),
876 ]
877 );
878
879 }
880 }
881
882 // Trigger an action after the email is sent, allowing additional processing or logging.
883 do_action(
884 'srfm_after_email_send',
885 $parsed,
886 $submission_data,
887 $item,
888 $form_data
889 );
890
891 $is_mail_sent = $sent;
892 $emails[] = $parsed['to'];
893 }
894 }
895 }
896
897 if ( empty( $emails ) ) {
898 $entries_db_instance->reset_logs();
899 $entries_db_instance->add_log( __( 'No emails were sent', 'sureforms' ) );
900 }
901 }
902
903 return [
904 'success' => $is_mail_sent,
905 'emails' => $emails,
906 ];
907 }
908
909 /**
910 * Retrieve all entries data for a specific form ID to check for unique values.
911 *
912 * @since 0.0.1
913 * @return void
914 */
915 public function field_unique_validation() {
916 if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'unique_validation_nonce' ) ) {
917 $error_message = __( 'Nonce verification failed.', 'sureforms' );
918 $error_data = [
919 'error' => $error_message,
920 ];
921 wp_send_json_error( $error_data );
922 }
923
924 global $wpdb;
925 $id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;
926 $meta_value = $id;
927
928 if ( ! $meta_value ) {
929 $error_message = __( 'Invalid form ID.', 'sureforms' );
930 $error_data = [
931 'error' => $error_message,
932 ];
933 wp_send_json_error( $error_data );
934 }
935
936 $_POST = array_map( 'wp_unslash', $_POST );
937
938 // Get the entry IDs for the particualr form to perform unique field validation.
939 $entry_ids = Entries::get_all_entry_ids_for_form( $id );
940
941 $all_form_entries = [];
942 $keys = array_keys( $_POST );
943 $length = count( $keys );
944
945 for ( $i = 3; $i < $length; $i++ ) {
946 $key = $keys[ $i ];
947 $value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
948 $key = str_replace( '_', ' ', $keys[ $i ] );
949
950 foreach ( $entry_ids as $entry_id ) {
951 $entry_id = is_array( $entry_id ) ? Helper::get_integer_value( $entry_id['ID'] ) : 0;
952 $form_data = Entries::get_form_data( $entry_id );
953 if ( is_array( $form_data ) && isset( $form_data[ $key ] ) && $form_data[ $key ] === $value ) {
954 $obj = [ $key => 'not unique' ];
955 array_push( $all_form_entries, $obj );
956 break;
957 }
958 }
959 }
960
961 $results = [
962 'data' => $all_form_entries,
963 ];
964
965 wp_send_json( $results );
966 }
967
968 /**
969 * Function to save allowed block data.
970 *
971 * @since 0.0.1
972 * @return void
973 */
974 public function srfm_global_update_allowed_block() {
975 if ( ! Helper::current_user_can() ) {
976 wp_send_json_error();
977 }
978
979 if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
980 wp_send_json_error();
981 }
982
983 if ( ! empty( $_POST['defaultAllowedQuickSidebarBlocks'] ) ) {
984 $srfm_default_allowed_quick_sidebar_blocks = json_decode( sanitize_text_field( wp_unslash( $_POST['defaultAllowedQuickSidebarBlocks'] ) ), true );
985 Helper::update_admin_settings_option( 'srfm_quick_sidebar_allowed_blocks', $srfm_default_allowed_quick_sidebar_blocks );
986 wp_send_json_success();
987 }
988 wp_send_json_error();
989 }
990
991 /**
992 * Function to save enable/disable data.
993 *
994 * @since 0.0.1
995 * @return void
996 */
997 public function srfm_global_sidebar_enabled() {
998 if ( ! Helper::current_user_can() ) {
999 wp_send_json_error();
1000 }
1001
1002 if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
1003 wp_send_json_error();
1004 }
1005
1006 if ( ! empty( $_POST['enableQuickActionSidebar'] ) ) {
1007 $srfm_enable_quick_action_sidebar = ( 'enabled' === $_POST['enableQuickActionSidebar'] ? 'enabled' : 'disabled' );
1008 Helper::update_admin_settings_option( 'srfm_enable_quick_action_sidebar', $srfm_enable_quick_action_sidebar );
1009 wp_send_json_success();
1010 }
1011 wp_send_json_error();
1012 }
1013
1014 /**
1015 * Send error response for reCAPTCHA validation failure.
1016 *
1017 * @param string $type The type of CAPTCHA used. Accepted values: 'g-recaptcha', 'hcaptcha', 'cf-turnstile'.
1018 * @param array<mixed> $api_response The response returned from the CAPTCHA validation API.
1019 * @since 1.7.0
1020 * @return void
1021 */
1022 public function recaptcha_error_response( $type, $api_response ) {
1023 $error_message = $this->recaptcha_error_message( $type, $api_response );
1024 $response = array_merge(
1025 [
1026 'api_response' => $api_response,
1027 ],
1028 $error_message
1029 );
1030
1031 wp_send_json_error( $response );
1032 }
1033
1034 /**
1035 * Get the error message for a CAPTCHA validation failure based on the service type and API response.
1036 *
1037 * @param string $type The type of CAPTCHA used. Accepted values: 'g-recaptcha', 'hcaptcha', 'cf-turnstile'.
1038 * @param array<mixed> $api_response The response returned from the CAPTCHA validation API.
1039 * @since 1.7.0
1040 * @return array<string,string> An associative array containing the error message and a detailed message.
1041 */
1042 public function recaptcha_error_message( $type, $api_response ) {
1043
1044 if ( empty( $api_response['error-codes'] ) || ! is_array( $api_response['error-codes'] ) ) {
1045 return [
1046 'detail_message' => __( 'Captcha validation failed. No error code provided.', 'sureforms' ),
1047 'message' => __( 'Captcha validation failed.', 'sureforms' ),
1048 ];
1049 }
1050
1051 /**
1052 * Note: The error codes are not translated because these messages are intended for debugging purposes.
1053 * Translating them would make debugging difficult. These error messages are primarily for developers or administrators.
1054 * A generic message will be displayed to the user, while detailed error information will be logged or shown in the console.
1055 */
1056
1057 // Google reCAPTCHA error codes.
1058 // Reference: (https://developers.google.com/recaptcha/docs/verify#error-code-reference).
1059 $google_recaptcha_error = [
1060 'missing-input-secret' => 'The secret parameter is missing.',
1061 'invalid-input-secret' => 'The secret parameter is invalid or malformed.',
1062 'missing-input-response' => 'The response parameter is missing.',
1063 'invalid-input-response' => 'The response parameter is invalid or malformed.',
1064 'bad-request' => 'The request is invalid or malformed.',
1065 'timeout-or-duplicate' => 'The response is no longer valid: either is too old or has been used previously.',
1066 ];
1067
1068 // hCaptcha error codes.
1069 // Reference: (https://docs.hcaptcha.com/#siteverify-error-codes).
1070 $hcaptcha_errors = [
1071 'missing-input-secret' => 'Your secret key is missing.',
1072 'invalid-input-secret' => 'Your secret key is invalid or malformed.',
1073 'missing-input-response' => 'The response parameter (verification token) is missing.',
1074 'invalid-input-response' => 'The response parameter (verification token) is invalid or malformed.',
1075 'expired-input-response' => 'The response parameter (verification token) is expired. (120s default)',
1076 'already-seen-response' => 'The response parameter (verification token) was already verified once.',
1077 'bad-request' => 'The request is invalid or malformed.',
1078 'missing-remoteip' => 'The remoteip parameter is missing.',
1079 'invalid-remoteip' => 'The remoteip parameter is not a valid IP address or blinded value.',
1080 'not-using-dummy-passcode' => 'You have used a testing sitekey but have not used its matching secret.',
1081 'sitekey-secret-mismatch' => 'The sitekey is not registered with the provided secret.',
1082 ];
1083
1084 // Cloudflare Turnstile error codes.
1085 // Reference: (https://developers.cloudflare.com/turnstile/get-started/server-side-validation/).
1086 $cf_turnstile_errors = [
1087 'missing-input-secret' => 'The secret parameter was not passed.',
1088 'invalid-input-secret' => 'The secret parameter was invalid, did not exist, or is a testing secret key with a non-testing response.',
1089 'missing-input-response' => 'The response parameter (token) was not passed.',
1090 '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.',
1091 'bad-request' => 'The request was rejected because it was malformed.',
1092 '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.',
1093 'internal-error' => 'An internal error happened while validating the response. The request can be retried.',
1094 ];
1095
1096 $error_code = $api_response['error-codes'][0] ?? 'no-error-code';
1097
1098 $captcha_title = '';
1099 $captcha_message = '';
1100 switch ( $type ) {
1101 case 'g-recaptcha':
1102 $captcha_title = __( 'Google reCAPTCHA', 'sureforms' );
1103 $captcha_message = $google_recaptcha_error[ $error_code ];
1104 break;
1105 case 'hcaptcha':
1106 $captcha_title = __( 'hCaptcha', 'sureforms' );
1107 $captcha_message = $hcaptcha_errors[ $error_code ];
1108 break;
1109 case 'cf-turnstile':
1110 $captcha_title = __( 'Cloudflare Turnstile', 'sureforms' );
1111 $captcha_message = $cf_turnstile_errors[ $error_code ];
1112 break;
1113 default:
1114 $captcha_title = __( 'Unknown Captcha', 'sureforms' );
1115 $captcha_message = __( 'Invalid captcha type.', 'sureforms' );
1116 break;
1117 }
1118
1119 $detail_message = sprintf(
1120 '%s: %s <br> Error Code: %s',
1121 $captcha_title,
1122 $captcha_message ?? 'Unknown error occurred.',
1123 $error_code
1124 );
1125
1126 $message = sprintf(
1127 /* translators: %s is the captcha title. */
1128 __( '%s verification failed. Please contact your site administrator.', 'sureforms' ),
1129 $captcha_title
1130 );
1131
1132 return [
1133 'log_message' => $detail_message, // This variable is used for logging purposes, such as displaying detailed error information in the console on the front end.
1134 'message' => $message,
1135 ];
1136 }
1137
1138 /**
1139 * Process and sanitize SureForms field data from submitted form data.
1140 *
1141 * @param array<mixed> $form_data Raw form data from submission.
1142 *
1143 * @since 1.11.0
1144 * @return array Processed and sanitized submission data.
1145 */
1146 private function process_form_fields( $form_data ) {
1147 $submission_data = [];
1148
1149 $form_data_keys = array_keys( $form_data );
1150 $form_data_count = count( $form_data );
1151
1152 for ( $i = 0; $i < $form_data_count; $i++ ) {
1153 $key = strval( $form_data_keys[ $i ] );
1154
1155 /**
1156 * This will allow to pass only sureforms fields
1157 * checking -lbl- as thats mandatory for in key of sureforms fields.
1158 */
1159 if ( false === str_contains( $key, '-lbl-' ) ) {
1160 continue;
1161 }
1162
1163 $value = $form_data[ $key ];
1164
1165 $field_name = htmlspecialchars( str_replace( '_', ' ', $key ) );
1166
1167 $field_block_name = Helper::get_block_name_from_field( $field_name );
1168
1169 /**
1170 * Filters the field value during form submission processing.
1171 *
1172 * This filter allows the Pro plugin to process and modify field values before they are saved.
1173 * The Pro plugin can implement custom sanitization, validation and escaping logic for its
1174 * specialized field types. When this filter is used by Pro, the core plugin will skip its
1175 * default validation.
1176 *
1177 * @since 1.11.0
1178 *
1179 * @param mixed $value The raw field value from form submission.
1180 * @param array $field_data Field information array containing:
1181 * - 'field_name': The field name/key
1182 * - 'field_block_name': The block type identifier
1183 * @return array {
1184 * Processed field value data
1185 *
1186 * @type bool $is_processed Whether the value was processed by Pro plugin
1187 * @type mixed $value The processed and sanitized field value
1188 * }
1189 */
1190 $process_field_value = apply_filters(
1191 'srfm_process_field_value',
1192 $value,
1193 [
1194 'field_name' => $field_name,
1195 'field_block_name' => $field_block_name,
1196 ]
1197 );
1198
1199 if ( is_array( $process_field_value ) && ! empty( $process_field_value['is_processed'] ) && ! empty( $process_field_value['value'] ) ) {
1200 $submission_data[ $field_name ] = $process_field_value['value'];
1201 continue;
1202 }
1203
1204 /**
1205 * Need to remove this refactor array value handling.
1206 *
1207 * The current array-based value handling needs to be replaced with:
1208 * 1. Block-specific value processing based on block type.
1209 * 2. Move premium features to pro version.
1210 * 3. Implement value processing through filters for extensibility.
1211 *
1212 * This will improve code organization and maintainability while properly
1213 * separating free/pro functionality.
1214 */
1215
1216 // If the field is an array, encode the values. This is to add support for multi-upload field.
1217 if ( is_array( $value ) ) {
1218 $submission_data[ $field_name ] =
1219 array_map(
1220 static function ( $val ) {
1221 return rawurlencode( $val );
1222 },
1223 $value
1224 );
1225 } else {
1226 $submission_data[ $field_name ] = is_string( $value ) ? htmlspecialchars( $value ) : $value;
1227 }
1228 }
1229
1230 return apply_filters( 'srfm_before_prepare_submission_data', $submission_data );
1231 }
1232
1233 /**
1234 * Add From email and name in the header.
1235 *
1236 * @param array<mixed> $submission_data Submission data.
1237 * @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'.
1238 * @param Smart_Tags $smart_tags Smart Tags instance.
1239 * @since 1.6.1
1240 * @return string The formatted "From" email header.
1241 */
1242 private static function add_from_data_in_header( $submission_data, $item, $smart_tags ) {
1243 $from_name = is_array( $item ) && ! empty( $item['from_name'] ) ? sanitize_text_field( Helper::get_string_value( $item['from_name'] ) ) : '{site_title}';
1244 $from_email = is_array( $item ) && ! empty( $item['from_email'] ) ? Helper::get_string_value( $item['from_email'] ) : '{admin_email}';
1245
1246 // Check if the email contains smart tags. If not, validate the email.
1247 $is_valid_email = true;
1248 if ( ! str_contains( $from_email, '{' ) && ! str_contains( $from_email, '}' ) ) {
1249 $is_valid_email = filter_var( $from_email, FILTER_VALIDATE_EMAIL );
1250 }
1251 // if the email is not valid, set it to the admin email.
1252 if ( ! $is_valid_email ) {
1253 $from_email = Helper::get_string_value( get_option( 'admin_email' ) );
1254 }
1255
1256 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";
1257 }
1258 }
1259