<?php
/**
 * Handle Form Submit.
 *
 * @since 3.1
 * @package fusion-builder
 */

/**
 * Handle Form Submit.
 *
 * @since 3.1
 */
class Fusion_Form_Submit {

	/**
	 * The reCAPTCHA class instance
	 *
	 * @access public
	 * @var bool|object
	 */
	public $re_captcha = false;

	/**
	 * Whats the error?
	 *
	 * @access public
	 * @var string
	 */
	public $captcha_error = '';

	/**
	 * ReCapatcha error flag.
	 *
	 * @access public
	 * @var bool
	 */
	public $has_error = false;

	/**
	 * Array of uploaded files, used to add email attachments.
	 *
	 * @access protected
	 * @var array
	 */
	protected $uploads = [];

	/**
	 * Email errors.
	 *
	 * @access public
	 * @var string
	 */
	public $mail_error = false;

	/**
	 * Database Submission ID.
	 *
	 * @access public
	 * @var string
	 */
	protected $db_submission_id = null;

	/**
	 * Form admin capability, used to decide who can see the debug info.
	 *
	 * @access public
	 * @var string
	 */
	protected $admin_cap = 'edit_others_posts';

	/**
	 * Initializes hooks, filters and administrative functions.
	 *
	 * @since 3.1
	 * @access public
	 */
	public function __construct() {

		add_action( 'wp_ajax_fusion_form_submit_ajax', [ $this, 'ajax_form_submit' ] );
		add_action( 'wp_ajax_nopriv_fusion_form_submit_ajax', [ $this, 'ajax_form_submit' ] );
	}

	/**
	 * Form submission will be stored in the database.
	 *
	 * @access protected
	 * @since 3.1.1
	 * @param array $data Form data array.
	 * @return void
	 */
	protected function submit_form_to_database( $data ) {

		if ( ! $data ) {
			$data = $this->get_submit_data();
		}

		// Email errors.
		if ( $this->mail_error ) {
			$data['submission']['data']['email_errors'] = wp_json_encode( $this->mail_error );
		}

		$data['submission']['data'] = isset( $data['submission']['data'] ) && is_array( $data['submission']['data'] ) ? wp_json_encode( $data['submission']['data'] ) : null;

		$fusion_forms    = new Fusion_Form_DB_Forms();
		$submission      = new Fusion_Form_DB_Submissions();
		$submission_data = [
			'form_id'            => $data['submission']['form_id'],
			'time'               => $data['submission']['time'],
			'source_url'         => $data['submission']['source_url'],
			'post_id'            => $data['submission']['post_id'],
			'user_id'            => $data['submission']['user_id'],
			'device_type'        => $data['submission']['device_type'],
			'user_agent'         => $data['submission']['user_agent'],
			'ip'                 => $data['submission']['ip'],
			'is_read'            => $data['submission']['is_read'],
			'privacy_scrub_date' => $data['submission']['privacy_scrub_date'],
			'on_privacy_scrub'   => $data['submission']['on_privacy_scrub'],
			'data'               => $data['submission']['data'],
		];
		$submission_id = $submission->insert( $submission_data );

		// Add 'device'type' column on the fly, if upgrade script did not run.
		if ( is_wp_error( $submission_id ) && false !== strpos( $submission_id->get_error_message(), 'device_type' ) ) {
			Fusion_Form_Builder()->add_device_type_to_submissions_table();
			$submission_id = $submission->insert( $submission_data );
		}

		foreach ( $data['data'] as $field => $value ) {
			$field_data  = ( is_array( $value ) ) ? implode( ' | ', $value ) : $value;
			$field_label = isset( $data['field_labels'][ $field ] ) ? $data['field_labels'][ $field ] : '';
			$db_field_id = $fusion_forms->insert_form_field( $data['submission']['form_id'], $field, $field_label );
			$entries     = new Fusion_Form_DB_Entries();

			$entries->insert(
				[
					'form_id'       => absint( $data['submission']['form_id'] ),
					'submission_id' => absint( $submission_id ),
					'field_id'      => sanitize_key( $db_field_id ),
					'value'         => $field_data,
					'privacy'       => in_array( $field, $data['fields_holding_privacy_data'], true ),
				]
			);
			$this->db_submission_id = absint( $submission_id );
		}
	}

	/**
	 * Form submission will be sent to email.
	 *
	 * @access protected
	 * @since 3.1.1
	 * @param array $data Form data array.
	 * @return bool
	 */
	protected function submit_form_to_email( $data ) {

		if ( ! $data ) {
			$data = $this->get_submit_data();
		}

		$form_post_id   = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$form_meta      = Fusion_Builder_Form_Helper::fusion_form_get_form_meta( $form_post_id );
		$to             = ! empty( $form_meta['email'] ) ? $form_meta['email'] : get_option( 'admin_email' );
		$reply_to_email = ! empty( $form_meta['email_reply_to'] ) ? $form_meta['email_reply_to'] : '';
		$from_name      = ! empty( $form_meta['email_from'] ) ? $form_meta['email_from'] : 'WordPress';
		$default_email  = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
		$from_id        = ! empty( $form_meta['email_from_id'] ) ? $form_meta['email_from_id'] : $default_email;
		$subject        = ! empty( $form_meta['email_subject'] ) ? $form_meta['email_subject'] : sprintf(
			/* Translators: The form-ID. */
			esc_html__( '%s - Form Submission Notification', 'fusion-builder' ),
			get_the_title( $form_post_id )
		);

		$subject_encode     = ! empty( $form_meta['email_subject_encode'] ) ? $form_meta['email_subject_encode'] : 0;
		$attachments        = [];
		$use_attachments    = 'yes' === fusion_data()->post_meta( $form_post_id )->get( 'email_attachments' ) ? true : false;
		$hidden_field_names = isset( $data['hidden_field_names'] ) && is_array( $data['hidden_field_names'] ) ? $data['hidden_field_names'] : [];

		// We don't want internal email fields to sent in email.
		$data = $this->remove_internal_email_fields( $data );

		$email_data = '';
		foreach ( $data['data'] as $field => $value ) {

			// Don't add fields which are hidden by form logic.
			if ( in_array( $field, $hidden_field_names, true ) ) {
				continue;
			}

			// Don't add attachments to email body.
			if ( true === $use_attachments && isset( $this->uploads[ $field ] ) ) {
				continue;
			}

			$value       = is_array( $value ) ? implode( ' | ', $value ) : $value;
			$field_label = isset( $data['field_labels'][ $field ] ) && '' !== $data['field_labels'][ $field ] ? $data['field_labels'][ $field ] : Fusion_Builder_Form_Helper::fusion_name_to_label( $field );

			$email_data .= '<tr>';
			$email_data .= '<th align="left">' . htmlentities( $field_label, ENT_COMPAT, 'UTF-8' ) . '</th>';

			if ( isset( $data['field_types'][ $field ] ) && 'textarea' === $data['field_types'][ $field ] ) {
				$email_data .= '<td>' . nl2br( htmlentities( $value, ENT_COMPAT, 'UTF-8' ) ) . '</td>';
			} else {
				$email_data .= '<td>' . htmlentities( $value, ENT_COMPAT, 'UTF-8' ) . '</td>';
			}
			$email_data .= '</tr>';

			// Replace placholders.
			if ( '' !== $to && false !== strpos( $to, '[' . $field . ']' ) ) {
				$to = str_replace( '[' . $field . ']', $value, $to );
			}

			if ( '' !== $reply_to_email && false !== strpos( $reply_to_email, '[' . $field . ']' ) ) {
				$reply_to_email = str_replace( '[' . $field . ']', $value, $reply_to_email );
			}

			if ( false !== strpos( $from_name, '[' . $field . ']' ) ) {
				$from_name = str_replace( '[' . $field . ']', $value, $from_name );
			}

			if ( false !== strpos( $from_id, '[' . $field . ']' ) ) {
				$from_id = str_replace( '[' . $field . ']', $value, $from_id );
			}

			if ( false !== strpos( $subject, '[' . $field . ']' ) ) {
				$subject = str_replace( '[' . $field . ']', $value, $subject );
			}
		}

		$title   = htmlentities( $subject );
		$message = "<html><head><title>$title</title></head><body><table cellspacing='4' cellpadding='4' align='left'>$email_data</table></body></html>";

		$headers = 'Content-type: text/html; charset=UTF-8' . "\r\n";
		$headers .= 'From: ' . $from_name . ' <' . $from_id . '>' . "\r\n";

		if ( '' !== $reply_to_email ) {
			$headers .= 'Reply-to: ' . $reply_to_email . "\r\n";
		}

		if ( $subject_encode ) {
			$subject = '=?utf-8?B?' . base64_encode( $subject ) . '?=';
		}

		// Add attachments if there are uploaded files.
		if ( true === $use_attachments && ! empty( $this->uploads ) ) {
			foreach ( $this->uploads as $field_name ) {
				foreach ( $field_name as $upload ) {
					$attachments[] = $upload['file'];
				}
			}
		}

		$sendmail_args = apply_filters(
			'fusion_form_send_mail_args',
			[
				'to'          => $to,
				'subject'     => $subject,
				'message'     => $message,
				'headers'     => $headers,
				'attachments' => $attachments,
			],
			$data['submission']['form_id'],
			$data
		);

		add_action( 'wp_mail_failed', [ $this, 'mail_failed_error' ] );

		$sendmail = wp_mail(
			$sendmail_args['to'],
			$sendmail_args['subject'],
			$sendmail_args['message'],
			$sendmail_args['headers'],
			$sendmail_args['attachments']
		);

		remove_action( 'wp_mail_failed', [ $this, 'mail_failed_error' ] );

		return $sendmail;
	}

