PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 1.6.2
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v1.6.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
989 lines 32.8 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' => '__return_true',
77 ]
78 );
79 }
80
81 /**
82 * Check whether a given request has permission access route.
83 *
84 * @since 0.0.1
85 * @return WP_Error|bool
86 */
87 public function permissions_check() {
88 if ( ! current_user_can( 'manage_options' ) ) {
89 return new WP_Error( 'rest_forbidden', __( 'Sorry, you cannot access this route', 'sureforms' ), [ 'status' => rest_authorization_required_code() ] );
90 }
91 return true;
92 }
93
94 /**
95 * Validate Turnstile token
96 *
97 * @param string $secret_key Turnstile token.
98 * @param string|false $response Response.
99 * @param string|false $remote_ip Remote IP.
100 * @return array<mixed>|mixed Result of the validation.
101 */
102 public static function validate_turnstile_token( $secret_key, $response, $remote_ip ) {
103
104 if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
105 return [
106 'success' => false,
107 'error' => __( 'Cloudflare Turnstile secret key is invalid.', 'sureforms' ),
108 ];
109 }
110
111 if ( empty( $response ) ) {
112 return [
113 'success' => false,
114 'error' => __( 'Cloudflare Turnstile response is missing.', 'sureforms' ),
115 ];
116 }
117
118 $body = [
119 'secret' => $secret_key,
120 'response' => $response,
121 'remoteip' => $remote_ip,
122 ];
123
124 $url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
125
126 $args = [
127 'body' => $body,
128 'timeout' => 15,
129 ];
130
131 $response = wp_remote_post( $url, $args );
132
133 if ( is_wp_error( $response ) ) {
134 $error_message = $response->get_error_message();
135 return [
136 'success' => false,
137 'error' => $error_message,
138 ];
139 }
140
141 return json_decode( wp_remote_retrieve_body( $response ), true );
142 }
143
144 /**
145 * Validate hCaptcha token
146 *
147 * @param string $secret_key hCaptcha token.
148 * @param string|false $response Response.
149 * @param string|false $remote_ip Remote IP.
150 * @since 0.0.5
151 * @return array<mixed>|mixed Result of the validation.
152 */
153 public static function validate_hcaptcha_token( $secret_key, $response, $remote_ip ) {
154
155 if ( empty( $secret_key ) || ! is_string( $secret_key ) ) {
156 return [
157 'success' => false,
158 'error' => __( 'hCaptcha secret key is invalid.', 'sureforms' ),
159 ];
160 }
161
162 if ( empty( $response ) ) {
163 return [
164 'success' => false,
165 'error' => __( 'hCaptcha response is missing.', 'sureforms' ),
166 ];
167 }
168
169 $body = [
170 'secret' => $secret_key,
171 'response' => $response,
172 'remoteip' => $remote_ip,
173 ];
174
175 $url = 'https://api.hcaptcha.com/siteverify';
176
177 $args = [
178 'body' => $body,
179 'timeout' => 15,
180 ];
181
182 $response = wp_remote_post( $url, $args );
183
184 if ( is_wp_error( $response ) ) {
185 $error_message = $response->get_error_message();
186 return [
187 'success' => false,
188 'error' => $error_message,
189 ];
190 }
191
192 return json_decode( wp_remote_retrieve_body( $response ), true );
193 }
194
195 /**
196 * Handle Form Submission
197 *
198 * @param \WP_REST_Request $request Request object or array containing form data.
199 * @since 0.0.1
200 * @return \WP_REST_Response|\WP_Error Response object on success, or WP_Error object on failure.
201 */
202 public function handle_form_submission( $request ) {
203 $nonce = Helper::get_string_value( $request->get_header( 'X-WP-Nonce' ) );
204
205 if ( ! wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ) ) {
206 wp_send_json_error(
207 [
208 'message' => __( 'Nonce verification failed.', 'sureforms' ),
209 ]
210 );
211 }
212
213 $form_data = Helper::sanitize_by_field_type( $request->get_params() );
214
215 if ( empty( $form_data ) || ! is_array( $form_data ) ) {
216 wp_send_json_error(
217 [
218 'message' => __( 'Form data is not found.', 'sureforms' ),
219 ]
220 );
221 }
222
223 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] && ! empty( $_FILES ) ) {
224 add_filter( 'upload_dir', [ $this, 'change_upload_dir' ] );
225
226 foreach ( $_FILES as $field => $file ) {
227 if ( is_array( $file['name'] ) ) {
228 foreach ( $file['name'] as $key => $filename ) {
229 $temp_path = $file['tmp_name'][ $key ];
230 $file_size = $file['size'][ $key ];
231 $file_type = $file['type'][ $key ];
232 $file_error = $file['error'][ $key ];
233
234 if ( ! $filename && ! $temp_path && ! $file_size && ! $file_type ) {
235 $form_data[ $field ][] = '';
236 continue;
237 }
238
239 $uploaded_file = [
240 'name' => $filename,
241 'type' => $file_type,
242 'tmp_name' => $temp_path,
243 'error' => $file_error,
244 'size' => $file_size,
245 ];
246
247 $upload_overrides = [
248 'test_form' => false,
249 ];
250 $move_file = wp_handle_upload( $uploaded_file, $upload_overrides );
251 remove_filter( 'upload_dir', [ $this, 'change_upload_dir' ] );
252
253 if ( $move_file && ! isset( $move_file['error'] ) ) {
254 $form_data[ $field ][] = $move_file['url'];
255 } else {
256 wp_send_json_error(
257 [
258 'message' => __( 'File is not uploaded', 'sureforms' ),
259 ]
260 );
261 }
262 }
263 } else {
264 $form_data[ $field ][] = '';
265 }
266 }
267 }
268
269 if ( ! $form_data['form-id'] ) {
270 wp_send_json_error(
271 [
272 'message' => __( 'Form Id is missing.', 'sureforms' ),
273 'position' => 'header',
274 ]
275 );
276 }
277 $current_form_id = $form_data['form-id'];
278 $security_type = Helper::get_meta_value( Helper::get_integer_value( $current_form_id ), '_srfm_captcha_security_type' );
279 $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 ) ) : '';
280
281 if ( 'none' !== $security_type ) {
282 $global_setting_options = get_option( 'srfm_security_settings_options' );
283 } else {
284 $global_setting_options = [];
285 }
286
287 if ( 'g-recaptcha' === $security_type ) {
288 switch ( $selected_captcha_type ) {
289 case 'v2-checkbox':
290 $key = 'srfm_v2_checkbox_secret_key';
291 break;
292 case 'v2-invisible':
293 $key = 'srfm_v2_invisible_secret_key';
294 break;
295 case 'v3-reCAPTCHA':
296 $key = 'srfm_v3_secret_key';
297 break;
298 default:
299 $key = '';
300 break;
301 }
302
303 $google_captcha_secret_key = is_array( $global_setting_options ) && isset( $global_setting_options[ $key ] ) ? $global_setting_options[ $key ] : '';
304 }
305
306 if ( 'cf-turnstile' === $security_type ) {
307 // Turnstile validation.
308 $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'] ) : '';
309 $cf_response = ! empty( $form_data['cf-turnstile-response'] ) ? $form_data['cf-turnstile-response'] : false;
310
311 // if gdpr is enabled then set remote ip to empty.
312 $compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
313 $gdpr = false;
314
315 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
316 $gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
317 }
318
319 // check if ip logging is disabled in global settings then set remote ip to empty.
320 $gb_general_settinionsgs_opt = get_option( 'srfm_general_settings_options' );
321 $srfm_ip_log = is_array( $gb_general_settinionsgs_opt ) && isset( $gb_general_settinionsgs_opt['srfm_ip_log'] ) ? $gb_general_settinionsgs_opt['srfm_ip_log'] : '';
322
323 $remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
324
325 $turnstile_validation_result = self::validate_turnstile_token( $srfm_cf_turnstile_secret_key, $cf_response, $remote_ip );
326
327 // If the cloudflare validation fails, return an error.
328 if ( is_array( $turnstile_validation_result ) && isset( $turnstile_validation_result['success'] ) && false === $turnstile_validation_result['success'] ) {
329 $error_message = $turnstile_validation_result['error'] ?? __( 'Cloudflare Turnstile validation failed.', 'sureforms' );
330 wp_send_json_error(
331 [
332 'message' => $error_message,
333 ]
334 );
335 }
336 }
337
338 if ( 'hcaptcha' === $security_type ) {
339 $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'] ) : '';
340 $hcaptcha_response = ! empty( $form_data['h-captcha-response'] ) ? $form_data['h-captcha-response'] : false;
341
342 // if gdpr is enabled then set remote ip to empty.
343 $compliance = get_post_meta( Helper::get_integer_value( $current_form_id ), '_srfm_compliance', true );
344 $gdpr = false;
345
346 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
347 $gdpr = ! empty( $compliance[0]['gdpr'] ) ? $compliance[0]['gdpr'] : false;
348 }
349
350 // check if ip logging is disabled in global settings then set remote ip to empty.
351 $gb_general_settings_options = get_option( 'srfm_general_settings_options' );
352 $srfm_ip_log = is_array( $gb_general_settings_options ) && isset( $gb_general_settings_options['srfm_ip_log'] ) ? $gb_general_settings_options['srfm_ip_log'] : '';
353
354 $remote_ip = $gdpr || ( ! $srfm_ip_log ) ? '' : ( isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '' );
355 $hcaptcha_validation_result = self::validate_hcaptcha_token( $srfm_hcaptcha_secret_key, $hcaptcha_response, $remote_ip );
356
357 // If the hcaptcha validation fails, return an error.
358 if ( is_array( $hcaptcha_validation_result ) && isset( $hcaptcha_validation_result['success'] ) && false === $hcaptcha_validation_result['success'] ) {
359 $error_message = $hcaptcha_validation_result['error'] ?? __( 'hCaptcha validation failed.', 'sureforms' );
360 wp_send_json_error(
361 [
362 'message' => $error_message,
363 ]
364 );
365 }
366 }
367
368 if ( isset( $form_data['srfm-honeypot-field'] ) && empty( $form_data['srfm-honeypot-field'] ) ) {
369 if ( ! empty( $google_captcha_secret_key ) ) {
370 if ( isset( $form_data['sureforms_form_submit'] ) ) {
371 $secret_key = $google_captcha_secret_key;
372 $ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
373 $captcha_response = $form_data['g-recaptcha-response'];
374 $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
375
376 $response = wp_remote_get( $url );
377
378 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
379 $json_string = wp_remote_retrieve_body( $response );
380 $data = (array) json_decode( $json_string, true );
381 } else {
382 $data = [];
383 }
384 $sureforms_captcha_data = $data;
385
386 } else {
387 wp_send_json_error(
388 [
389 'message' => __( 'reCAPTCHA error.', 'sureforms' ),
390 ]
391 );
392 }
393 if ( isset( $sureforms_captcha_data['success'] ) && true === $sureforms_captcha_data['success'] ) {
394 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
395 }
396
397 wp_send_json_error(
398 [
399 'message' => __( 'reCAPTCHA error.', 'sureforms' ),
400 ]
401 );
402
403 }
404
405 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
406 }
407
408 if ( ! isset( $form_data['srfm-honeypot-field'] ) ) {
409 if ( ! empty( $google_captcha_secret_key ) ) {
410 if ( isset( $form_data['sureforms_form_submit'] ) ) {
411 $secret_key = $google_captcha_secret_key;
412 $ipaddress = isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
413 $captcha_response = $form_data['g-recaptcha-response'];
414 $url = 'https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_key . '&response=' . $captcha_response . '&ip=' . $ipaddress;
415
416 $response = wp_remote_get( $url );
417
418 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
419 $json_string = wp_remote_retrieve_body( $response );
420 $data = (array) json_decode( $json_string, true );
421 } else {
422 $data = [];
423 }
424 $sureforms_captcha_data = $data;
425
426 } else {
427 wp_send_json_error(
428 [
429 'message' => __( 'reCAPTCHA error.', 'sureforms' ),
430 ]
431 );
432 }
433 if ( true === $sureforms_captcha_data['success'] ) {
434 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
435 }
436
437 wp_send_json_error(
438 [
439 'message' => __( 'reCAPTCHA error.', 'sureforms' ),
440 ]
441 );
442 }
443
444 return rest_ensure_response( $this->handle_form_entry( $form_data ) );
445 }
446
447 wp_send_json_error(
448 [
449 'message' => __( 'Spam Detected', 'sureforms' ),
450 ]
451 );
452 }
453
454 /**
455 * Change the upload directory
456 *
457 * @param array<mixed> $dirs upload directory.
458 * @return array<mixed>
459 * @since 0.0.1
460 */
461 public function change_upload_dir( $dirs ) {
462 $dirs['subdir'] = '/sureforms';
463 $dirs['path'] = $dirs['basedir'] . $dirs['subdir'];
464 $dirs['url'] = $dirs['baseurl'] . $dirs['subdir'];
465 return $dirs;
466 }
467
468 /**
469 * Send Email and Create Entry.
470 *
471 * @param array<string> $form_data Request object or array containing form data.
472 * @since 0.0.1
473 * @return array<mixed> Array containing the response data.
474 */
475 public function handle_form_entry( $form_data ) {
476
477 $id = sanitize_text_field( $form_data['form-id'] );
478
479 // Get the compliance settings.
480 $compliance = get_post_meta( Helper::get_integer_value( $id ), '_srfm_compliance', true );
481 $gdpr = '';
482 $do_not_store_entries = '';
483
484 if ( is_array( $compliance ) && is_array( $compliance[0] ) ) {
485 $gdpr = $compliance[0]['gdpr'] ?? '';
486 $do_not_store_entries = $compliance[0]['do_not_store_entries'] ?? '';
487 }
488
489 // Check if the form data contains 'srfm_addresses' and is not empty.
490 if ( ! empty( $form_data['srfm_addresses'] ) ) {
491 // Assign the addresses to the class property for further processing.
492 $this->addresses = $form_data['srfm_addresses'];
493 // Remove the address data from the form data to avoid redundancy.
494 unset( $form_data['srfm_addresses'] );
495 }
496
497 $submission_data = [];
498
499 $form_data_keys = array_keys( $form_data );
500 $form_data_count = count( $form_data );
501
502 for ( $i = 0; $i < $form_data_count; $i++ ) {
503 $key = strval( $form_data_keys[ $i ] );
504
505 /**
506 * This will allow to pass only sureforms fields
507 * checking -lbl- as thats mandatory for in key of sureforms fields.
508 */
509 if ( false === str_contains( $key, '-lbl-' ) ) {
510 continue;
511 }
512
513 $value = $form_data[ $key ];
514
515 $field_name = htmlspecialchars( str_replace( '_', ' ', $key ) );
516
517 // If the field is an array, encode the values. This is to add support for multi-upload field.
518 if ( is_array( $value ) ) {
519 $submission_data[ $field_name ] =
520 array_map(
521 static function ( $val ) {
522 return rawurlencode( $val );
523 },
524 $value
525 );
526 } else {
527 $submission_data[ $field_name ] = htmlspecialchars( $value );
528 }
529 }
530
531 $submission_data = apply_filters( 'srfm_before_prepare_submission_data', $submission_data );
532
533 $modified_message = $this->prepare_submission_data( $submission_data );
534
535 $form_before_submission_data = [
536 'form_id' => $id ? intval( $id ) : '',
537 'data' => $modified_message,
538 ];
539
540 /**
541 * Fires before submission process starts.
542 */
543 do_action( 'srfm_before_submission', $form_before_submission_data );
544
545 $name = sanitize_text_field( get_the_title( intval( $id ) ) );
546 $send_email = $this->send_email( $id, $submission_data, $form_data );
547 $emails = [];
548
549 if ( $send_email ) {
550 $emails = $send_email['emails'];
551 }
552
553 // Check if GDPR is enabled and do not store entries is enabled.
554 // If so, send email and do not store entries.
555 if ( $gdpr && $do_not_store_entries ) {
556
557 $form_submit_response = [
558 'success' => true,
559 'form_id' => $id ? intval( $id ) : '',
560 'to_emails' => $emails,
561 'form_name' => $name ? esc_attr( $name ) : '',
562 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
563 'data' => $modified_message,
564 ];
565
566 do_action( 'srfm_form_submit', $form_submit_response );
567
568 /**
569 * Hook for enabling background processes.
570 *
571 * @param array $form_data form data related to submission.
572 */
573 $form_data['form_id'] = $id ? intval( $id ) : '';
574 do_action( 'srfm_after_submission_process', $form_data );
575
576 return [
577 'success' => true,
578 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
579 'data' => [
580 'name' => $name,
581 'after_submit' => false,
582 ],
583 'redirect_url' => Generate_Form_Markup::get_redirect_url( $form_data, $submission_data ),
584 ];
585
586 }
587
588 $global_setting_options = get_option( 'srfm_general_settings_options' );
589
590 // If GDPR is enabled, do not store IP, browser, and device info.
591 // If not, store IP, browser, and device info.
592 $user_ip = '';
593 $browser_name = '';
594 $device_name = '';
595 if ( ! $gdpr ) {
596 $srfm_ip_log = is_array( $global_setting_options ) && isset( $global_setting_options['srfm_ip_log'] ) ? $global_setting_options['srfm_ip_log'] : '';
597
598 $user_ip = $srfm_ip_log && isset( $_SERVER['REMOTE_ADDR'] ) ? filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) : '';
599 $browser = new Browser();
600 $browser_name = sanitize_text_field( $browser->getBrowser() );
601 $device_name = sanitize_text_field( $browser->getPlatform() );
602 }
603
604 $form_markup = get_the_content( null, false, Helper::get_integer_value( $form_data['form-id'] ) );
605 $pattern = '/"label":"(.*?)"/';
606 preg_match_all( $pattern, $form_markup, $matches );
607 $submission_info = [
608 'user_ip' => $user_ip,
609 'browser_name' => $browser_name,
610 'device_name' => $device_name,
611 ];
612 $entries_data = [
613 'form_id' => $id,
614 'form_data' => $submission_data,
615 'submission_info' => $submission_info,
616 ];
617 if ( is_user_logged_in() ) {
618 // If user is logged in then save their user id.
619 $entries_data['user_id'] = get_current_user_id();
620 }
621 $entry_id = Entries::add( $entries_data );
622 if ( $entry_id ) {
623
624 $response = [
625 'success' => true,
626 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
627 'data' => [
628 'name' => $name,
629 'submission_id' => $entry_id,
630 'after_submit' => true,
631 ],
632 'redirect_url' => Generate_Form_Markup::get_redirect_url( $form_data, $submission_data ),
633 ];
634
635 $form_submit_response = apply_filters(
636 'srfm_form_submit_response',
637 [
638 'success' => true,
639 'form_id' => $id ? intval( $id ) : '',
640 'entry_id' => intval( $entry_id ),
641 'to_emails' => $emails,
642 'form_name' => $name ? esc_attr( $name ) : '',
643 'message' => Generate_Form_Markup::get_confirmation_markup( $form_data, $submission_data ),
644 'data' => $modified_message,
645 ]
646 );
647
648 do_action( 'srfm_form_submit', $form_submit_response );
649 } else {
650 $response = [
651 'success' => false,
652 'message' => __( 'Error submitting form', 'sureforms' ),
653 ];
654 }
655
656 return $response;
657 }
658
659 /**
660 * Prepare submission data.
661 *
662 * @param array<mixed> $submission_data Submission data.
663 * @since 0.0.7
664 * @return array<mixed> Modified submission data.
665 */
666 public function prepare_submission_data( $submission_data ) {
667 $modified_message = [];
668 foreach ( $submission_data as $key => $value ) {
669 $parts = explode( '-lbl-', $key );
670 $label = '';
671
672 if ( ! empty( $parts[1] ) ) {
673 $tokens = explode( '-', $parts[1] );
674 if ( count( $tokens ) > 1 ) {
675 $label = implode( '-', array_slice( $tokens, 1 ) );
676 }
677
678 $fields = explode( '-', $parts[0] );
679
680 // Since the upload field returns an array of file URLs, we need to implode them with a comma.
681 if ( 'upload' === $fields[1] && ! empty( $value ) && is_array( $value ) ) {
682 $modified_message[ $label ] = urldecode( implode( ', ', $value ) );
683 } else {
684 $modified_message[ $label ] = html_entity_decode( esc_attr( Helper::get_string_value( $value ) ) );
685 }
686 }
687 }
688
689 // If the address is not empty, add it to the submission data.
690 // We are providing this for third-party integrations like Ottokit.
691 // They can use compact addresses such as permanent address, temporary address, etc.
692 // The address will be structured as field 1, field 2, and so on.
693 if ( ! empty( $this->addresses ) ) {
694 // Address will be JSON stringified, so decode it.
695 $address = json_decode( wp_unslash( $this->addresses ), true );
696 if ( ! empty( $address ) && is_array( $address ) ) {
697 $modified_message = array_merge( $modified_message, $address );
698 }
699 }
700
701 return $modified_message;
702 }
703
704 /**
705 * Parse an email notification template and generate the necessary components for sending an email.
706 *
707 * @param array<mixed> $submission_data An associative array containing submission data to be used in the email template.
708 * @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'.
709 * @param array<string> $form_data Request object or array containing form data.
710 * @since 1.3.0
711 * @return array<string,string> An associative array containing 'to', 'subject', 'message', and 'headers' for the email.
712 */
713 public static function parse_email_notification_template( $submission_data, $item, $form_data = [] ) {
714 $smart_tags = Smart_Tags::get_instance();
715
716 $to = $smart_tags->process_smart_tags( $item['email_to'], $submission_data );
717 $subject = $smart_tags->process_smart_tags( $item['subject'], $submission_data, $form_data );
718 $email_body = $smart_tags->process_smart_tags( $item['email_body'], $submission_data, $form_data );
719 $email_template = new Email_Template();
720 $message = $email_template->render( $submission_data, $email_body );
721 $headers = 'X-Mailer: PHP/' . phpversion() . "\r\n";
722 $headers .= "Content-Type: text/html; charset=utf-8\r\n";
723
724 // Add the From: to the headers.
725 $headers .= self::add_from_data_in_header( $submission_data, $item, $smart_tags );
726
727 if ( isset( $item['email_reply_to'] ) && ! empty( $item['email_reply_to'] ) ) {
728 $headers .= 'Reply-To:' . $smart_tags->process_smart_tags( $item['email_reply_to'], $submission_data ) . "\r\n";
729 }
730 if ( isset( $item['email_cc'] ) && ! empty( $item['email_cc'] ) ) {
731 $headers .= 'Cc:' . $smart_tags->process_smart_tags( $item['email_cc'], $submission_data ) . "\r\n";
732 }
733 if ( isset( $item['email_bcc'] ) && ! empty( $item['email_bcc'] ) ) {
734 $headers .= 'Bcc:' . $smart_tags->process_smart_tags( $item['email_bcc'], $submission_data ) . "\r\n";
735 }
736
737 return compact( 'to', 'subject', 'message', 'headers' );
738 }
739
740 /**
741 * Send Email.
742 *
743 * @param string $id Form ID.
744 * @param array<mixed> $submission_data Submission data.
745 * @param array<string> $form_data Request object or array containing form data.
746 * @since 0.0.1
747 * @return array<mixed> Array containing the response data.
748 */
749 public static function send_email( $id, $submission_data, $form_data = [] ) {
750 $email_notification = get_post_meta( intval( $id ), '_srfm_email_notification' );
751 $is_mail_sent = false;
752 $emails = [];
753
754 // Filter to determine whether the email notification should be sent.
755 $email_notification = apply_filters( 'srfm_email_notification_should_send', $email_notification, $submission_data, $form_data );
756
757 if ( is_iterable( $email_notification ) ) {
758 $entries_db_instance = Entries::get_instance();
759 $log_key = $entries_db_instance->add_log( __( 'Email notification passed to the sending server', 'sureforms' ) );
760
761 foreach ( $email_notification as $notification ) {
762 foreach ( $notification as $item ) {
763 if ( true === $item['status'] ) {
764
765 $parsed = self::parse_email_notification_template( $submission_data, $item, $form_data );
766
767 // Allow filtering of the email data before it is sent.
768 $parsed = apply_filters( 'srfm_email_notification', $parsed, $submission_data, $item, $form_data );
769
770 // Trigger an action before sending the email, allowing additional processing or logging.
771 do_action( 'srfm_before_email_send', $parsed, $submission_data, $item, $form_data );
772
773 /**
774 * Temporary override the content type for wp_mail.
775 * This helps us from breaking of content type from other plugins.
776 *
777 * @since 1.2.2
778 */
779 add_filter(
780 'wp_mail_content_type',
781 static function() {
782 return 'text/html'; // We need "text/html" content type to render our emails.
783 },
784 99
785 );
786
787 /**
788 * Start sending email.
789 * Wrapping it in the buffer because when some plugin such as zoho mail, overrides the wp_mail
790 * function and any exception is thrown ( Or printed ) from that plugin side, it affects the JSON response.
791 * So, to make sure such exceptions doesn't affect our JSON response, we are wrapping it inside buffer.
792 *
793 * Try-Catch does not work because the notice or errors might be echoed by other plugins rather than thrown as an exception.
794 *
795 * @since 1.2.2
796 */
797 $sent = false;
798 ob_start();
799 $sent = wp_mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
800 if ( ! $sent ) {
801 // Fallback to default PHP mail if for some reasons wp_mail fails.
802 $sent = mail( $parsed['to'], $parsed['subject'], $parsed['message'], $parsed['headers'] );
803 }
804 $email_report = ob_get_clean(); // Catch any printed notice/errors/message for reports.
805
806 if ( is_int( $log_key ) ) {
807 if ( true === $sent ) {
808 $entries_db_instance->update_log(
809 $log_key,
810 null,
811 [
812 /* translators: Here, %s is the comma separated emails list. */
813 sprintf( __( 'Email notification recipient: %s', 'sureforms' ), esc_html( $parsed['to'] ) ),
814 ]
815 );
816 } else {
817 $entries_db_instance->update_log(
818 $log_key,
819 null,
820 [
821 sprintf(
822 /* translators: Here, %1$s is the comma separated emails list and %2$s is error report ( if any ). */
823 __( 'Email server was unable to send the email notification. Recipient: %1$s. Reason: %2$s', 'sureforms' ),
824 esc_html( $parsed['to'] ),
825 ! empty( $email_report ) ? esc_html( $email_report ) : esc_html__( 'Unknown', 'sureforms' )
826 ),
827 ]
828 );
829 }
830 }
831
832 // Trigger an action after the email is sent, allowing additional processing or logging.
833 do_action(
834 'srfm_after_email_send',
835 $parsed,
836 $submission_data,
837 $item,
838 $form_data
839 );
840
841 $is_mail_sent = $sent;
842 $emails[] = $parsed['to'];
843 }
844 }
845 }
846
847 if ( empty( $emails ) ) {
848 $entries_db_instance->add_log( __( 'No emails were sent', 'sureforms' ) );
849 }
850 }
851
852 return [
853 'success' => $is_mail_sent,
854 'emails' => $emails,
855 ];
856 }
857
858 /**
859 * Retrieve all entries data for a specific form ID to check for unique values.
860 *
861 * @since 0.0.1
862 * @return void
863 */
864 public function field_unique_validation() {
865 if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'unique_validation_nonce' ) ) {
866 $error_message = __( 'Nonce verification failed.', 'sureforms' );
867 $error_data = [
868 'error' => $error_message,
869 ];
870 wp_send_json_error( $error_data );
871 }
872
873 global $wpdb;
874 $id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;
875 $meta_value = $id;
876
877 if ( ! $meta_value ) {
878 $error_message = __( 'Invalid form ID.', 'sureforms' );
879 $error_data = [
880 'error' => $error_message,
881 ];
882 wp_send_json_error( $error_data );
883 }
884
885 $_POST = array_map( 'wp_unslash', $_POST );
886
887 // Get the entry IDs for the particualr form to perform unique field validation.
888 $entry_ids = Entries::get_all_entry_ids_for_form( $id );
889
890 $all_form_entries = [];
891 $keys = array_keys( $_POST );
892 $length = count( $keys );
893
894 for ( $i = 3; $i < $length; $i++ ) {
895 $key = $keys[ $i ];
896 $value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
897 $key = str_replace( '_', ' ', $keys[ $i ] );
898
899 foreach ( $entry_ids as $entry_id ) {
900 $entry_id = is_array( $entry_id ) ? Helper::get_integer_value( $entry_id['ID'] ) : 0;
901 $form_data = Entries::get_form_data( $entry_id );
902 if ( is_array( $form_data ) && isset( $form_data[ $key ] ) && $form_data[ $key ] === $value ) {
903 $obj = [ $key => 'not unique' ];
904 array_push( $all_form_entries, $obj );
905 break;
906 }
907 }
908 }
909
910 $results = [
911 'data' => $all_form_entries,
912 ];
913
914 wp_send_json( $results );
915 }
916
917 /**
918 * Function to save allowed block data.
919 *
920 * @since 0.0.1
921 * @return void
922 */
923 public function srfm_global_update_allowed_block() {
924 if ( ! current_user_can( 'manage_options' ) ) {
925 wp_send_json_error();
926 }
927
928 if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
929 wp_send_json_error();
930 }
931
932 if ( ! empty( $_POST['defaultAllowedQuickSidebarBlocks'] ) ) {
933 $srfm_default_allowed_quick_sidebar_blocks = json_decode( sanitize_text_field( wp_unslash( $_POST['defaultAllowedQuickSidebarBlocks'] ) ), true );
934 Helper::update_admin_settings_option( 'srfm_quick_sidebar_allowed_blocks', $srfm_default_allowed_quick_sidebar_blocks );
935 wp_send_json_success();
936 }
937 wp_send_json_error();
938 }
939
940 /**
941 * Function to save enable/disable data.
942 *
943 * @since 0.0.1
944 * @return void
945 */
946 public function srfm_global_sidebar_enabled() {
947 if ( ! current_user_can( 'manage_options' ) ) {
948 wp_send_json_error();
949 }
950
951 if ( ! check_ajax_referer( 'srfm_ajax_nonce', 'security', false ) ) {
952 wp_send_json_error();
953 }
954
955 if ( ! empty( $_POST['enableQuickActionSidebar'] ) ) {
956 $srfm_enable_quick_action_sidebar = ( 'enabled' === $_POST['enableQuickActionSidebar'] ? 'enabled' : 'disabled' );
957 Helper::update_admin_settings_option( 'srfm_enable_quick_action_sidebar', $srfm_enable_quick_action_sidebar );
958 wp_send_json_success();
959 }
960 wp_send_json_error();
961 }
962
963 /**
964 * Add From email and name in the header.
965 *
966 * @param array<mixed> $submission_data Submission data.
967 * @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'.
968 * @param Smart_Tags $smart_tags Smart Tags instance.
969 * @since 1.6.1
970 * @return string The formatted "From" email header.
971 */
972 private static function add_from_data_in_header( $submission_data, $item, $smart_tags ) {
973 $from_name = is_array( $item ) && ! empty( $item['from_name'] ) ? sanitize_text_field( Helper::get_string_value( $item['from_name'] ) ) : '{site_title}';
974 $from_email = is_array( $item ) && ! empty( $item['from_email'] ) ? Helper::get_string_value( $item['from_email'] ) : '{admin_email}';
975
976 // Check if the email contains smart tags. If not, validate the email.
977 $is_valid_email = true;
978 if ( ! str_contains( $from_email, '{' ) && ! str_contains( $from_email, '}' ) ) {
979 $is_valid_email = filter_var( $from_email, FILTER_VALIDATE_EMAIL );
980 }
981 // if the email is not valid, set it to the admin email.
982 if ( ! $is_valid_email ) {
983 $from_email = Helper::get_string_value( get_option( 'admin_email' ) );
984 }
985
986 return 'From: ' . esc_html( $smart_tags->process_smart_tags( $from_name, $submission_data ) ) . ' <' . esc_html( $smart_tags->process_smart_tags( $from_email, $submission_data ) ) . '>' . "\r\n";
987 }
988 }
989