HEX
Server: LiteSpeed
System: Linux server315.web-hosting.com 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: globfdxw (6114)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/globfdxw/www/wp-content/plugins/wpforms-conversational-forms/src/Frontend.php
<?php

namespace WPFormsConversationalForms;

use WP;
use WPForms_Field_File_Upload;
use WPForms_Field_Select;
use WPFormsConversationalForms\Helpers\Colors;
use WP_Post;

/**
 * Conversational Forms frontend functionality.
 *
 * @since 1.0.0
 */
class Frontend {

	/**
	 * Current form data.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	protected $form_data;

	/**
	 * Color helper instance.
	 *
	 * @since 1.0.0
	 *
	 * @var Colors
	 */
	public $colors;

	/**
	 * Color string.
	 *
	 * @since 1.7.1
	 *
	 * @var string
	 */
	private $color = '';

	/**
	 * Color scheme string.
	 *
	 * @since 1.7.1
	 *
	 * @var string
	 */
	private $color_scheme = '';

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		$this->colors = new Colors();

		$this->init();
	}

	/**
	 * Initialize.
	 *
	 * @since 1.0.0
	 */
	public function init(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		if ( wp_doing_ajax() ) {
			add_action( 'wpforms_ajax_submit_before_processing', [ $this, 'handle_ajax_request' ] );

			return;
		}

		add_action( 'parse_request', [ $this, 'handle_request' ] );
	}

	/**
	 * Handle the request.
	 *
	 * @since 1.0.0
	 *
	 * @param WP $wp WP instance.
	 */
	public function handle_request( $wp ): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( ! empty( $wp->query_vars['name'] ) ) {
			$request = $wp->query_vars['name'];
		}

		if ( empty( $request ) && ! empty( $wp->query_vars['pagename'] ) ) {
			$request = $wp->query_vars['pagename'];
		}

		if ( empty( $request ) ) {
			$request = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
			$request = ! empty( $request ) ? sanitize_key( basename( wp_parse_url( $request, PHP_URL_PATH ) ) ) : '';
		}

		$forms = [];

		if ( ! empty( $request ) ) {
			$form_handler = wpforms()->obj( 'form' );
			$args         = [
				'name' => $request,
			];

			// @WPFormsBackCompatStart User Generated Templates since WPForms v1.8.8
			if ( defined( get_class( $form_handler ?? '' ) . '::POST_TYPES' ) ) {
				$args['post_type'] = $form_handler::POST_TYPES;
			}
			// @WPFormsBackCompatEnd

			$forms = $form_handler->get( '', $args );
		}

		$form = ! empty( $forms[0] ) ? $forms[0] : null;

		if ( ! $this->is_conversational_form( $form ) ) {
			return;
		}

		// Form templates are only allowed for logged-in users that have permissions to view them.
		if ( $form->post_type === 'wpforms-template' && ! wpforms_current_user_can( 'view_forms' ) ) {
			return;
		}

		$form_data = wpforms_decode( $form->post_content );

		/**
		 * This filter allows overwriting form data for frontend handle request.
		 *
		 * @since 1.7.0
		 *
		 * @param array $form_data Form data array.
		 */
		$this->form_data = apply_filters( 'wpforms_conversational_forms_frontend_handle_request_form_data', $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		// Override page URLs with the same slug.
		if ( ! empty( $wp->query_vars['pagename'] ) ) {
			$wp->query_vars['name'] = $wp->query_vars['pagename'];

			unset( $wp->query_vars['pagename'] );
		}

		if ( empty( $wp->query_vars['name'] ) ) {
			$wp->query_vars['name'] = $request;
		}

		$wp->query_vars['post_type'] = $form->post_type;

		// Unset the 'error' query var that may appear if custom permalink structures used.
		unset( $wp->query_vars['error'] );

		/**
		 * Execute after detecting the Conversational Form.
		 *
		 * @since 1.5.9.5
		 *
		 * @param array   $form_data Form data array.
		 * @param WP_Post $form      Form data.
		 */
		do_action( 'wpforms_conversational_form_detected', $this->form_data, $form ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		// Enabled conversational form detected. Adding the hooks.
		$this->conversational_form_hooks();

		// Force the classic render engine for WPForms version starting from 1.8.1.
		if ( function_exists( 'wpforms_get_render_engine' ) ) {
			$frontend_obj = wpforms()->obj( 'frontend' );

			if ( $frontend_obj ) {
				$frontend_obj->init_render_engine( 'classic' );
			}
		}
	}

	/**
	 * Conversational form-specific hooks.
	 *
	 * @since 1.0.0
	 */
	public function conversational_form_hooks(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		add_filter( 'template_include', [ $this, 'get_form_template' ], PHP_INT_MAX );
		add_filter( 'document_title_parts', [ $this, 'change_form_page_title' ] );
		add_filter( 'post_type_link', [ $this, 'modify_permalink' ], 10, 2 );
		add_filter( 'wpforms_get_render_engine', [ $this, 'set_render_engine_classic' ], PHP_INT_MAX );

		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
		add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_scripts' ], 20 );
		add_action( 'wp_print_scripts', [ $this, 'dequeue_scripts' ], 5 );
		add_action( 'wpforms_wp_footer', [ $this, 'dequeue_scripts' ] );

		add_action( 'wpforms_frontend_confirmation', [ $this, 'dequeue_scripts' ] );
		add_action( 'wp_print_styles', [ $this, 'css_compatibility_mode' ] );
		add_action( 'wp_head', [ $this, 'print_form_styles' ] );
		add_filter( 'body_class', [ $this, 'set_body_classes' ] );

		add_filter( 'wpforms_frontend_form_data', [ $this, 'ignore_pagebreaks' ] );
		add_filter( 'wpforms_frontend_form_data', [ $this, 'change_multiple_dropdown_type' ] );
		add_filter( 'wpforms_field_data', [ $this, 'ignore_date_dropdowns' ], 10, 2 );
		add_filter( 'wpforms_field_data', [ $this, 'select_field_data' ], 10, 2 );
		add_filter( 'wpforms_field_properties', [ $this, 'ignore_multi_column_layout' ], 10, 3 );
		add_filter( 'wpforms_field_properties', [ $this, 'add_data_field_type_attr' ], 10, 3 );
		add_filter( 'wpforms_field_properties', [ $this, 'field_properties' ], 10, 3 );
		add_filter( 'wpforms_pro_fields_entry_preview_is_fields_ignored', '__return_true' );

		add_action( 'wpforms_display_field_after', [ $this, 'add_file_upload_html' ], 10, 2 );
		add_filter( 'wpforms_datetime_limits_available', '__return_false', 100 );

		add_action( 'wpforms_conversational_forms_content_before', [ $this, 'form_loader_html' ] );
		add_action( 'wpforms_conversational_forms_content_before', [ $this, 'form_header_html' ] );
		add_action( 'wpforms_conversational_forms_footer', [ $this, 'form_footer_html' ] );

		add_filter( 'tiny_mce_before_init', [ $this, 'add_styles_for_rich_text_field' ], 10, 2 );

		add_action( 'wp', [ $this, 'meta_tags' ] );

		add_action( 'wpforms_frontend_confirmation_message_before', [ $this, 'open_confirmation_wrapper' ], -1000 );
		add_action( 'wpforms_frontend_confirmation_message_after', [ $this, 'close_confirmation_wrapper' ], 1000 );

		add_filter(
			'wpforms_smarttags_process_page_title_value',
			[ $this, 'filter_page_title_smart_tag_value' ],
			10,
			2
		);

		// We need to remove some hooks as well.
		$this->remove_conflicting_hooks();

		// Disable Monster Insights loading to prevent conflicts, e.g., Date Picker Styles.
		add_filter( 'monsterinsights_get_option_hide_admin_bar_reports', '__return_true' );
	}

	/**
	 * Remove unnecessary or conflicting hooks.
	 *
	 * @since 1.6.0
	 */
	public function remove_conflicting_hooks(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' );
		remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
		remove_action( 'wp_head', 'wp_oembed_add_host_js' );

		remove_action( 'wp_enqueue_scripts', 'twentyseventeen_scripts' );
		remove_action( 'wp_enqueue_scripts', 'twentytwenty_register_scripts' );
		remove_action( 'wp_enqueue_scripts', 'twentytwenty_register_styles' );
	}

	/**
	 * Set render engine to `classic`.
	 *
	 * @since 1.10.0
	 *
	 * @param string $engine Render engine slug.
	 *
	 * @return string
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function set_render_engine_classic( $engine ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found

		return 'classic';
	}

	/**
	 * Handle AJAX request.
	 *
	 * @since 1.7.0
	 */
	public function handle_ajax_request(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		global $post;

		if ( ! $this->is_conversational_form( $post ) ) {
			return;
		}

		add_filter( 'wpforms_get_render_engine', [ $this, 'set_render_engine_classic' ], PHP_INT_MAX );

		// Force the classic render engine for WPForms version starting from 1.8.1.
		if ( function_exists( 'wpforms_get_render_engine' ) ) {
			$frontend_obj = wpforms()->obj( 'frontend' );

			if ( $frontend_obj ) {
				$frontend_obj->init_render_engine( 'classic' );
			}
		}

		add_action( 'wpforms_frontend_confirmation_message_before', [ $this, 'open_confirmation_wrapper' ], -1000 );
		add_action( 'wpforms_frontend_confirmation_message_after', [ $this, 'close_confirmation_wrapper' ], 1000 );
		add_filter(
			'wpforms_smarttags_process_page_title_value',
			[ $this, 'filter_page_title_smart_tag_value' ],
			10,
			2
		);
	}

	/**
	 * Determine whether it is a conversational form.
	 *
	 * @since 1.7.0
	 *
	 * @param WP_Post $form Form post.
	 *
	 * @return bool
	 */
	private function is_conversational_form( $form ): bool {

		$form_handler = wpforms()->obj( 'form' );
		$post_types   = [ 'wpforms' ];

		// @WPFormsBackCompatStart User Generated Templates since WPForms v1.8.8
		if ( defined( get_class( $form_handler ?? '' ) . '::POST_TYPES' ) ) {
			$post_types = $form_handler::POST_TYPES;
		}
		// @WPFormsBackCompatEnd

		if ( ! isset( $form->post_type ) || ! in_array( $form->post_type, $post_types, true ) ) {
			return false;
		}

		$form_data = wpforms_decode( $form->post_content );

		return ! empty( $form_data['settings']['conversational_forms_enable'] );
	}

	/**
	 * Conversational form template.
	 *
	 * @since 1.0.0
	 */
	public function get_form_template(): string {

		return plugin_dir_path( WPFORMS_CONVERSATIONAL_FORMS_FILE ) . 'templates/single-form.php';
	}

	/**
	 * Change the document title to a custom form title.
	 *
	 * @since 1.0.0
	 *
	 * @param array|mixed $title Original document title parts.
	 *
	 * @return array
	 */
	public function change_form_page_title( $title ): array {

		$title          = (array) $title;
		$title['title'] = $this->get_title();

		return $title;
	}

	/**
	 * Modify the permalink for a conversational form.
	 *
	 * @since 1.0.0
	 *
	 * @param string|mixed $post_link The post's permalink.
	 * @param WP_Post      $post      The post object.
	 *
	 * @return string
	 */
	public function modify_permalink( $post_link, $post ): string {

		$post_link = (string) $post_link;

		if ( empty( $this->form_data['id'] ) || absint( $this->form_data['id'] ) !== $post->ID ) {
			return $post_link;
		}

		if ( empty( $this->form_data['settings']['conversational_forms_enable'] ) ) {
			return $post_link;
		}

		return (string) home_url( $post->post_name );
	}

	/**
	 * Enqueue scripts and styles.
	 *
	 * @since 1.0.0
	 */
	public function enqueue_scripts(): void {

		$min = wpforms_get_min_suffix();

		if ( wpforms_has_field_type( 'date-time', $this->form_data ) ) {
			wp_enqueue_script(
				'wpforms-maskedinput',
				WPFORMS_PLUGIN_URL . 'assets/lib/jquery.inputmask.min.js',
				[ 'jquery' ],
				'5.0.9',
				true
			);
		}

		wp_enqueue_script(
			'wpforms-conversational-forms-mobile-detect',
			wpforms_conversational_forms()->url . "assets/js/vendor/mobile-detect{$min}.js",
			[],
			'1.4.3',
			true
		);

		wp_enqueue_script(
			'wpforms-conversational-forms',
			wpforms_conversational_forms()->url . "assets/js/conversational-forms.es5{$min}.js",
			[ 'jquery', 'wpforms-conversational-forms-mobile-detect' ],
			WPFORMS_CONVERSATIONAL_FORMS_VERSION,
			true
		);

		wp_enqueue_style(
			'wpforms-conversational-forms',
			wpforms_conversational_forms()->url . "assets/css/conversational-forms{$min}.css",
			[ 'wpforms-font-awesome' ],
			WPFORMS_CONVERSATIONAL_FORMS_VERSION
		);

		wp_localize_script(
			'wpforms-conversational-forms',
			'wpforms_conversational_forms',
			[
				'html' => $this->get_field_additional_html(),
				'i18n' => [
					'select_placeholder'   => esc_html__( 'Type or select an option', 'wpforms-conversational-forms' ),
					'select_list_empty'    => esc_html__( 'No suggestions found', 'wpforms-conversational-forms' ),
					'select_option_helper' => wp_kses( __( '<strong>Enter</strong> to select an option', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ),
					'no_file_chosen'       => esc_html__( 'No file chosen', 'wpforms-conversational-forms' ),
				],
			]
		);

		// FontAwesome.
		wp_enqueue_style(
			'wpforms-font-awesome',
			WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/all.min.css',
			null,
			'7.0.1'
		);

		// FontAwesome v4 compatibility shims.
		wp_enqueue_style(
			'wpforms-font-awesome-v4-shim',
			WPFORMS_PLUGIN_URL . 'assets/lib/font-awesome/css/v4-shims.min.css',
			null,
			'4.7.0'
		);
	}

	/**
	 * Dequeue scripts and styles.
	 *
	 * @since 1.0.0
	 */
	public function dequeue_scripts(): void {

		wp_dequeue_script( 'wpforms-jquery-timepicker' );
		wp_dequeue_style( 'wpforms-jquery-timepicker' );

		wp_dequeue_script( 'wpforms-flatpickr' );
		wp_dequeue_style( 'wpforms-flatpickr' );

		wp_dequeue_script( 'popup-maker-site' );

		// Dequeue WPForms Core styles.
		wp_dequeue_style( 'wpforms-full' );
		wp_dequeue_style( 'wpforms-base' );

		wp_dequeue_style( 'wpforms-classic-full' );
		wp_dequeue_style( 'wpforms-classic-base' );
		wp_dequeue_style( 'wpforms-pro-classic-full' );
		wp_dequeue_style( 'wpforms-pro-classic-base' );

		wp_dequeue_style( 'wpforms-modern-full' );
		wp_dequeue_style( 'wpforms-modern-base' );
		wp_dequeue_style( 'wpforms-pro-modern-full' );
		wp_dequeue_style( 'wpforms-pro-modern-base' );

		// Dequeue OceanWP theme scripts that cause conflicts.
		wp_dequeue_script( 'oceanwp-main' );
		wp_deregister_script( 'oceanwp-main' );
		wp_dequeue_script( 'oceanwp-scroll-effect' );
		wp_deregister_script( 'oceanwp-scroll-effect' );

		wp_dequeue_script( 'oceanwp-scroll-top' );
		wp_deregister_script( 'oceanwp-scroll-top' );
	}

	/**
	 * Unload CSS potentially interfering with the Conversational Forms layout.
	 *
	 * @since 1.0.0
	 */
	public function css_compatibility_mode(): void {

		/**
		 * Filter to disable CSS compatibility mode.
		 *
		 * @since 1.0.0
		 *
		 * @param bool $compat_mode CSS compatibility mode. Default is true.
		 */
		if ( ! apply_filters( 'wpforms_conversational_forms_css_compatibility_mode', true ) ) { // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
			return;
		}

		$styles = wp_styles();

		if ( empty( $styles->queue ) ) {
			return;
		}

		$theme_uri        = wp_make_link_relative( get_stylesheet_directory_uri() );
		$parent_theme_uri = wp_make_link_relative( get_template_directory_uri() );

		$upload_uri = wp_get_upload_dir();
		$upload_uri = isset( $upload_uri['baseurl'] ) ? wp_make_link_relative( $upload_uri['baseurl'] ) : $theme_uri;

		foreach ( $styles->queue as $handle ) {

			if ( ! isset( $styles->registered[ $handle ]->src ) ) {
				continue;
			}

			$src = wp_make_link_relative( $styles->registered[ $handle ]->src );

			// Dequeue theme or upload folder CSS.
			foreach ( [ $theme_uri, $parent_theme_uri, $upload_uri ] as $uri ) {
				if ( strpos( $src, $uri ) !== false ) {
					wp_dequeue_style( $handle );
					break;
				}
			}
		}

		/**
		 * Action to enqueue custom styles on the Conversational Form page.
		 *
		 * @since 1.0.0
		 */
		do_action( 'wpforms_conversational_forms_enqueue_styles' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Print dynamic form styles.
	 *
	 * @since 1.0.0
	 */
	public function print_form_styles(): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		if ( empty( $this->form_data['settings']['conversational_forms_color_scheme'] ) ) {
			return;
		}

		$color = sanitize_hex_color( $this->form_data['settings']['conversational_forms_color_scheme'] );

		if ( empty( $color ) ) {
			$color = '#448ccb';
		}

		$this->color = $color;
		$min         = wpforms_get_min_suffix();

		switch ( $color ) {
			case '#448ccb':
				$theme = 'color-scheme-blue';
				break;

			case '#1a3c5a':
				$theme = 'color-scheme-dark_blue';
				break;

			case '#4aa891':
				$theme = 'color-scheme-teal';
				break;

			case '#9178b3':
				$theme = 'color-scheme-purple';
				break;

			case '#cccccc':
				$theme = 'color-scheme-light';
				break;

			case '#363636':
				$theme = 'color-scheme-dark';
				break;

			default:
				$theme = '';
		}

		if ( ! $theme ) {
			require plugin_dir_path( WPFORMS_CONVERSATIONAL_FORMS_FILE ) . 'templates/dynamic-color-scheme-styles.php';

			return;
		}

		wp_enqueue_style(
			"wpforms-conversational-forms-{$theme}",
			wpforms_conversational_forms()->url . "assets/css/color-schemes/{$theme}{$min}.css",
			[ 'wpforms-conversational-forms' ],
			WPFORMS_CONVERSATIONAL_FORMS_VERSION
		);

		$this->color_scheme = $theme;

		wp_localize_script(
			'wpforms-conversational-forms',
			'wpforms_conversational_form_appearance',
			$this->get_theme_appearance()
		);
	}

	/**
	 * Get theme appearance data.
	 *
	 * @since 1.16.0
	 *
	 * @return array
	 */
	private function get_theme_appearance(): array {

		$themes = [
			'color-scheme-blue'      => [
				'colorBackground' => '#e2edf7',
				'colorText'       => '#1a3d5c',
			],
			'color-scheme-dark_blue' => [
				'colorBackground' => '#193c5b',
				'colorText'       => '#d7e6f3',
			],
			'color-scheme-teal'      => [
				'colorBackground' => '#cce8e1',
				'colorText'       => '#317162',
			],
			'color-scheme-purple'    => [
				'colorBackground' => '#e6e1ed',
				'colorText'       => '#5d497d',
			],
			'color-scheme-light'     => [
				'colorBackground' => '#f6f6f6',
				'colorText'       => '#333333',
			],
			'color-scheme-dark'      => [
				'colorBackground' => '#161616',
				'colorText'       => '#cccccc',
			],
		];

		return $themes[ $this->color_scheme ] ?? [];
	}

	/**
	 * Set body classes to apply different form styling.
	 *
	 * @since 1.0.0
	 *
	 * @param array|mixed $classes Body classes.
	 *
	 * @return array
	 */
	public function set_body_classes( $classes ): array {

		$classes = (array) $classes;

		if ( ! empty( $this->form_data['settings']['conversational_forms_custom_logo'] ) ) {
			$classes[] = 'wpforms-conversational-form-custom-logo';
		}

		return $classes;
	}

	/**
	 * Ignore pagebreak elements on render.
	 *
	 * @since 1.0.0
	 *
	 * @param array|mixed $form_data Form data and settings.
	 *
	 * @return array
	 */
	public function ignore_pagebreaks( $form_data ): array {

		$form_data = (array) $form_data;

		foreach ( $form_data['fields'] as $id => $field ) {
			if ( $field['type'] !== 'pagebreak' ) {
				continue;
			}

			unset( $form_data['fields'][ $id ] );
		}

		return $form_data;
	}

	/**
	 * Ignore the date dropdown style on render.
	 *
	 * @since        1.0.0
	 *
	 * @param array|mixed $field     Field settings.
	 * @param array       $form_data Form data and settings.
	 *
	 * @return array
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function ignore_date_dropdowns( $field, $form_data ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$field = (array) $field;

		if ( $field['type'] === 'date-time' && $field['date_type'] === 'dropdown' ) {
			$field['date_type'] = 'datepicker';
		}

		return $field;
	}

	/**
	 * Convert the date type from `dropdown` to `datepicker` before processing.
	 *
	 * @since      1.6.0
	 * @deprecated 1.7.0
	 *
	 * @param array $form_data Form data and settings.
	 *
	 * @return array Updated form data.
	 * @noinspection PhpMissingReturnTypeInspection
	 * @noinspection ReturnTypeCanBeDeclaredInspection
	 */
	public function prepare_date_dropdowns( $form_data ) {

		_deprecated_function( __METHOD__, '1.7.0 of the WPForms Conversational Forms addon', '\WPFormsConversationalForms\Process::prepare_date_dropdowns()' );

		return ( new Process() )->prepare_date_dropdowns( $form_data, [] );
	}

	/**
	 * Change modern and multiple styles on render for `Dropdown` and `Dropdown Items` fields.
	 *
	 * @since        1.6.0
	 *
	 * @param array|mixed $field     Field settings.
	 * @param array       $form_data Form data and settings.
	 *
	 * @return array
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function select_field_data( $field, $form_data ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$field = (array) $field;

		if ( ! in_array( $field['type'], [ 'select', 'payment-select' ], true ) ) {
			return $field;
		}

		// Modern select style should look like a Classic style.
		if (
			! empty( $field['style'] ) &&
			$field['style'] === WPForms_Field_Select::STYLE_MODERN
		) {
			$field['style'] = WPForms_Field_Select::STYLE_CLASSIC;
		}

		// Multiple select has the same logic as checkboxes.
		if ( ! empty( $field['multiple'] ) ) {
			$field['type'] = 'checkbox';
		}

		return $field;
	}

	/**
	 * Ignore multi-column fields layout.
	 *
	 * @since        1.0.0
	 *
	 * @param array|mixed $properties Field properties.
	 * @param array       $field      Field settings.
	 * @param array       $form_data  Form data and settings.
	 *
	 * @return array
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function ignore_multi_column_layout( $properties, $field, $form_data ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$properties = (array) $properties;

		if ( empty( $properties['container']['class'] ) ) {
			return $properties;
		}

		foreach ( $properties['container']['class'] as $i => $class ) {
			if ( in_array(
				$class,
				[
					'wpforms-first',
					'wpforms-one-half',
					'wpforms-one-third',
					'wpforms-two-thirds',
					'wpforms-one-fourth',
					'wpforms-two-fourths',
					'wpforms-one-fifth',
					'wpforms-two-fifths',
				],
				true
			) ) {
				unset( $properties['container']['class'][ $i ] );
			}
		}

		return $properties;
	}

	/**
	 * Add a data-field-type attribute to field elements.
	 *
	 * @since        1.0.0
	 *
	 * @param array|mixed $properties Field properties.
	 * @param array       $field      Field settings.
	 * @param array       $form_data  Form data and settings.
	 *
	 * @return array
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function add_data_field_type_attr( $properties, $field, $form_data ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$properties = (array) $properties;

		$properties['container']['data']['field-type'] = $field['type'];

		return $properties;
	}

	/**
	 * Various field properties filters.
	 *
	 * @since        1.6.0
	 *
	 * @param array|mixed $properties Field properties.
	 * @param array       $field      Field settings.
	 * @param array       $form_data  Form data and settings.
	 *
	 * @return array
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function field_properties( $properties, $field, $form_data ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$properties = (array) $properties;

		// Numerate fields with empty labels.
		$properties['label']['value'] = empty( $properties['label']['value'] ) ? '&nbsp;' : $properties['label']['value'];

		// Apply the "Hide label" advanced option.
		$properties['label']['value'] = ! empty( $field['label_hide'] ) ? '&nbsp;' : $properties['label']['value'];

		return $properties;
	}

	/**
	 * Add HTML to the file upload field.
	 *
	 * @since 1.0.0
	 *
	 * @param array $field     Field settings.
	 * @param array $form_data Form data and settings.
	 *
	 * @noinspection PhpMissingParamTypeInspection
	 * @noinspection PhpUnusedParameterInspection
	 * @noinspection PhpClassConstantAccessedViaChildClassInspection
	 */
	public function add_file_upload_html( $field, $form_data ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		// Display only for the file uploader field.
		if ( empty( $field['type'] ) || $field['type'] !== 'file-upload' ) {
			return;
		}

		// Ignore the modern style.
		if ( ! empty( $field['style'] ) && $field['style'] === WPForms_Field_File_Upload::STYLE_MODERN ) {
			return;
		}
		?>

		<label
				class="wpforms-field-file-upload-label wpforms-conversational-btn"
				for="<?php echo esc_attr( $field['properties']['inputs']['primary']['id'] ); ?>">
			<?php esc_html_e( 'Choose File', 'wpforms-conversational-forms' ); ?>
		</label>
		<span class="wpforms-field-file-upload-file-name wpforms-conversational-form-btn-desc">
			<?php esc_html_e( 'No file chosen', 'wpforms-conversational-forms' ); ?>
		</span>

		<?php
	}

	/**
	 * Form Loader HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_loader_html(): void {

		$brand_disable = ! empty( $this->form_data['settings']['conversational_forms_brand_disable'] ) ? $this->form_data['settings']['conversational_forms_brand_disable'] : '';

		?>
		<div id="wpforms-conversational-form-loader-container">
			<div class="wpforms-conversational-form-loader-content">
				<div class="wpforms-conversational-form-loader">
					<?php esc_html_e( 'Loading...', 'wpforms-conversational-forms' ); ?>
				</div>

				<?php if ( ! $brand_disable ) : ?>
					<div class="wpforms-conversational-form-loader-powered-by">
						<span class="wpforms-conversational-form-loader-powered-by-text">
							<?php esc_html_e( 'powered by', 'wpforms-conversational-forms' ); ?>
						</span>
						<?php
						readfile( plugin_dir_path( WPFORMS_CONVERSATIONAL_FORMS_FILE ) . 'assets/images/wpforms-text-logo.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
						?>
					</div>
				<?php endif; ?>

			</div>
		</div>
		<?php
	}

	/**
	 * Form header HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_header_html(): void {

		if ( $this->is_form_submit_success( $this->form_data['id'] ) ) {
			return;
		}

		?>
		<div class="wpforms-conversational-form-header">
			<?php $this->form_logo_html(); ?>
			<?php $this->form_head_html(); ?>
			<?php $this->form_template_notice_html(); ?>

			<?php
			// phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
			if ( ! apply_filters( 'wpforms_conversational_forms_start_button_disabled', false, $this->form_data ) ) :

				$start_button_text = ! empty( $this->form_data['settings']['conversational_forms_start_button_text'] )
					? $this->form_data['settings']['conversational_forms_start_button_text']
					: __( 'Start', 'wpforms-conversational-forms' );
				?>

				<div class="wpforms-conversational-form-btn-container">
					<button class="wpforms-conversational-btn-start wpforms-conversational-btn"><?php echo esc_html( $start_button_text ); ?></button>
					<div class="wpforms-conversational-form-btn-desc">
						<?php echo wp_kses( __( 'press <strong>Enter</strong>', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
					</div>
				</div>

			<?php endif; ?>

		</div>
		<?php
	}

	/**
	 * Form custom logo HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_logo_html(): void {

		if ( empty( $this->form_data['settings']['conversational_forms_custom_logo'] ) ) {
			return;
		}

		$custom_logo_url = wp_get_attachment_image_src( $this->form_data['settings']['conversational_forms_custom_logo'], 'full' );
		$custom_logo_url = $custom_logo_url[0] ?? '';

		?>
		<div class="wpforms-conversational-form-logo">
			<img
					src="<?php echo esc_url( $custom_logo_url ); ?>"
					alt="<?php esc_html_e( 'Form Logo', 'wpforms-conversational-forms' ); ?>">
		</div>
		<?php
	}

	/**
	 * Form head area HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_head_html(): void {

		$settings = $this->form_data['settings'];

		$title       = ! empty( $settings['conversational_forms_title'] ) ? $settings['conversational_forms_title'] : '';
		$description = ! empty( $settings['conversational_forms_description'] ) ? $settings['conversational_forms_description'] : '';

		if ( empty( $title ) && empty( $description ) ) {
			return;
		}

		$settings['form_title'] = $title;
		$settings['form_desc']  = $description;

		$frontend_obj = wpforms()->obj( 'frontend' );

		if ( $frontend_obj ) {
			$frontend_obj->head( array_merge( $this->form_data, [ 'settings' => $settings ] ), null, true, true, [] );
		}
	}

	/**
	 * Display a notice about the form template.
	 *
	 * @since 1.15.0
	 */
	public function form_template_notice_html(): void {

		if ( ! isset( $this->form_data['settings']['template_description'] ) ) {
			return;
		}

		$content = '<div class="wpforms-preview-notice">';

		$content .= sprintf(
			'<strong>%s</strong> %s',
			esc_html__( 'Heads up!', 'wpforms-conversational-forms' ),
			esc_html__( 'You\'re viewing a preview of a form template.', 'wpforms-conversational-forms' )
		);

		if ( wpforms()->is_pro() ) {
			/** This filter is documented in wpforms/src/Pro/Tasks/Actions/PurgeTemplateEntryTask.php */
			$delay = (int) apply_filters( 'wpforms_pro_tasks_actions_purge_template_entry_task_delay', DAY_IN_SECONDS ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

			$message = sprintf( /* translators: %s - time period, e.g., 24 hours. */
				__( 'Entries are automatically deleted after %s.', 'wpforms-conversational-forms' ),
				human_time_diff( time(), time() + $delay - 1 )
			);

			$content .= sprintf( '<p>%s</p>', esc_html( $message ) );
		}

		$content .= '</div>';

		echo wp_kses_post( $content );
	}

	/**
	 * Field additional HTML.
	 *
	 * @since 1.0.0
	 */
	public function get_field_additional_html(): array {

		$html = [];

		ob_start();
		?>
		<div class="wpforms-conversational-form-btn-container wpforms-conversational-form-next-field-btns">
			<button class="wpforms-conversational-btn-next wpforms-conversational-btn"><?php esc_html_e( 'Done', 'wpforms-conversational-forms' ); ?></button>
			<div class="wpforms-conversational-form-btn-desc">
				<?php echo wp_kses( __( 'press <strong>Enter</strong>', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
			</div>
		</div>
		<?php

		$html['general']['action_buttons'] = ob_get_clean();

		ob_start();
		?>
		<div class="wpforms-conversational-form-field-info">
			<?php echo wp_kses( __( '<strong>Enter</strong> or <strong>&#x2B07;</strong> to go to the next field', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
		</div>
		<?php

		$html['general']['next_field'] = ob_get_clean();

		ob_start();
		?>
		<div class="wpforms-conversational-form-field-info">
			<?php echo wp_kses( __( '<strong>Shift+Enter</strong> to make a line break', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
		</div>
		<?php

		$html['textarea'] = ob_get_clean();

		ob_start();
		?>
		<div class="wpforms-conversational-form-field-info">
			<?php echo wp_kses( __( '<strong>Tab</strong> or <strong>&#x2B07;</strong> to switch the line', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
		</div>
		<?php

		$html['likert_scale'] = ob_get_clean();

		ob_start();
		?>
		<div class="wpforms-conversational-form-field-info">
			<?php echo wp_kses( __( '<strong>Shift+Enter</strong> to open a file', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
		</div>
		<?php

		$html['file_upload'] = ob_get_clean();

		ob_start();
		?>
		<div class="wpforms-conversational-form-field-info">
			<?php echo wp_kses( __( '<strong>Shift+Enter</strong> to go to the next field', 'wpforms-conversational-forms' ), [ 'strong' => [] ] ); ?>
		</div>
		<?php

		$html['checkbox'] = ob_get_clean();

		return $html;
	}

	/**
	 * Form footer HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_footer_html(): void {

		$this->form_footer_progress_block_html();
		$this->form_footer_right_block_html();
	}

	/**
	 * Form footer progress block HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_footer_progress_block_html(): void {

		$progress_style = ! empty( $this->form_data['settings']['conversational_forms_progress_bar'] ) ? $this->form_data['settings']['conversational_forms_progress_bar'] : '';

		?>
		<div class="wpforms-conversational-form-footer-progress">
			<div class="wpforms-conversational-form-footer-progress-status">
				<?php
				if ( $progress_style === 'proportion' ) {
					$this->form_footer_progress_status_proportion_html();
				} else {
					$this->form_footer_progress_status_percentage_html();
				}
				?>
			</div>
			<div class="wpforms-conversational-form-footer-progress-bar">
				<div class="wpforms-conversational-form-footer-progress-completed"></div>
			</div>
		</div>
		<?php
	}

	/**
	 * Form footer progress status (proportion) HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_footer_progress_status_proportion_html(): void {

		?>
		<div class="wpforms-conversational-form-footer-progress-status-proportion">
			<?php
			printf(
			/* translators: %1$s - Number of fields completed; %2$s - Number of fields in total. */
				esc_html__(
					'%1$s of %2$s completed',
					'wpforms-conversational-forms'
				),
				'<span class="completed"></span>',
				'<span class="completed-of"></span>'
			);
			?>
		</div>
		<div class="wpforms-conversational-form-footer-progress-status-proportion-completed" style="display: none">
			<?php esc_html_e( 'Form completed', 'wpforms-conversational-forms' ); ?>
		</div>
		<?php
	}

	/**
	 * Form footer progress status (percentage) HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_footer_progress_status_percentage_html(): void {

		?>
		<div class="wpforms-conversational-form-footer-progress-status-percentage">
			<?php
			printf(
			/* translators: %s - Percentage of fields completed. */
				esc_html__(
					'%s%% completed',
					'wpforms-conversational-forms'
				),
				'<span class="completed">100</span>'
			);
			?>
		</div>
		<?php
	}

	/**
	 * Open the wrapper for confirmation elements.
	 *
	 * @since 1.7.0
	 */
	public function open_confirmation_wrapper(): void {

		echo '<div class="wpforms-confirmation-container-wrapper">';
	}

	/**
	 * Close the wrapper for confirmation elements.
	 *
	 * @since 1.7.0
	 */
	public function close_confirmation_wrapper(): void {

		echo '</div>';
	}

	/**
	 * Form footer right block HTML.
	 *
	 * @since 1.0.0
	 */
	public function form_footer_right_block_html(): void {

		$brand_disable = ! empty( $this->form_data['settings']['conversational_forms_brand_disable'] ) ? $this->form_data['settings']['conversational_forms_brand_disable'] : '';

		?>
		<div class="wpforms-conversational-form-footer-right-container">
			<?php if ( ! $brand_disable ) : ?>
				<div class="wpforms-conversational-form-footer-powered-by">
						<span>
							<?php esc_html_e( 'powered by', 'wpforms-conversational-forms' ); ?>
						</span>
					<?php
					readfile( plugin_dir_path( WPFORMS_CONVERSATIONAL_FORMS_FILE ) . 'assets/images/wpforms-text-logo.svg' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
					?>
				</div>
			<?php endif; ?>

			<div class="wpforms-conversational-form-footer-switch-step">
				<div class="wpforms-conversational-form-footer-switch-step-up">
					<i class="fa fa-angle-up" aria-hidden="true"></i>
				</div>
				<div class="wpforms-conversational-form-footer-switch-step-down">
					<i class="fa fa-angle-down" aria-hidden="true"></i>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Check if the form was submitted successfully.
	 *
	 * @since 1.0.0
	 *
	 * @param int $id Form id.
	 */
	public function is_form_submit_success( $id ): bool {

		// TODO: Code needs revision. Copy-paste from class-frontend.php.
		$form_obj = wpforms()->obj( 'form' );
		$form     = $form_obj ? $form_obj->get( (int) $id ) : null;

		if ( empty( $form ) ) {
			return false;
		}

		$form_id   = absint( $form->ID );
		$form_data = apply_filters( 'wpforms_frontend_form_data', wpforms_decode( $form->post_content ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName, WPForms.Comments.PHPDocHooks.RequiredHookDocumentation
		$errors    = empty( wpforms()->obj( 'process' )->errors[ $form_id ] ) ? [] : wpforms()->obj( 'process' )->errors[ $form_id ];

		// Check for return hash.
		if (
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended
			! empty( $_GET['wpforms_return'] ) &&
			wpforms()->obj( 'process' )->valid_hash &&
			absint( wpforms()->obj( 'process' )->form_data['id'] ) === $form_id
		) {
			return true;
		}

		// Check for error-free completed form.
		// phpcs:disable WordPress.Security.NonceVerification.Missing
		if (
			empty( $errors ) &&
			! empty( $form_data ) &&
			! empty( $_POST['wpforms']['id'] ) &&
			absint( $_POST['wpforms']['id'] ) === $form_id
		) {
			return true;
		}

		// phpcs:enable WordPress.Security.NonceVerification.Missing

		return false;
	}

	/**
	 * Meta robots.
	 *
	 * @since      1.3.2
	 * @deprecated 1.6.0
	 */
	public function meta_robots(): void { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		_deprecated_function( __METHOD__, '1.6.0 of the WPForms Conversational Forms addon', __CLASS__ . '::meta_tags()' );

		$seo_plugin_enabled = false;

		if ( class_exists( 'WPSEO_Options' ) ) {
			add_filter( 'wpseo_robots', [ $this, 'get_meta_robots' ], PHP_INT_MAX );
			$seo_plugin_enabled = true;
		}

		if ( class_exists( 'All_in_One_SEO_Pack' ) ) {
			add_filter( 'aioseop_robots_meta', [ $this, 'get_meta_robots' ], PHP_INT_MAX );
			$seo_plugin_enabled = true;
		}

		if ( ! $seo_plugin_enabled ) {
			add_action( 'wp_head', [ $this, 'output_meta_robots_tag' ] );
		}
	}

	/**
	 * Get meta robots value.
	 *
	 * @since 1.3.2
	 *
	 * @return string Meta robots value.
	 */
	public function get_meta_robots(): string {

		/**
		 * Filter the meta-robots value for the Conversational Form page.
		 *
		 * @since 1.3.2
		 *
		 * @param string $value Meta robots value.
		 */
		return (string) apply_filters( 'wpforms_conversational_forms_meta_robots_value', 'noindex,nofollow' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Output meta robots tag.
	 *
	 * @since 1.3.2
	 */
	public function output_meta_robots_tag(): void {

		printf(
			'<meta name="robots" content="%s"/>%s',
			esc_attr( $this->get_meta_robots() ),
			"\n"
		);
	}

	/**
	 * Rank Math robots filter.
	 *
	 * @since 1.6.0
	 *
	 * @return array Robots data.
	 */
	public function get_rank_math_meta_robots(): array {

		return explode( ',', $this->get_meta_robots() );
	}

	/**
	 * Meta tags.
	 *
	 * @since 1.6.0
	 */
	public function meta_tags(): void { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks

		$seo_plugin_enabled = false;

		if ( class_exists( 'WPSEO_Options' ) ) {
			add_filter( 'wpseo_title', [ $this, 'get_seo_title' ], PHP_INT_MAX );
			add_filter( 'wpseo_opengraph_desc', [ $this, 'get_description' ], PHP_INT_MAX );
			add_filter( 'wpseo_opengraph_url', [ $this, 'get_seo_url' ], PHP_INT_MAX );
			add_filter( 'wpseo_canonical', [ $this, 'get_seo_url' ], PHP_INT_MAX );
			add_filter( 'wpseo_twitter_description', [ $this, 'get_description' ], PHP_INT_MAX );
			add_filter( 'wpseo_robots', [ $this, 'get_meta_robots' ], PHP_INT_MAX );
			$seo_plugin_enabled = true;
		}

		if ( class_exists( 'All_in_One_SEO_Pack' ) ) {
			add_filter( 'aioseop_title', [ $this, 'get_seo_title' ], PHP_INT_MAX );
			add_filter( 'aioseop_description_override', [ $this, 'get_description' ], PHP_INT_MAX );
			add_filter( 'aioseop_robots_meta', [ $this, 'get_meta_robots' ], PHP_INT_MAX );
			$seo_plugin_enabled = true;
		}

		if ( class_exists( 'AIOSEO\Plugin\AIOSEO' ) && function_exists( 'aioseo' ) ) {
			// Disable AIOSEO in Conversational Form page.
			add_filter( 'aioseo_disable', '__return_true' );

			add_action( 'wp_head', [ $this, 'output_aioseo_alternative_meta_tags' ], 3 );
			$seo_plugin_enabled = true;
		}

		if ( defined( 'SEOPRESS_VERSION' ) ) {
			add_filter( 'seopress_titles_desc', [ $this, 'get_description' ], PHP_INT_MAX );
			add_filter( 'seopress_social_twitter_card_title', [ $this, 'get_twitter_meta_title' ], PHP_INT_MAX );
			add_filter( 'seopress_social_og_title', [ $this, 'get_fb_meta_title' ], PHP_INT_MAX );
			$seo_plugin_enabled = true;
		}

		if ( class_exists( 'RankMath' ) ) {
			add_filter( 'rank_math/frontend/title', [ $this, 'get_seo_title' ], PHP_INT_MAX );
			add_filter( 'rank_math/frontend/description', [ $this, 'get_rank_math_description' ], PHP_INT_MAX );
			add_filter( 'rank_math/frontend/robots', [ $this, 'get_rank_math_meta_robots' ], PHP_INT_MAX );
			add_filter(
				'rank_math/opengraph/facebook/og_description',
				[ $this, 'get_rank_math_description' ],
				PHP_INT_MAX
			);
			$seo_plugin_enabled = true;
		}

		// Handle 'SEO Plugin by Squirrly'.
		if ( defined( 'SQ_VERSION' ) ) {
			add_filter( 'sq_description', [ $this, 'get_sq_description' ], 90 );

			/*
			 * Priority is set to `90` because 'SEO Plugin by Squirrly' plugin has a filter
			 * that converts the `array` args to string at priority `99`.
			 * See `packOpenGraph()` and `packTwitterCard()`.
			 */
			add_filter( 'sq_open_graph', [ $this, 'filter_social_tags' ], 90 );
			add_filter( 'sq_twitter_card', [ $this, 'filter_social_tags' ], 90 );
			$seo_plugin_enabled = true;
		}

		if ( ! $seo_plugin_enabled ) {
			add_action( 'wp_head', [ $this, 'output_meta_robots_tag' ] );
		}
	}

	/**
	 * Get title value.
	 *
	 * @since 1.6.0
	 *
	 * @return string Title value.
	 */
	public function get_title(): string {

		$title = ! empty( $this->form_data['settings']['form_title'] ) ? $this->form_data['settings']['form_title'] : '';

		if ( ! empty( $this->form_data['settings']['conversational_forms_title'] ) ) {
			$title = $this->form_data['settings']['conversational_forms_title'];
		}

		return wp_strip_all_tags( $title, true );
	}

	/**
	 * Get SEO plugin title value.
	 *
	 * @since 1.6.0
	 *
	 * @param string|mixed $title Original title.
	 *
	 * @return string Title value.
	 */
	public function get_seo_title( $title = '' ): string {

		$title = (string) $title;

		if ( ! empty( $this->form_data['settings']['form_title'] ) ) {
			$title = str_replace( $this->form_data['settings']['form_title'], $this->get_title(), $title );
		}

		return $title;
	}

	/**
	 * Get description value.
	 *
	 * @since 1.6.0
	 *
	 * @return string Description value.
	 */
	public function get_description(): string {

		return ! empty( $this->form_data['settings']['conversational_forms_description'] )
			? wp_strip_all_tags( $this->form_data['settings']['conversational_forms_description'], true )
			: '';
	}

	/**
	 * Get description value for the Rank Math plugin.
	 *
	 * @since 1.6.0
	 *
	 * @return string Description value.
	 */
	public function get_rank_math_description() {

		$description = $this->get_description();

		return ! empty( $description ) ?
			$description :
			false; // `false` means that Rank Math will not generate a description automatically.
	}

	/**
	 * Force Yoast SEO og/twitter descriptions.
	 *
	 * @since      1.0.0
	 * @deprecated 1.6.0
	 *
	 * @return string
	 */
	public function yoast_seo_description(): string {

		_deprecated_function( __METHOD__, '1.6.0 of the WPForms Conversational Forms addon', __CLASS__ . '::get_description()' );

		return ! empty( $this->form_data['settings']['conversational_forms_description'] ) ? wp_strip_all_tags( $this->form_data['settings']['conversational_forms_description'], true ) : '';
	}

	/**
	 * Get SEO url value for Yoast SEO plugin.
	 *
	 * @since 1.7.0
	 *
	 * @return string
	 */
	public function get_seo_url(): string {

		return (string) get_the_permalink();
	}

	/**
	 * Add extra styles for the Rich Text field.
	 *
	 * @since 1.7.0
	 *
	 * @param array|mixed $mce_init  An array with TinyMCE config.
	 * @param string      $editor_id Unique editor identifier.
	 *
	 * @return array
	 */
	public function add_styles_for_rich_text_field( $mce_init, $editor_id ): array {

		$mce_init = (array) $mce_init;

		if ( strpos( $editor_id, 'wpforms-' ) !== 0 ) {
			return $mce_init;
		}

		// If we have a color scheme, load the color scheme stylesheet. Otherwise, set custom styles.
		if ( $this->color_scheme !== '' ) {
			$min    = wpforms_get_min_suffix();
			$styles = wpforms_conversational_forms()->url . "assets/css/color-schemes/{$this->color_scheme}{$min}.css";

			if ( ! isset( $mce_init['content_css'] ) ) {
				$mce_init['content_css'] = $styles;
			} else {
				$mce_init['content_css'] .= ',' . $styles;
			}
		} else {
			$styles = '.mce-content-body { background-color: transparent; color: ' . $this->color . ' }';

			if ( ! isset( $mce_init['content_style'] ) ) {
				$mce_init['content_style'] = $styles . ' ';
			} else {
				$mce_init['content_style'] .= ' ' . $styles . ' ';
			}
		}

		return $mce_init;
	}

	/**
	 * Change a field type to checkbox for multiple dropdown.
	 *
	 * @since 1.7.0
	 *
	 * @param array|mixed $form_data Form data and settings.
	 *
	 * @return array
	 */
	public function change_multiple_dropdown_type( $form_data ): array {

		$form_data = (array) $form_data;

		foreach ( $form_data['fields'] as $id => $field ) {
			if ( isset( $field['multiple'], $field['type'] ) && $field['type'] === 'select' ) {
				$form_data['fields'][ $id ]['type'] = 'checkbox';
			}
		}

		return $form_data;
	}

	/**
	 * Output meta-tags when using AIOSEO plugin.
	 *
	 * @since 1.7.1
	 *
	 * @noinspection PhpUndefinedFunctionInspection
	 */
	public function output_aioseo_alternative_meta_tags(): void {

		?>
		<meta name="description" content="<?php echo esc_attr( $this->get_description() ); ?>"/>
		<meta name="robots" content="max-image-preview:large"/>
		<?php

		if ( property_exists( aioseo(), 'helpers' ) && method_exists( aioseo()->helpers, 'canonicalUrl' ) ) {
			?>
			<link rel="canonical" href="<?php echo esc_url( aioseo()->helpers->canonicalUrl() ); ?>"/>
			<?php
		}

		$this->output_aioseo_facebook_meta();
		$this->output_aioseo_twitter_meta();
	}

	/**
	 * Output Facebook meta-tags when AIOSEO plugin is installed.
	 *
	 * @since 1.7.1
	 */
	private function output_aioseo_facebook_meta(): void {

		?>
		<meta property="og:title" content="<?php echo esc_attr( $this->get_title() ); ?>"/>
		<meta property="og:description" content="<?php echo esc_attr( $this->get_description() ); ?>"/>
		<?php

		$this->maybe_output_aioseo_social_meta(
			'getFacebookMeta',
			[
				'og:locale',
				'og:site_name',
				'og:type',
				'og:url',
			],
			'property'
		);
	}

	/**
	 * Output social meta-tags using AIOSEO.
	 *
	 * @since 1.7.1
	 *
	 * @param string $get_social_meta_function_name Name of the function in AIOSEO to fetch social meta-tags.
	 * @param array  $meta_tags_to_output           Meta-tags to output.
	 * @param string $name_or_property              Whether to 'name' or 'property' as the meta-tag attribute.
	 *                                              Accepts 'name' or 'property'.
	 *
	 * @noinspection PhpUndefinedFunctionInspection
	 * @noinspection HtmlUnknownAttribute
	 */
	private function maybe_output_aioseo_social_meta( $get_social_meta_function_name, $meta_tags_to_output, $name_or_property ): void {

		if (
			! property_exists( aioseo(), 'social' ) ||
			! property_exists( aioseo()->social, 'output' ) ||
			! method_exists( aioseo()->social->output, $get_social_meta_function_name )
		) {
			return;
		}

		$meta_tags = call_user_func( [ aioseo()->social->output, $get_social_meta_function_name ] );

		if ( empty( $meta_tags ) || empty( $meta_tags_to_output ) ) {
			return;
		}

		$meta_tag_attribute = 'property';

		if ( $name_or_property === 'name' ) {
			$meta_tag_attribute = 'name';
		}

		foreach ( $meta_tags_to_output as $meta_to_output ) {
			if ( empty( $meta_tags[ $meta_to_output ] ) ) {
				continue;
			}

			printf(
				'<meta %1$s="%2$s" content="%3$s" />' . "\n",
				esc_attr( $meta_tag_attribute ),
				esc_attr( $meta_to_output ),
				esc_attr( $meta_tags[ $meta_to_output ] )
			);
		}
	}

	/**
	 * Output Twitter meta tags when AIOSEO plugin is installed.
	 *
	 * @since 1.7.1
	 */
	private function output_aioseo_twitter_meta(): void {

		?>
		<meta name="twitter:title" content="<?php echo esc_attr( $this->get_title() ); ?>"/>
		<meta name="twitter:description" content="<?php echo esc_attr( $this->get_description() ); ?>"/>
		<?php

		$this->maybe_output_aioseo_social_meta(
			'getTwitterMeta',
			[
				'twitter:card',
				'twitter:site',
			],
			'name'
		);
	}

	/**
	 * Returns the Twitter meta-title.
	 *
	 * @since 1.7.1
	 *
	 * @return string
	 */
	public function get_twitter_meta_title(): string {

		return '<meta name="twitter:title" content="' . esc_attr( $this->get_title() ) . '" />';
	}

	/**
	 * Returns the Facebook meta-title.
	 *
	 * @since 1.7.1
	 *
	 * @return string
	 */
	public function get_fb_meta_title(): string {

		return '<meta property="og:title" content="' . esc_attr( $this->get_title() ) . '" />';
	}

	/**
	 * This function returns `false` if nothing was passed to retain Squirrly's behavior not to
	 * output anything if "No SEO Configuration" was selected and returns CF's description
	 * if anything truthy was passed.
	 *
	 * @since 1.7.1
	 *
	 * @param mixed $desc Description passed to sq_description filter from Squirrly plugin.
	 *
	 * @return false|string
	 */
	public function get_sq_description( $desc ) {

		if ( empty( $desc ) ) {
			return false;
		}

		return $this->get_description();
	}

	/**
	 * Filter social tags.
	 *
	 * @since 1.7.1
	 *
	 * @param array $meta_tags Social meta tags.
	 *
	 * @return mixed
	 */
	public function filter_social_tags( $meta_tags ) {

		if ( ! is_array( $meta_tags ) ) {
			return $meta_tags;
		}

		$social_tags = [
			[
				'tags_to_replace' => [
					'twitter:description',
					'og:description',
				],
				'new_value'       => $this->get_description(),
			],
			[
				'tags_to_replace' => [
					'twitter:title',
					'og:title',
				],
				'new_value'       => $this->get_title(),
			],
		];

		foreach ( $social_tags as $social_tag ) {
			$meta_tags = $this->replace_array_keys_with_new_value(
				$meta_tags,
				$social_tag['tags_to_replace'],
				$social_tag['new_value']
			);
		}

		return array_filter( $meta_tags );
	}

	/**
	 * Replace the value of keys in an array.
	 *
	 * @since 1.7.1
	 *
	 * @param array $arr             Array with keys to be replaced with a new value.
	 * @param array $keys_to_replace Keys to be replaced with new value.
	 * @param mixed $new_value       New value.
	 *
	 * @return array
	 */
	private function replace_array_keys_with_new_value( $arr, $keys_to_replace, $new_value ): array {

		if ( ! is_array( $arr ) ) {
			return $arr;
		}

		foreach ( $keys_to_replace as $key ) {
			if ( array_key_exists( $key, $arr ) ) {
				$arr[ $key ] = $new_value;
			}
		}

		return $arr;
	}

	/**
	 * Change the `{page_title}` value to Conversational Forms title.
	 *
	 * @since 1.7.1
	 *
	 * @param string|mixed $value     The page title.
	 * @param array        $form_data Form data.
	 *
	 * @return string
	 */
	public function filter_page_title_smart_tag_value( $value, $form_data ): string {

		$value = (string) $value;

		if ( empty( $form_data['settings']['conversational_forms_title'] ) ) {
			return $value;
		}

		return esc_html( wp_strip_all_tags( $form_data['settings']['conversational_forms_title'], true ) );
	}
}