	/**
	 * URL Action.
	 *
	 * @access public
	 * @since 3.7
	 * @param array $data Form data array.
	 * @param array $args Ajax data.
	 * @return array
	 */
	public function form_to_url_action( $data, $args ) {

		// Get the form-ID.
		$form_id = 0;
		if ( isset( $args['form_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
			$form_id = absint( str_replace( 'fusion-form-', '', sanitize_text_field( wp_unslash( $args['form_id'] ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}

		if ( $form_id && 'fusion_form' === get_post_type( $form_id ) ) {

			// GET URL and method from post meta.
			$request_url    = fusion_data()->post_meta( $form_id )->get( 'action' );
			$request_method = fusion_data()->post_meta( $form_id )->get( 'url_method' );

			// Error if no URL was found.
			if ( ! $request_url ) {
				return $this->get_results_from_message( 'error', 'no_url' );
			}

			$request_args = [
				'method' => 'POST',
			];

			// Get the form method.
			if ( '' !== $request_method ) {
				$request_args['method'] = strtoupper( $request_method );

				// Fallback in case we don't have a valid value.
				if ( ! in_array( $request_args['method'], [ 'POST', 'GET', 'HEAD', 'PUT', 'DELETE' ], true ) ) {
					$request_args['method'] = 'POST';
				}
			}

			// Add the submission arguments to our request.
			$request_args['body']            = wp_parse_args( $data['data'], $data['submission'] );
			$request_args['body']['form_id'] = $form_id;

			// Add custom headers if defined.
			$custom_headers = fusion_data()->post_meta( $form_id )->get( 'custom_headers' );
			if ( ! empty( $custom_headers ) && is_string( $custom_headers ) ) {
				$custom_headers = json_decode( $custom_headers, true );
			}

			if ( ! empty( $custom_headers ) ) {
				$request_args['headers'] = [];
				foreach ( $custom_headers as $header ) {
					$request_args['headers'][ $header['header_key'] ] = $header['header_value'];
				}
			}

			// Make the request.
			$response = wp_safe_remote_request( $request_url, $request_args );

			if ( ! is_wp_error( $response ) && isset( $response['body'] ) ) {
				$data['response_body'] = ( is_string( $response['body'] ) ) ? $response['body'] : wp_json_encode( $response['body'] );
				$type                  = $this->get_response_type_string( $response );

				return $this->get_results_from_message( $type, $data['response_body'] );
			}
		}
		return $this->get_results_from_message( 'error', __( 'URL Failed', 'fusion-builder' ) );
	}

	/**
	 * Ajax callback for form submission.
	 *
	 * @access public
	 * @since 3.7
	 * @return void
	 */
	public function ajax_form_submit() {

		$form_post_id = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$responses     = [];
		$admin_success = [];
		$success       = [];
		$admin_errors  = [];
		$errors        = [];

		// Checks nonce, recaptcha and similar. Dies if checks fail.
		$this->pre_process_form_submit();

		$data = $this->get_submit_data();

		$actions       = $this->handle_form_actions( $data, $form_post_id, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification
		$notifications = $this->handle_form_notifications( $data, $form_post_id );

		// Submission ID must be added here, amd not earlier.
		$submission_id = ! is_null( $this->db_submission_id ) ? $this->db_submission_id  : '';
		$data['submission']['submission_id'] = $submission_id;
		$this->db_submission_id = null;

		$responses = array_merge( $responses, $notifications, $actions );

		if ( ! empty( $responses ) ) {
			foreach ( $responses as $key => $res ) {

				if ( isset( $res['status'] ) && 'error' === $res['status'] ) {
					$admin_errors[ $key ] = isset( $res['info'] ) ? $res['info'] : 'form_failed';

					if ( isset( $res['force_display'] ) && $res['force_display'] ) {
						$errors[ $key ] = isset( $res['info'] ) ? $res['info'] : 'form_failed';
					}
				}

				if ( isset( $res['status'] ) && 'success' === $res['status'] ) {
					$admin_success[ $key ] = isset( $res['info'] ) ? $res['info'] : 'form_submitted';

					if ( isset( $res['force_display'] ) && $res['force_display'] ) {
						$success[ $key ] = isset( $res['info'] ) ? $res['info'] : 'form_submitted';
					}					
				}
			}
		}

		// Handle errors. $admin_errors contains all errors, while $erros only the ones that everyone will see.
		if ( ! empty( $admin_errors ) ) {

			// If current user can edit forms display the detailed errors.
			if ( is_user_logged_in() && current_user_can( $this->admin_cap ) ) {
				$message = $this->get_results_from_message( 'error', $admin_errors );
			} else {
				if ( ! empty( $errors ) ) {
					$message = $this->get_results_from_message( 'error', $errors );
				} else {
					$message = $this->get_results_from_message( 'error', 'form_failed' );
				}
			}
			die( wp_json_encode( apply_filters( 'awb_form_return_message', $message, $form_post_id, $errors ) ) );
		}

		// If form submitted successfully increase the form entries.
		$fusion_forms = new Fusion_Form_DB_Forms();
		$fusion_forms->increment_submissions_count( $data['submission']['form_id'] );

		// If current user can edit forms he can see the details in inspector network tab.
		if ( is_user_logged_in() && current_user_can( $this->admin_cap ) ) {
			$message = $this->get_results_from_message( 'success', $admin_success );
		} else {
			if ( ! empty( $success ) ) {
				$message = $this->get_results_from_message( 'success', $success );
			} else {
				$message = $this->get_results_from_message( 'success', 'form_submitted' );
			}
		}

		$message['submissionMetaData'] = $data['submission'];
		$message['submissionData']     = $data['data'];

		die( wp_json_encode( apply_filters( 'awb_form_return_message', $message, $form_post_id, $success ) ) );
	}

	/**
	 * Form notifications.
	 *
	 * @access protected
	 * @since 3.7
	 * @param array $data Form data array.
	 * @param array $id Form Post ID.
	 * @return bool
	 */
	protected function handle_form_notifications( $data, $id ) {

		if ( ! $data ) {
			$data = $this->get_submit_data();
		}

		$output        = [];
		$notifications = fusion_data()->post_meta( $id )->get( 'notifications' );
		if ( $notifications && is_string( $notifications ) && 5 < strlen( $notifications ) ) {
			$notifications = json_decode( $notifications, true );
		}

		if ( is_array( $notifications ) ) {
			$i = 1;
			foreach ( $notifications as $notification ) {
				if ( isset( $notification['conditional_logic'] ) ) {
					$notification['render_logics']  = $notification['conditional_logic'];
					$notification['current_values'] = $data['data'];

					if ( ! Fusion_Builder_Conditional_Render_Helper::should_render( $notification ) ) {
						continue;
					}
				}

				$to             = ! empty( $notification['email'] ) ? $notification['email'] : get_option( 'admin_email' );
				$reply_to_email = ! empty( $notification['email_reply_to'] ) ? $notification['email_reply_to'] : '';
				$from_name      = ! empty( $notification['email_from'] ) ? $notification['email_from'] : 'WordPress';
				$default_email  = 'wordpress@' . preg_replace( '#^www\.#', '', wp_parse_url( network_home_url(), PHP_URL_HOST ) );
				$from_id        = ! empty( $notification['email_from_id'] ) ? $notification['email_from_id'] : $default_email;
				$subject        = ! empty( $notification['email_subject'] ) ? $notification['email_subject'] : sprintf(
					/* Translators: The form-ID. */
					esc_html__( '%s - Form Submission Notification', 'fusion-builder' ),
					get_the_title( $id )
				);

				$subject_encode  = ! empty( $notification['email_subject_encode'] ) ? $notification['email_subject_encode'] : 0;
				$attachments     = [];
				$use_attachments = ! empty( $notification['email_attachments'] ) && 'yes' === $notification['email_attachments'] ? true : false;

				$notification_data = [
					'email_attachments'    => ! empty( $notification['email_attachments'] ) && 'yes' === $notification['email_attachments'] ? true : false,
					'email_label_wordwrap' => ! empty( $notification['email_label_wordwrap'] ) && 'yes' === $notification['email_label_wordwrap'] ? true : false,
				];

				$email_message = ! empty( $notification['email_message'] ) ? $this->custom_email_message( $notification['email_message'], $subject, $data ) : $this->default_email_message( $subject, $data, $notification_data );

				// Parse inline dynamic data, if used.
				if ( false !== strpos( $email_message, '{' ) ) {

					// Set up post data for the page the form was submitted on, so that dynamic
					// data callbacks (post title, custom fields, ACF, etc.) resolve correctly.
					$submission_post_id = ! empty( $data['submission']['post_id'] ) ? (int) $data['submission']['post_id'] : 0;
					if ( $submission_post_id ) {
						$submission_post = get_post( $submission_post_id );
						if ( $submission_post ) {
							setup_postdata( $GLOBALS['post'] = $submission_post ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride
						}
					}

					$email_message = FusionBuilder()->dynamic_data->parse_and_evaluate_inline_dynamic_data( $email_message );

					if ( $submission_post_id ) {
						wp_reset_postdata();
					}
				}

				$hidden_field_names = isset( $data['hidden_field_names'] ) && is_array( $data['hidden_field_names'] ) ? $data['hidden_field_names'] : [];

				// We don't want internal email fields to sent in email.
				$data = $this->remove_internal_email_fields( $data );

				foreach ( $data['data'] as $field => $value ) {

					// Don't add fields which are hidden by form logic.
					if ( in_array( $field, $hidden_field_names, true ) ) {
						continue;
					}

					// Don't add attachments to email body.
					if ( true === $notification_data['email_attachments'] && isset( $this->uploads[ $field ] ) ) {
						continue;
					}

					$value       = is_array( $value ) ? implode( ' | ', $value ) : $value;
					$field_label = isset( $data['field_labels'][ $field ] ) && '' !== $data['field_labels'][ $field ] ? $data['field_labels'][ $field ] : Fusion_Builder_Form_Helper::fusion_name_to_label( $field );

					// Replace placholders.
					if ( '' !== $to && false !== strpos( $to, '[' . $field . ']' ) ) {
						$to = str_replace( '[' . $field . ']', $value, $to );
					}

					if ( '' !== $reply_to_email && false !== strpos( $reply_to_email, '[' . $field . ']' ) ) {
						$reply_to_email = str_replace( '[' . $field . ']', $value, $reply_to_email );
					}

					if ( false !== strpos( $from_name, '[' . $field . ']' ) ) {
						$from_name = str_replace( '[' . $field . ']', $value, $from_name );
					}

					if ( false !== strpos( $from_id, '[' . $field . ']' ) ) {
						$from_id = str_replace( '[' . $field . ']', $value, $from_id );
					}

					if ( false !== strpos( $subject, '[' . $field . ']' ) ) {
						$subject = str_replace( '[' . $field . ']', $value, $subject );
					}
				}

				$headers = [
					'Content-type: text/html; charset=UTF8',
					 'From: ' . $from_name . ' <' . $from_id . '>',
				];

				if ( isset( $notification['email_cc'] ) ) {
					$additional_emails = explode( "\n", $notification['email_cc'] );
					foreach ( $additional_emails as $email_cc ) {
						preg_match( '/\[.+\]/', $email_cc, $matches );
						if ( isset( $matches[0] ) && isset( $data['data'][ str_replace( [ '[', ']' ], '', $matches[0] ) ] ) ) {
							$email_cc = $data['data'][ str_replace( [ '[', ']' ], '', $matches[0] ) ];
						}

						$headers[] = 'Cc: ' . $email_cc;
					}
				}

				if ( isset( $notification['email_bcc'] ) ) {
					$additional_emails = explode( "\n", $notification['email_bcc'] );
					foreach ( $additional_emails as $email_bcc ) {
						preg_match( '/\[.+\]/', $email_bcc, $matches );
						if ( isset( $matches[0] ) && isset( $data['data'][ str_replace( [ '[', ']' ], '', $matches[0] ) ] ) ) {
							$email_bcc = $data['data'][ str_replace( [ '[', ']' ], '', $matches[0] ) ];
						}

						$headers[] = 'Bcc: ' . $email_bcc;
					}
				}

				if ( '' !== $reply_to_email ) {
					$headers[] = 'Reply-to: ' . $reply_to_email;
				}

				if ( $subject_encode ) {
					$subject = '=?utf-8?B?' . base64_encode( $subject ) . '?=';
				}

				// Add attachments if there are uploaded files.
				if ( true === $notification_data['email_attachments'] && ! empty( $this->uploads ) ) {
					foreach ( $this->uploads as $field_name ) {
						foreach ( $field_name as $upload ) {
							$attachments[] = $upload['file'];
						}
					}
				}

				// Add in the custom notification attachment, if it is set.
				if ( isset( $notification['custom_attachment'] ) ) {
					$file_path = '';

					if ( is_array( $notification['custom_attachment'] ) ) {
						if ( isset( $notification['custom_attachment']['url'] ) ) {
							$base_url = Fusion_Images::get_attachment_base_url( $notification['custom_attachment']['url'] );

							if ( $base_url !== $notification['custom_attachment']['url'] ) {
								$upload_dir = wp_get_upload_dir();
								$file_path  = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $notification['custom_attachment']['url'] );
							} else if ( isset( $notification['custom_attachment']['id'] ) ) {
								$file_path = get_attached_file( (int) $notification['custom_attachment']['id'] );
							}
						}
					} else {
						$base_url = Fusion_Images::get_attachment_base_url( $notification['custom_attachment'] );

						if ( $base_url !== $notification['custom_attachment'] ) {
							$upload_dir = wp_get_upload_dir();
							$file_path  = str_replace( $upload_dir['baseurl'], $upload_dir['basedir'], $notification['custom_attachment'] );
						} else if ( isset( $notification['custom_attachment_id'] ) ) {
							$file_path = get_attached_file( (int) $notification['custom_attachment_id']);
						}
					}

					if ( $file_path ) {
						$attachments[] = $file_path;
					}
				}

				$sendmail_args = apply_filters(
					'fusion_form_send_mail_args',
					[
						'to'          => $to,
						'subject'     => $subject,
						'message'     => $email_message,
						'headers'     => $headers,
						'attachments' => $attachments,
					],
					$data['submission']['form_id'],
					$data
				);

				add_action( 'wp_mail_failed', [ $this, 'mail_failed_error' ] );

				$sendmail = wp_mail(
					$sendmail_args['to'],
					$sendmail_args['subject'],
					$sendmail_args['message'],
					$sendmail_args['headers'],
					$sendmail_args['attachments']
				);
				if ( $sendmail ) {
					if ( is_user_logged_in() && current_user_can( $this->admin_cap ) ) {
						$output[ 'email_' . $i ] = $this->get_results_from_message(
							'success',
							sprintf(
							/* translators: %s: Email to */
								__( 'Email to (%s) has been sent.', 'fusion-builder' ),
								$to
							)
						);
					} else {
						$output[ 'email_' . $i ] = $this->get_results_from_message( 'success', __( 'Email has been sent.', 'fusion-builder' ) );
					}
				} else {
					if ( is_user_logged_in() && current_user_can( $this->admin_cap ) && $this->mail_error ) {
						$output[ 'email_' . $i ] = $this->get_results_from_message( 'error', $this->mail_error );
					} else {
						$output[ 'email_' . $i ] = $this->get_results_from_message( 'error', __( 'Email Failed', 'fusion-builder' ) );
					}
				}
				remove_action( 'wp_mail_failed', [ $this, 'mail_failed_error' ] );

				$i++;
			}
		}

		return $output;
	}

	/**
	 * Form actions.
	 *
	 * @access protected
	 * @since 3.7
	 * @param array $data Form data array. Needs to be by reference, to update the data array in the auth endpoints.
	 * @param array $id Form Post ID.
	 * @param array $args Ajax data.
	 * @return array
	 */
	protected function handle_form_actions( &$data, $id, $args ) {

		if ( ! $data ) {
			$data = $this->get_submit_data();
		}

		$form_meta = Fusion_Builder_Form_Helper::fusion_form_get_form_meta( $id );

		$actions = $form_meta['form_actions'];

		// Backward Compatibility.
		$type = fusion_data()->post_meta( $id )->get( 'form_type' );

		if ( empty( $actions ) || ! is_array( $actions ) ) {
			if ( 'database' === $type ) {
				$actions = [ 'database' ];
			} elseif ( 'email' === $type ) {
				$actions = [ 'email' ];
			} elseif ( 'database_email' === $type ) {
				$actions = [ 'database', 'email' ];
			} elseif ( 'url' === $type ) {
				$actions = [ 'url' ];
			} else {
				return [];
			}
		}

		// Make sure the auth actions run first, so that we have the data.
		$actions_to_run_first = [ 'login', 'register', 'lost_password', 'reset_password' ];
		$front                = [];
		$back                 = [];
		foreach ( $actions as $action ) {
			if (in_array( $action, $actions_to_run_first, true ) ) {
				$front[] = $action;
			} else {
				$back[] = $action;
			}
		}
		$actions = array_merge ( $front, $back );

		$output = [];

		$custom_actions = (array) FusionBuilder()->get_custom_form_actions();

		foreach ( $actions as $action ) {

			// Save to database.
			if ( 'database' === $action ) {

				$this->submit_form_to_database( $data );

				if ( $data['submission']['form_id'] ) {
					$output['database'] = $this->get_results_from_message( 'success', __( 'Form data is saved to database.', 'fusion-builder' ) );
				} else {
					$output['database'] = $this->get_results_from_message( 'error', __( 'Saving form data to database failed.', 'fusion-builder' ) );
				}
			}

			// Send to url.
			if ( 'url' === $action ) {
				$output['url'] = $this->form_to_url_action( $data, $args );
			}

			// Email action for BC only.
			if ( 'email' === $action ) {
				$sendmail = $this->submit_form_to_email( $data );

				if ( $sendmail ) {
					$output['email'] = $this->get_results_from_message( 'success', __( 'Email has been sent.', 'fusion-builder' ) );
				} else {
					if ( is_user_logged_in() && current_user_can( $this->admin_cap ) && $this->mail_error ) {
						$output['email'] = $this->get_results_from_message( 'error', $this->mail_error );
					} else {
						$output['email'] = $this->get_results_from_message( 'error', __( 'Email Failed', 'fusion-builder' ) );
					}
				}
			}

			if ( 'login' === $action ) {
				$output[ $action ] = Fusion_Form_Auth_Actions()->process_login( $data, $id, $form_meta, $args );
			}

			if ( 'register' === $action ) {
				$output[ $action ] = Fusion_Form_Auth_Actions()->process_register( $data, $id, $form_meta, $args );
			}

			if ( 'lost_password' === $action ) {
				$output[ $action ] = Fusion_Form_Auth_Actions()->process_lost_password( $data, $id, $form_meta, $args );
			}

			if ( 'reset_password' === $action ) {
				$output[ $action ] = Fusion_Form_Auth_Actions()->process_reset_password( $data, $id, $form_meta, $args );
			}

			if ( isset( $custom_actions[ $action ]['callback'] ) ) {
				$output[ $action ] = call_user_func_array( $custom_actions[ $action ]['callback'], [ $data, $id, $form_meta, $args ] );
			}
		}

		return $output;
	}

	/**
	 * Form default email message.
	 *
	 * @access protected
	 * @since 3.7
	 * @param string $subject the subject.
	 * @param array  $data Form data array.
	 * @param array   $notification_data Additional notification data.
	 * @return bool
	 */
	protected function default_email_message( $subject, $data, $notification_data = [] ) {

		$email_data = $this->form_fields_table( $data, $notification_data );
		$title      = htmlentities( $subject );
		$message    = "<html><head><title>$title</title></head><body><table cellspacing='4' cellpadding='4' align='left'>$email_data</table></body></html>";

		return $message;
	}

	/**
	 * Form custom email message.
	 *
	 * @access protected
	 * @since 3.7
	 * @param string $message the custom message.
	 * @param string $subject the subject.
	 * @param array  $data Form data array.
	 * @return bool
	 */
	protected function custom_email_message( $message, $subject, $data ) {

		$title = htmlentities( $subject );

		// replace placeholders.
		$message = $this->replace_placeholders( $message, $data );

		$custom_message = "<html><head><title>$title</title></head><body><table cellspacing='4' cellpadding='4' align='left'>" . wpautop( $message ) . '</table></body></html>';

		return $custom_message;
	}

	/**
	 * Form fields as table.
	 *
	 * @access protected
	 * @since 3.7
	 * @param array $data Form data array.
	 * @param array   $notification_data Additional notification data.
	 * @return bool
	 */
	protected function form_fields_table( $data, $notification_data = [] ) {
		$hidden_field_names = isset( $data['hidden_field_names'] ) && is_array( $data['hidden_field_names'] ) ? $data['hidden_field_names'] : [];

		$table = '<table>';
		foreach ( $data['data'] as $field => $value ) {

			// Don't add fields which are hidden by form logic.
			if ( in_array( $field, $hidden_field_names, true ) ) {
				continue;
			}

			// Don't add attachments to email body if attachment enabled.
			if ( isset( $this->uploads[ $field ] ) && $notification_data['email_attachments'] ) {
				continue;
			}

			// Add uploaded files to the message body if attachment disabled.
			if ( isset( $this->uploads[ $field ] ) && ! $notification_data['email_attachments'] ) {
				if ( strpos( $value, '|' ) !== false ) {
					$value = explode( '|', $value );
				}

				if ( is_array( $value ) ) {
					$i     = 1;
					$links = [];
					foreach ( $value as $link ) {
						$links[] = '<a href="' . esc_url( trim( $link ) ) . '" target="_blank">' . esc_html__( 'View File', 'fusion-builder' ) . ' ' . $i . '</a>';
						$i++;
					}
					$value = join( ', ', $links );
				} else {
					$value = '<a href="' . esc_url( trim( $value ) ) . '" target="_blank">' . esc_html__( 'View File', 'fusion-builder' ) . '</a>';
				}
			} else {
				$value = is_array( $value ) ? implode( "\r\n", $value ) : $value;

				$value = htmlentities( $value, ENT_COMPAT, 'UTF-8' );
			}
			$field_label = isset( $data['field_labels'][ $field ] ) && '' !== $data['field_labels'][ $field ] ? $data['field_labels'][ $field ] : Fusion_Builder_Form_Helper::fusion_name_to_label( $field );
			$table .= '<tr>';
			$table .= '<th align="left" style="vertical-align:top;' . ( isset( $notification_data['email_label_wordwrap'] ) && ! $notification_data['email_label_wordwrap'] ? 'white-space:nowrap;' : '' ) . '">' . htmlentities( $field_label, ENT_COMPAT, 'UTF-8' ) . '</th>';

			if ( isset( $data['field_types'][ $field ] ) && ( 'textarea' === $data['field_types'][ $field ] || 'select' === $data['field_types'][ $field ] ) ) {
				$table .= '<td>' . nl2br( $value ) . '</td>';
			} else {
				$table .= '<td>' . $value . '</td>';
			}
			$table .= '</tr>';

		}
		$table .= '</table>';

		return $table;
	}

	/**
	 * Replace form placeholders.
	 *
	 * @access public
	 * @since 3.7
	 * @param string $text the subject.
	 * @param array  $data Form data array.
	 * @return bool
	 */
	public function replace_placeholders( $text, $data ) {
		preg_match_all( '/\[.*?\]/', $text, $matches );

		if ( ! empty( $matches[0] ) && is_array( $matches[0] ) ) {

			$output = $text;

			foreach ( $matches[0] as $placeholder ) {
				if ( '[all_fields]' === $placeholder ) {
					$output = str_replace( $placeholder, $this->form_fields_table( $data ), $output );
				} elseif ( '[source_url]' === $placeholder ) {
					$source_url = isset( $data['submission']['source_url'] ) ? $data['submission']['source_url'] : '';
					$output     = str_replace( $placeholder, $source_url, $output );
				} elseif ( '[submission_id]' === $placeholder ) {
					$submission_id = ! is_null( $this->db_submission_id ) ? $this->db_submission_id  : '';
					$output = str_replace( $placeholder, $submission_id, $output );
				} elseif ( '[user_agent]' === $placeholder ) {
					$user_agent = isset( $data['submission']['user_agent'] ) ? $data['submission']['user_agent'] : '';
					$output     = str_replace( $placeholder, $user_agent, $output );
				} elseif ( '[device_type]' === $placeholder ) {
					$device_type = isset( $data['submission']['device_type_output'] ) ? $data['submission']['device_type_output'] : '';
					$output     = str_replace( $placeholder, $device_type, $output );
				} elseif ( '[user_ip]' === $placeholder ) {
					$user_ip = isset( $data['submission']['ip'] ) ? $data['submission']['ip'] : '';
					$output  = str_replace( $placeholder, $user_ip, $output );
				} elseif ( '[submission_date]' === $placeholder ) {
					$date   = isset( $data['submission']['date_output'] ) ? $data['submission']['date_output'] : '';
					$output = str_replace( $placeholder, $date, $output );
				} elseif ( '[submission_time]' === $placeholder ) {
					$time   = isset( $data['submission']['time_output'] ) ? $data['submission']['time_output'] : '';
					$output = str_replace( $placeholder, $time, $output );
				} else {
					preg_match( '/\[(.*?)\]/', $placeholder, $tag );
					$field_name  = isset( $tag[1] ) ? $tag[1] : '';
					$field_value = $field_name && isset( $data['data'][ $field_name ] ) ? $data['data'][ $field_name ] : '';
					$field_value = is_array( $field_value ) ? implode( '|', $field_value ) : $field_value;
					$field_value = htmlentities( $field_value, ENT_COMPAT, 'UTF-8' );

					$output = str_replace( $placeholder, $field_value, $output );
				}
			}
		} else {
			$output = $text;
		}

		return $output;
	}

	/**
	 * Process nonce, recaptcha and similar checks.
	 * Dies if checks fail.
	 *
	 * @access protected
	 * @since 3.1.1
	 * @return void
	 */
	protected function pre_process_form_submit() {

		$form_post_id = isset( $_POST['form_id'] ) ? absint( $_POST['form_id'] ) : 0;
		$form_post    = get_post( $form_post_id );

		if ( ! $form_post || 'fusion_form' !== $form_post->post_type || 'publish' !== $form_post->post_status ) {
			die( wp_json_encode( $this->get_results_from_message( 'error', 'invalid_form' ) ) );
		}

		$form_meta = Fusion_Builder_Form_Helper::fusion_form_get_form_meta( $form_post_id );

		// Check for form type.
		if ( isset( $form_meta['form_type'] ) && 'ajax' !== $form_meta['form_type'] ) {
			die( wp_json_encode( $this->get_results_from_message( 'error', 'not allowed' ) ) );
		}

		if ( ! isset( $form_meta['nonce_method'] ) || 'none' !== $form_meta['nonce_method'] ) {
			// Verify the form submission nonce.
			check_ajax_referer( 'fusion_form_nonce-' . absint( $form_post_id ), 'fusion_form_nonce' );
		}

		// If we are in demo mode, just pretend it has sent.
		if ( apply_filters( 'fusion_form_demo_mode', false ) ) {
			die( wp_json_encode( $this->get_results_from_message( 'success', 'demo' ) ) );
		}

		// Check reCAPTCHA response and die if error.
		$this->check_recaptcha_response();

		// Check Turnstile response and die if error.
		$this->check_turnstile_response();
	}

	/**
	 * Proces nonce, recaptcha and similar checks.
	 * Dies if checks fail.
	 *
	 * @access protected
	 * @since 3.1.1
	 * @param array $data Form data array.
	 * @return array
	 */
	protected function remove_internal_email_fields( $data ) {

		// Remove data used for internal purpose, only for email submission type.
		if ( isset( $data['data']['fusion_form_email'] ) ) {
			unset( $data['data']['fusion_form_email'] );
		}

		if ( isset( $data['data']['fusion_form_email_from'] ) ) {
			unset( $data['data']['fusion_form_email_from'] );
		}

		if ( isset( $data['data']['fusion_form_email_from_id'] ) ) {
			unset( $data['data']['fusion_form_email_from_id'] );
		}

		if ( isset( $data['data']['fusion_form_email_subject'] ) ) {
			unset( $data['data']['fusion_form_email_subject'] );
		}

		if ( isset( $data['data']['fusion_form_email_subject_encode'] ) ) {
			unset( $data['data']['fusion_form_email_subject_encode'] );
		}

		return $data;
	}

	/**
	 * Get the submission data.
	 *
	 * @access public
	 * @since 3.1.0
	 * @return array
	 */
	public function get_submit_data() {

		$form_data          = wp_unslash( $_POST['formData'] ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
		$files              = isset( $_FILES ) && ! empty( $_FILES ) ? $_FILES : []; // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
		$field_labels       = (array) json_decode( stripcslashes( $_POST['field_labels'] ), true ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
		$field_types        = (array) json_decode( stripcslashes( $_POST['field_types'] ), true ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
		$hidden_field_names = (array) json_decode( stripcslashes( $_POST['hidden_field_names'] ), true ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput

		parse_str( $form_data, $form_data_array ); // phpcs:ignore WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput

		// Sanitize user input.
		foreach( $form_data_array as $key => $value ) {
			$type = isset( $field_types[ $key ] ) ? $field_types[ $key ] : '';
			$form_data_array[ $key ] = Fusion_Builder_Form_Helper::fusion_form_sanitize( $value, $type );
		}

		$form_post_id = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$files        = apply_filters( 'awb_form_upload_files', $files, $form_data );
		$uploads      = ! empty( $files ) ? $this->handle_upload( $files, $form_post_id ) : [];
		if ( ! empty( $uploads ) && is_array( $uploads ) ) {
			foreach ( $uploads as $upload_name => $upload_url ) {
				$upload_name                        = explode( '@|@', $upload_name );
				$form_data_array[ $upload_name[0] ] = ! empty( $form_data_array[ $upload_name[0] ] ) ? $form_data_array[ $upload_name[0] ] . ' | ' . $upload_url : $upload_url;
			}
		}

		$device_type = 'unknown';
		if ( fusion_library()->device_detection->is_mobile() ) {
			$device_type = 'mobile';
		} elseif ( fusion_library()->device_detection->is_tablet() ) {
			$device_type = 'tablet';
		} elseif ( ! wp_is_mobile() ) {
			$device_type = 'desktop';
		} elseif ( wp_is_mobile() ) {
			$device_type = 'mobile_tablet';
		}
		$device_types = [
			'unknown'       => __( 'Unknown', 'fusion-builder' ),
			'mobile'        => __( 'Mobile', 'fusion-builder' ),
			'tablet'        => __( 'Tablet', 'fusion-builder' ),
			'desktop'       => __( 'Desktop', 'fusion-builder' ),
			'mobile_tablet' => __( 'Mobile or Tablet', 'fusion-builder' ),
		];

		$user_agent = '';
		if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
			$user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
		}
		$source_url = '';
		if ( isset( $_SERVER['HTTP_REFERER'] ) ) {
			$source_url = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) );
		}
		$ip = '';
		if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
		}
		if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
		}
		$post_id      = isset( $_POST['post_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$form_post_id = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification

		$fusion_forms = new Fusion_Form_DB_Forms();
		$form_id      = $fusion_forms->insert(
			[
				'form_id' => $form_post_id, // phpcs:ignore WordPress.Security.NonceVerification
				'views'   => 0,
			]
		);

		$submission_time = gmdate( 'Y-m-d H:i:s' );
		$date_format     = fusion_library()->get_option( 'date_format' ) ? fusion_library()->get_option( 'date_format' ) : get_option( 'date_format' );
		$date_output     = date( $date_format, strtotime( $submission_time ) );
		$time_format     = get_option( 'time_format' );
		$time_output     = date( $time_format, strtotime( $submission_time ) );

		$data = [
			'submission'         => [
				'form_id'            => absint( $form_id ),
				'time'               => gmdate( 'Y-m-d H:i:s' ),
				'date_output'        => $date_output,
				'time_output'        => $time_output,
				'source_url'         => sanitize_text_field( $source_url ),
				'post_id'            => absint( $post_id ),
				'user_id'            => absint( get_current_user_id() ),
				'device_type'        => $device_type,
				'device_type_output' => $device_types[ $device_type ],
				'user_agent'         => sanitize_text_field( $user_agent ),
				'ip'                 => sanitize_text_field( $ip ),
				'is_read'            => false,
				'privacy_scrub_date' => gmdate( 'Y-m-d' ),
				'on_privacy_scrub'   => 'ignore',
			],
			'data'               => $form_data_array,
			'field_labels'       => $field_labels,
			'field_types'        => $field_types,
			'hidden_field_names' => $hidden_field_names,
		];

		// Allow filtering the submission data.
		$data = apply_filters( 'fusion_builder_form_submission_data', $data );

		$fields_holding_privacy_data = [];
		if ( isset( $data['data']['fusion-fields-hold-private-data'] ) ) {
			$fields_holding_privacy_data = explode( ',', $data['data']['fusion-fields-hold-private-data'] );
			unset( $data['data']['fusion-fields-hold-private-data'] );
		}

		$data['fields_holding_privacy_data'] = $fields_holding_privacy_data;

		unset( $data['data']['fusion_privacy_store_ip_ua'] );
		unset( $data['data']['fusion_privacy_expiration_interval'] );
		unset( $data['data']['privacy_expiration_action'] );
		unset( $data['data'][ 'fusion-form-nonce-' . $form_post_id ] );

		if ( isset( $data['data']['g-recaptcha-response'] ) ) {
			unset( $data['data']['g-recaptcha-response'] );
		}

		if ( isset( $data['data']['fusion-form-recaptcha-response'] ) ) {
			unset( $data['data']['fusion-form-recaptcha-response'] );
		}

		$data['data'] = array_filter( $data['data'], fn( $key ) => ! str_starts_with( $key, 'cf-turnstile-response' ), ARRAY_FILTER_USE_KEY );

		// HubSpot data options. Add do_action here for further extensions.
		if ( class_exists( 'Fusion_Hubspot' ) && 'contact' === fusion_data()->post_meta( $form_post_id )->get( 'hubspot_action' ) ) {
			if ( '' !== get_post_meta( $form_post_id, 'form_hubspot_map', true ) ) {
				$hubspot_response = Fusion_Hubspot()->submit_form( $data, $field_labels, $form_post_id );
			} else {
				$hubspot_response = Fusion_Hubspot()->create_contact( $data, fusion_data()->post_meta( $form_post_id )->get( 'hubspot_map' ), $field_labels );
			}

			$data['submission']['data']['hubspot_response'] = ! empty( $hubspot_response ) ? $hubspot_response : wp_json_encode( [ 'notice' => __( 'Not connected to HubSpot API.', 'fusion-builder' ) ] );
		}

		// Mailchimp data options.  Add do_action here for further extensions.
		if ( class_exists( 'Fusion_Mailchimp' ) && 'contact' === fusion_data()->post_meta( $form_post_id )->get( 'mailchimp_action' ) && $this->check_mailchimp_consent( $form_post_id, $data ) ) { // here.
			$data['submission']['data']['mailchimp_response'] = Fusion_Mailchimp()->create_contact( $data, $form_post_id, $field_labels );
		}

		do_action( 'fusion_form_submission_data', $data, $form_post_id );

		return $data;
	}

	/**
	 * Checks MailChimp consent.
	 *
	 * @access protected
	 * @since 3.9
	 * @param int   $form_post_id Form post ID.
	 * @param array $data Form   data array.
	 * @return bool
	 */
	protected function check_mailchimp_consent( $form_post_id, $data ) {
		$option  = fusion_data()->post_meta( $form_post_id )->get( 'mailchimp_consent' );
		$consent = true;

		if ( ! empty( $option ) && ( ! isset( $data['data'][ $option ] ) || ! $data['data'][ $option ] ) ) {
			$consent = false;
		}

		return $consent;
	}

	/**
	 * Check the reCAPTCHA response and die if error.
	 *
	 * @access protected
	 * @since 3.1
	 * @return void
	 */
	protected function check_recaptcha_response() {

		$form_post_id = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$form_data    = Fusion_Builder_Form_Helper::fusion_get_form_post_content( $form_post_id );

		if ( ! isset( $_POST['g-recaptcha-response'] ) && isset( $form_data['content'] ) && false !== strpos( $form_data['content'], '[fusion_form_recaptcha' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			$this->has_error     = true;
			$this->captcha_error = __( 'reCAPTCHA could not verify that you are a human.', 'fusion-builder' );
			$results             = [
				'status'  => 'error',
				'captcha' => 'failed',
				'info'    => [ 'captcha' => $this->captcha_error ],
				'message' => $this->captcha_error,
			];
			$this->has_error     = false;
			die( wp_json_encode( $results ) );
		}

		if ( isset( $_POST['g-recaptcha-response'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
			$this->process_recaptcha( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification
			if ( $this->has_error ) {
				$results         = [
					'status'  => 'error',
					'captcha' => 'failed',
					'info'    => [ 'captcha' => $this->captcha_error ],
					'message' => $this->captcha_error,
				];
				$this->has_error = false;
				die( wp_json_encode( $results ) );
			}
		}
	}

	/**
	 * Check reCAPTCHA.
	 *
	 * @since 3.1
	 * @access private
	 * @return void
	 */
	private function process_recaptcha() {
		$response = class_exists( 'AWB_Google_Recaptcha' ) ? AWB_Google_Recaptcha::get_instance()->verify() : [];
		if ( is_array( $response ) && $response['has_error'] && $response['message'] ) {
			$this->has_error     = $response['has_error'];
			$this->captcha_error = $response['message'];
		}
	}

	/**
	 * Check the Turnstile response and die if error.
	 *
	 * @access protected
	 * @since 3.13.2
	 * @return void
	 */
	protected function check_turnstile_response() {
		$form_post_id = isset( $_POST['form_id'] ) ? absint( sanitize_text_field( wp_unslash( $_POST['form_id'] ) ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification
		$form_data    = Fusion_Builder_Form_Helper::fusion_get_form_post_content( $form_post_id );		
		$results      = [
			'status'    => 'error',
			'turnstile' => 'failed',
			'info'      => [ 'turnstile' => __( 'The Turnstile challenge could not be completed.', 'fusion-builder' ) ],
			'message'   => __( 'The Turnstile challenge could not be completed.', 'fusion-builder' ),
		];

		if ( empty( $_POST['cf-turnstile-response'] ) && isset( $form_data['content'] ) && false !== strpos( $form_data['content'], '[fusion_form_turnstile' ) ) {
			die( wp_json_encode( $results ) );
		}

		if ( ! empty( $_POST['cf-turnstile-response'] ) ) {
			$response = wp_remote_post( 'https://challenges.cloudflare.com/turnstile/v0/siteverify', [
				'body' => [
					'secret'   => fusion_library()->get_option( 'turnstile_secret_key' ),
					'response' => sanitize_text_field( $_POST['cf-turnstile-response'] ),
					'remoteip' => $_SERVER['REMOTE_ADDR'],
				],
			] );

			if ( is_wp_error( $response ) ) {
				$message                       =  __( 'The Cloudflare server did not respond.', 'fusion-builder' );
				$results['info']['turnstile'] .= ' ' . $message;
				$results['message']           .= ' ' . $message;
				die( wp_json_encode( $results ) );
			}

			$result = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( empty( $result['success'] ) ) {
				$message                       =  __( 'The token verification failed.', 'fusion-builder' );
				$results['info']['turnstile'] .= ' ' . $message;
				$results['message']           .= ' ' . $message;
				die( wp_json_encode( $results ) );
			}
		}
	}

	/**
	 * Handles the file upload using wp native function.
	 *
	 * @since 3.1
	 * @param array $files The uploaded files array.
	 * @return array $moved_files Array containing uploaded files data or the error.
	 */
	public function handle_upload( $files, $form_id = 0 ) {
		$uploaded_files = [];
		$moved_files    = [];

		$form_post      = get_post( $form_id );
		$allowed_fields = [];
		if ( $form_post ) {
			preg_match_all( '/\[fusion_form_upload\s+([^\]]+)\]/i', $form_post->post_content, $matches );
			if ( ! empty( $matches[1] ) ) {
				foreach ( $matches[1] as $attributes_str ) {
					$atts = shortcode_parse_atts( rtrim( $attributes_str, '/' ) );
					if ( isset( $atts['name'] ) ) {
						$extensions_str = isset( $atts['extensions'] ) ? strtolower( $atts['extensions'] ) : '';
						$extensions     = preg_split( '/[\s,|]+/', $extensions_str, -1, PREG_SPLIT_NO_EMPTY );
						$extensions     = array_map( function( $ext ) {
							return ltrim( $ext, '.' );
						}, $extensions );

						$allowed_fields[ $atts['name'] ] = [
							'extensions' => $extensions,
							'max_size'   => isset( $atts['upload_size'] ) ? (float) $atts['upload_size'] : 2.0,
						];
					}
				}
			}
		}

		foreach ( $files as $file ) {
			foreach ( $file as $key => $data ) {
				foreach ( $data as $key2 => $file_data ) {
					$uploaded_files[ $key2 ][ $key ] = $file_data;
				}
			}
		}

		if ( ! function_exists( 'wp_handle_upload' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}

		add_filter( 'sanitize_file_name', [ $this, 'randomize_name' ] );
		add_filter( 'upload_dir', [ $this, 'custom_upload_dir' ] );

		// Create form directory if not already there.
		$upload = wp_upload_dir( null, false );

		if ( ! file_exists( $upload['path'] ) ) {
			wp_mkdir_p( $upload['path'] );

			$index_file = @fopen( $upload['path'] . '/index.html', 'wb' );
			if ( $index_file ) {
				fclose( $index_file );
			}
		}

		foreach ( $uploaded_files as $field_name => $uploaded_file ) {
			$upload_name = explode( '@|@', $field_name );
			$base_name   = $upload_name[0];

			// Validate field name.
			if ( ! isset( $allowed_fields[ $base_name ] ) ) {
				die( wp_json_encode( $this->get_results_from_message( 'error', 'upload_failed' ) ) );
			}

			// Validate size.
			$max_size = isset( $allowed_fields[ $base_name ]['max_size'] ) ? $allowed_fields[ $base_name ]['max_size'] : 2.0;
			if ( $uploaded_file['size'] > ( $max_size * 1024 * 1024 ) ) {
				die( wp_json_encode( $this->get_results_from_message( 'error', 'upload_failed' ) ) );
			}

			// Validate extension.
			if ( ! empty( $allowed_fields[ $base_name ]['extensions'] ) ) {
				$ext = strtolower( pathinfo( $uploaded_file['name'], PATHINFO_EXTENSION ) );
				if ( ! in_array( $ext, $allowed_fields[ $base_name ]['extensions'], true ) ) {
					die( wp_json_encode( $this->get_results_from_message( 'error', 'upload_failed' ) ) );
				}
			}

			$upload_overrides = [
				'test_form' => false,
			];

			$move_file = wp_handle_upload( $uploaded_file, $upload_overrides );

			if ( $move_file && isset( $move_file['error'] ) ) {
				die( wp_json_encode( $this->get_results_from_message( 'error', 'upload_failed' ) ) );
			}
			$moved_files[ $field_name ] = $move_file['url'];

			// Build uploads, used for email attachments.
			if ( ! isset( $this->uploads[ $base_name ] ) ) {
				$this->uploads[ $base_name ] = [];
			}
			$this->uploads[ $base_name ][] = $move_file;
		}

		remove_filter( 'sanitize_file_name', [ $this, 'randomize_name' ] );
		remove_filter( 'upload_dir', [ $this, 'custom_upload_dir' ] );

		return $moved_files;
	}

	/**
	 * Change the upload location to a separate folder.
	 *
	 * @since 3.1
	 * @param array $dir Upload directory info.
	 * @return array
	 */
	public function custom_upload_dir( $dir = [] ) {
		$dir['path']   = $dir['basedir'] . '/fusion-forms';
		$dir['url']    = $dir['baseurl'] . '/fusion-forms';
		$dir['subdir'] = '/fusion-forms';
		return $dir;
	}

	/**
	 * Change upload file name to a random string.
	 *
	 * @since 3.1
	 * @param string $filename File name.
	 * @return string File name.
	 */
	public function randomize_name( $filename = '' ) {
		$ext = empty( pathinfo( $filename, PATHINFO_EXTENSION ) ) ? '' : '.' . pathinfo( $filename, PATHINFO_EXTENSION );

		try {
			$random_name = bin2hex( random_bytes( 16 ) );
		} catch ( Exception $e ) {
			$random_name = uniqid();
		}

		return apply_filters( 'awb_forms_upload_file_name', $random_name . $ext, $filename );
	}

	/**
	 * Get status string from status code.
	 *
	 * @access protected
	 * @since 3.2.1
	 * @param array $response The HTTP response array.
	 * @return string The status string.
	 */
	protected function get_response_type_string( $response ) {
		$code  = (string) wp_remote_retrieve_response_code( $response );
		$types = [
			'1' => 'info',
			'2' => 'success',
			'3' => 'redirect',
			'4' => 'client_error',
			'5' => 'server_error',
		];

		return isset( $types[ $code[0] ] ) ? $types[ $code[0] ] : 'error';
	}

	/**
	 * Get results message.
	 *
	 * @access protected
	 * @param string $type Can be success|error.
	 * @param string $info Type of success/error.
	 * @return string
	 */
	protected function get_results_from_message( $type, $info ) {
		return [
			'status' => $type,
			'info'   => $info,
		];
	}

	/**
	 * Adds mail failure error.
	 *
	 * @access protected
	 * @param object $wp_error WordPress error object.
	 * @return void
	 */
	public function mail_failed_error( $wp_error ) {
		if ( is_wp_error( $wp_error ) && isset( $wp_error->errors ) ) {
			$this->mail_error = isset( $wp_error->errors['wp_mail_failed'] ) ? $wp_error->errors['wp_mail_failed'] : false;
		}
	}
}
