<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
 * Hustle_Module_Renderer
 *
 * @package Hustle
 */

/**
 * Class Hustle_Module_Renderer
 * Used to render Embedded, Popup, and Slidein modules.
 *
 * @since 4.0
 */
class Hustle_Module_Renderer extends Hustle_Renderer_Abstract {

	/**
	 * Is optin
	 *
	 * @var bool
	 */
	protected $is_optin = null;

	/**
	 * Whether the module is "optin" or "informational.
	 *
	 * @since 4.0
	 *
	 * @return boolean
	 */
	protected function is_optin() {

		if ( is_null( $this->is_optin ) ) {
			$this->is_optin = ( 'optin' === $this->module->module_mode );
		}

		return $this->is_optin;
	}

	/**
	 * Get main wrapper
	 *
	 * @since 4.0
	 * @param string $subtype Sub types.
	 * @param string $custom_classes Custom classes.
	 * @return string
	 */
	public function get_wrapper_main( $subtype, $custom_classes = '' ) {

		$content        = $this->module->content;
		$design         = $this->module->design;
		$settings       = $this->module->settings;
		$module_type    = $this->module->module_type;
		$module_subtype = $subtype ? $subtype : $module_type;

		$id              = $this->module->module_id;
		$module_id       = sprintf( 'hustle-%s-id-%d', $module_type, $id );
		$module_id_class = sprintf(
			'hustle_module_id_%d module_id_%d',
			$id,
			$id
		);

		$module_palette = str_replace( '.', '_', $design->color_palette );

		$tracking_enabled_data = $this->module->is_tracking_enabled( $module_subtype ) ? 'enabled' : 'disabled';
		$module_data           = sprintf(
			'
			data-id="%d"
			data-render-id="%d"
			data-tracking="%s"
			',
			$id,
			self::$render_ids[ $id ],
			esc_attr( $tracking_enabled_data )
		);

		if ( Hustle_Module_Model::EMBEDDED_MODULE === $module_type ) {

			$module_type     = 'inline';
			$animation_intro = ( '' !== $settings->animation_in ) ? $settings->animation_in : 'no_animation';

			$module_data .= sprintf(
				'
				data-intro="%s"
				data-sub-type="%s"
				',
				esc_attr( $animation_intro ),
				esc_attr( $subtype )
			);

			if ( '1' === $design->customize_size ) {

				if ( '' !== $design->custom_width || '' !== $design->custom_height ) {
					$custom_classes .= ' hustle-size--custom';
				}
			}
		} elseif ( Hustle_Module_Model::POPUP_MODULE === $module_type ) {

			$animation_intro = ( '' !== $settings->animation_in ) ? $settings->animation_in : 'no_animation';
			$animation_outro = ( '' !== $settings->animation_out ) ? $settings->animation_out : 'no_animation';

			$auto_close = '1' === $settings->auto_hide ? Hustle_Time_Helper::to_microseconds( $settings->auto_hide_time, $settings->auto_hide_unit ) : 'false';

			// TODO: remove when the preview view is updated.
			// This is forced on preview so modules like 'Adblock' can still be closed when previewing.
			$overlay_can_close = ! self::$is_preview ? $settings->close_on_background_click : '1';

			$module_data .= sprintf(
				'
				role="dialog"
				aria-modal="true"
				data-intro="%s"
				data-outro="%s"
				data-overlay-close="%s"
				data-close-delay="%s"
				',
				esc_attr( $animation_intro ),
				esc_attr( $animation_outro ),
				esc_attr( $overlay_can_close ),
				esc_attr( $auto_close )
			);

		} elseif ( Hustle_Module_Model::SLIDEIN_MODULE === $module_type ) {

			$position = $settings->display_position;

			$auto_close = '1' === $settings->auto_hide ? Hustle_Time_Helper::to_microseconds( $settings->auto_hide_time, $settings->auto_hide_unit ) : 'false';

			$module_data .= sprintf(
				'
				role="dialog"
				aria-modal="true"
				data-position="%s"
				data-close-delay="%s"
				',
				esc_attr( $position ),
				esc_attr( $auto_close )
			);

			if ( '1' === $design->customize_size ) {

				if ( '' !== $design->custom_width || '' !== $design->custom_height ) {
					$custom_classes .= ' hustle-size--custom';
				}
			}
		}

		$image_class = '';

		if (
			'' !== $content->feature_image && // Feat image exists.
			(
				'' === $content->title && // Title is empty.
				'' === $content->sub_title && // Sub-title is empty.
				'' === $content->main_content && // Content is empty.
				$this->is_show_cta( $content ) // CTA button is hidden.
			)
		) {
			$image_class = 'hustle-image-only';
		}

		$inline_style = ! self::$is_preview ? 'style="opacity: 0;"' : 'style="opacity: 1;"';
		$aria_label   = $content->title . ' ' . esc_html__( 'popup', 'hustle' );

		$html = sprintf(
			'<div
				id="%s"
				class="hustle-ui hustle-%s hustle-palette--%s %s %s %s"
				%s
				%s
				aria-label="%s"
			>',
			esc_attr( $module_id ),
			esc_attr( $module_type ),
			esc_attr( $module_palette ),
			esc_attr( $module_id_class ),
			esc_attr( $image_class ),
			esc_attr( $custom_classes ),
			$module_data,
			$inline_style,
			$aria_label,
		);

		return $html;
	}

	/**
	 * Get content wrapper
	 *
	 * @since 4.0
	 * @return string
	 */
	protected function get_wrapper_content() {

		$module_type = $this->module->module_type;

		if ( Hustle_Module_Model::EMBEDDED_MODULE === $this->module->module_type ) {
			$module_type = 'inline';
		}

		$html = sprintf(
			'<div class="hustle-%s-content">',
			esc_attr( $module_type )
		);

		return $html;
	}

	/**
	 * Get the right body depending if the module is "optin" or "informational".
	 *
	 * @since 4.0
	 * @return string
	 */
	public function get_module_body() {

		if ( $this->is_optin() ) {
			$html = $this->get_optin_body();
		} else {
			$html = $this->get_informational_body();
		}

		return $html;
	}

	/**
	 * Get an overlay mask for pop-ups only.
	 *
	 * @since 4.0
	 * @return string
	 */
	protected function get_overlay_mask() {

		if ( Hustle_Module_Model::POPUP_MODULE !== $this->module->module_type ) {
			$overlay = '';
		} else {
			$overlay = '<div class="hustle-popup-mask hustle-optin-mask" aria-hidden="true"></div>';
		}

		return $overlay;
	}

	/**
	 * Get close button for pop-ups and slide-ins only.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_close_button() {

		$button = '<button class="hustle-button-icon hustle-button-close has-background">
			<span class="hustle-icon-close" aria-hidden="true"></span>
			<span class="hustle-screen-reader">Close this module</span>
		</button>';

		if ( Hustle_Module_Model::EMBEDDED_MODULE === $this->module->module_type ) {
			$button = '';
		}

		$html = $button;

		return $html;
	}

	/**
	 * Get feature image markup.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_feature_image() {

		$html        = '';
		$design      = $this->module->design;
		$source      = esc_url( $this->module->content->feature_image );
		$position    = '';
		$mobile_hide = '';

		if ( 'custom' !== $design->feature_image_horizontal_position || 'custom' !== $design->feature_image_vertical_position ) {

			$x_axis = '';
			$y_axis = '';

			if ( 'custom' !== $design->feature_image_horizontal_position ) {
				$x_axis = $design->feature_image_horizontal_position;
			} else {
				$x_axis = 'custom';
			}

			if ( 'custom' !== $design->feature_image_vertical_position ) {
				$y_axis = $design->feature_image_vertical_position;
			} else {
				$y_axis = 'custom';
			}

			// Legacy class. We're not making use of it as of 4.3.0.
			// Not removing it in case users are using it for custom css.
			$position .= sprintf(
				' class="hustle-image-position--%s%s"',
				esc_attr( $x_axis ),
				esc_attr( $y_axis )
			);
		}

		if ( '1' === $design->enable_mobile_settings && '1' === $design->feature_image_hide_on_mobile ) {
			$mobile_hide = ' hustle-hide-until-sm';
		}

		$alt = $this->module->get_feature_image_alt();

		$html .= sprintf(
			'<div class="hustle-image hustle-image-fit--%s%s" aria-hidden="true">',
			esc_attr( $design->feature_image_fit ),
			esc_attr( $mobile_hide )
		);
		$html .= sprintf(
			'<img src="%s" alt="%s"%s />',
			esc_url( $source ),
			esc_attr( $alt ),
			$position
		);
		$html .= '</div>';

		return $html;
	}

	/**
	 * Check whether the CTA should be shown.
	 *
	 * @since 4.3.0
	 *
	 * @param object $content The stored module's content meta settings.
	 * @return boolean
	 */
	private function is_show_cta( $content ) {

		// If CTA is disabled.
		if ( '0' === $content->show_cta ) {
			return false;
		}

		// Checks for when 1 and 2 CTAs are enabled.
		if ( '1' === $content->show_cta ) {

			// Make sure it has a label.
			// And make sure it has an URL if its action isn't to close the module.
			if ( '' === $content->cta_label ) {
				return false;

			} elseif ( 'close' !== $content->cta_target && '' === $content->cta_url ) {
				return false;
			}
		} else {
			// Make both buttons have a label.
			if ( '' === $content->cta_label || '' === $content->cta_two_label ) {
				return false;
			}

			// And make sure they have an URL if their action isn't to close the module.
			if (
				( 'close' !== $content->cta_target && '' === $content->cta_url ) ||
				( 'close' !== $content->cta_two_target && '' === $content->cta_two_url )
			) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Returns whether the primary CTA can be used as a whole-module trigger.
	 *
	 * Whole-module CTA can intentionally hide the visible button label, so we
	 * only require that the CTA action itself is actionable.
	 *
	 * @since 4.3.0
	 *
	 * @param object $content The stored module's content meta settings.
	 * @return boolean
	 */
	private function has_whole_cta( $content ) {

		if ( '1' !== $content->show_cta || empty( $content->cta_whole ) ) {
			return false;
		}

		if ( 'close' === $content->cta_target ) {
			return true;
		}

		return '' !== $content->cta_url;
	}

	/**
	 * Get call to action button.
	 *
	 * @since 4.0.0
	 * @return string
	 */
	private function get_cta_button() {

		$label  = $this->module->content->cta_label;
		$target = $this->module->content->cta_target;
		$link   = $this->module->content->cta_url;

		$html  = '<div class="hustle-cta-container">';
		$html .= $this->get_cta_markup( $label, $target, $link );

		if ( '2' === $this->module->content->show_cta ) {
			$label_two  = $this->module->content->cta_two_label;
			$target_two = $this->module->content->cta_two_target;
			$link_two   = $this->module->content->cta_two_url;
			// CTA #2.
			$html .= $this->get_cta_markup(
				$label_two,
				$target_two,
				$link_two,
				'cta_2'
			);
		}
		$html .= '</div>';

		// Display CTA helper text if enabled and not empty.
		if ( '1' === $this->module->content->cta_helper_show && '' !== $this->module->content->cta_helper_text ) {
			$allowed_html = array(
				'a'      => array(
					'href'   => true,
					'title'  => true,
					'target' => true,
					'alt'    => true,
				),
				'b'      => array(),
				'strong' => array(),
				'i'      => array(),
				'em'     => array(),
				'del'    => array(),
			);

			$cta_helper = '<p class="hustle-cta-helper-text">' . wp_kses( $this->module->content->cta_helper_text, $allowed_html ) . '</p>';

			/**
			 * Filter the whole markup for the CTA helper text
			 *
			 * @since 4.3.0
			 *
			 * @param $cta_helper CTA helper text markup to be shown.
			 */
			$html .= apply_filters( 'hustle_get_cta_helper_text', $cta_helper );
		}

		/**
		 * Filter the markup for the CTA buttons.
		 *
		 * @since 4.3.0
		 *
		 * @param $html The markup.
		 * @param $this->module The current module.
		 */
		return apply_filters( 'hustle_get_cta_buttons', $html, $this->module );
	}

	/**
	 * Get the markup of each CTA.
	 *
	 * @since 4.3.0
	 *
	 * @param string $label Label.
	 * @param string $target Target.
	 * @param string $url URL.
	 * @param string $cta_type CTA type.
	 * @return string
	 */
	private function get_cta_markup( $label, $target, $url, $cta_type = 'cta' ) {

		$extra_class = 'cta_2' === $cta_type ? 'hustle-last-button' : '';
		$class       = 'hustle-cta-close hustle-button-close ' . $extra_class;
		$data        = '';
		$label       = $this->input_sanitize( $label );

		if ( 'close' !== $target ) {
			$data  = sprintf( 'href="%s" target="_%s"', esc_url( $url ), esc_attr( $target ) );
			$class = $extra_class;
		} else {
			$data = 'href="#"';
		}

		/**
		 * Filter the extra classes for the CTA
		 *
		 * @since 4.3.0
		 *
		 * @param $class The class to be added to the button.
		 * @param $is_second Whether it's the CTA #2.
		 * @param $target The target.
		 */
		$class     = apply_filters( 'hustle_cta_extra_classes', $class, $cta_type, $target );
		$html      = '';
		$html     .= sprintf( '<a class="hustle-button hustle-button-cta %1$s" %2$s %3$s>', esc_attr( $class ), $data, 'data-cta-type="' . esc_attr( $cta_type ) . '"' );
			$html .= stripcslashes( $label );
		$html     .= '</a>';

		/**
		 * Filter the markup each CTA.
		 *
		 * @since 4.3.0
		 *
		 * @param $html The markup.
		 * @param $label The label.
		 * @param $target The target.
		 * @param $url You guess.
		 * @param $class Extra classes for the button.
		 */
		return apply_filters( 'hustle_get_cta_html', $html, $label, $target, $url, $class );
	}

	/**
	 * Adds an off-screen CTA target for whole-module CTA when the visible button
	 * is intentionally hidden by leaving its label empty.
	 *
	 * @since 4.3.0
	 * @return string
	 */
	private function maybe_get_hidden_whole_cta_button() {
		$content = $this->module->content;

		if ( ! $this->has_whole_cta( $content ) || $this->is_show_cta( $content ) ) {
			return '';
		}

		$target = $content->cta_target;
		$url    = $content->cta_url;
		$class  = '';
		$data   = '';

		if ( 'close' !== $target ) {
			$data = sprintf( 'href="%s" target="_%s"', esc_url( $url ), esc_attr( $target ) );
		} else {
			$class = 'hustle-cta-close hustle-button-close';
			$data  = 'href="#"';
		}

		$class = apply_filters( 'hustle_cta_extra_classes', $class, 'cta', $target );
		$class = trim( $class . ' hustle-hidden-whole-module-cta' );

		$html = sprintf(
			'<a class="hustle-button hustle-button-cta %1$s" %2$s %3$s aria-hidden="true" tabindex="-1" style="display:none;"></a>',
			esc_attr( $class ),
			$data,
			'data-cta-type="cta"'
		);

		return apply_filters( 'hustle_get_cta_html', $html, '', $target, $url, $class );
	}

	// ====================================
	// Informational only markup.
	// ====================================

	/**
	 * Get the body of Informational modules.
	 *
	 * @since 4.0
	 *
	 * @return string
	 */
	private function get_informational_body() {

		$layout  = $this->module->design->style;
		$content = $this->module->content;

		$module_layout = 'default';

		if ( 'simple' === $layout ) {
			$module_layout = 'compact';
		}

		if ( 'cabriolet' === $layout ) {
			$module_layout = 'stacked';
		}

		$html = sprintf(
			'<div class="hustle-info hustle-info--%s">',
			esc_attr( $module_layout )
		);

			$html .= '<div class="hustle-main-wrapper">';

				$html .= '<div class="hustle-layout'
						. ( $this->has_whole_cta( $content ) ? ' hustle-whole-module-cta' : '' ) . '">';

					$html .= ( 'cabriolet' !== $layout ) ? $this->get_close_button() : '';

					$html .= $this->get_informational_body_content();
					$html .= $this->maybe_get_hidden_whole_cta_button();

				$html .= '</div>';

			$html .= '</div>';

			// NSA Link.
			$html .= $this->get_optin_nsa_link( false );

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get the right optin body according to the design.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_informational_body_content() {

		switch ( $this->module->design->style ) {
			case 'minimal':
				return $this->get_informational_design_default();

			case 'simple':
				return $this->get_informational_design_compact();

			default: // 'cabriolet'.
				return $this->get_informational_design_stacked();

		}
	}

	/**
	 * Get default (minimal) layout markup for informational module.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_informational_design_default() {

		$html = '';

		$content = $this->module->content;

		// Header.
		if ( '' !== $content->title || '' !== $content->sub_title ) {

			$html .= '<div class="hustle-layout-header">';

			if ( '' !== $content->title ) {
				$html     .= '<h3 class="hustle-title">';
					$html .= $this->input_sanitize( $content->title );
				$html     .= '</h3>';
			}

			if ( '' !== $content->sub_title ) {
				$html     .= '<h4 class="hustle-subtitle">';
					$html .= $this->input_sanitize( $content->sub_title );
				$html     .= '</h4>';
			}

			$html .= '</div>';
		}

		// Content.
		if ( '' !== $content->main_content || '' !== $content->feature_image ) {

			$html .= '<div class="hustle-layout-content">';

			if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
				$html .= $this->get_feature_image();
			}

			if ( '' !== $content->main_content ) {
				$html             .= '<div class="hustle-content">';
					$html         .= '<div class="hustle-content-wrap">';
						$html     .= '<div class="hustle-group-content">';
							$html .= $this->get_module_main_content( $content );
						$html     .= '</div>';
					$html         .= '</div>';
				$html             .= '</div>';
			}

			if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
				$html .= $this->get_feature_image();
			}

			$html .= '</div>';

		}

		// Footer.
		if ( $this->is_show_cta( $content ) ) {

			$html     .= '<div class="hustle-layout-footer">';
				$html .= $this->get_cta_button();
			$html     .= '</div>';

		}

		return $html;
	}

	/**
	 * Get compact (simple) layout markup for informational module.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_informational_design_compact() {

		$html = '';

		$content = $this->module->content;

		// Image (Left).
		if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
			$html .= $this->get_feature_image();
		}

		// Content.
		if (
			'' !== $content->title ||
			'' !== $content->sub_title ||
			'' !== $content->main_content ||
			$this->is_show_cta( $content )
		) {

			$html .= '<div class="hustle-content">';

				$html .= '<div class="hustle-content-wrap">';

			if ( '' !== $content->title || '' !== $content->sub_title ) {

				$html .= '<div class="hustle-group-title">';

				if ( '' !== $content->title ) {
					$html     .= '<h3 class="hustle-title">';
						$html .= $this->input_sanitize( $content->title );
					$html     .= '</h3>';
				}

				if ( '' !== $content->sub_title ) {
					$html     .= '<h4 class="hustle-subtitle">';
						$html .= $this->input_sanitize( $content->sub_title );
					$html     .= '</h4>';
				}

				$html .= '</div>';
			}

			if ( '' !== $content->main_content ) {

				$html     .= '<div class="hustle-group-content">';
					$html .= $this->get_module_main_content( $content );
				$html     .= '</div>';

			}

			if ( $this->is_show_cta( $content ) ) {

				$html .= $this->get_cta_button();

			}

				$html .= '</div>';

			$html .= '</div>';

		}

		// Image (Right).
		if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
			$html .= $this->get_feature_image();
		}

		return $html;
	}

	/**
	 * Get stacked (cabriolet) layout markup for informational module.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_informational_design_stacked() {

		$html = '';

		$content = $this->module->content;

		$html .= $this->get_close_button();

		// Header.
		$html .= '<div class="hustle-layout-header">';

		if ( '' !== $content->title ) {
			$html     .= '<h3 class="hustle-title">';
				$html .= $this->input_sanitize( $content->title );
			$html     .= '</h3>';
		}

		if ( '' !== $content->sub_title ) {
			$html     .= '<h4 class="hustle-subtitle">';
				$html .= $this->input_sanitize( $content->sub_title );
			$html     .= '</h4>';
		}
		$html .= '</div>';

		// Body.
		if (
			'' !== $content->main_content ||
			'' !== $content->feature_image ||
			$this->is_show_cta( $content )
		) {

			$html .= '<div class="hustle-layout-body">';

				// Image (Left).
			if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
				$html .= $this->get_feature_image();
			}

			if (
					'' !== $content->main_content ||
					$this->is_show_cta( $content )
				) {

				$html .= '<div class="hustle-content">';

					$html .= '<div class="hustle-content-wrap">';

				if ( '' !== $content->main_content ) {

					$html .= '<div class="hustle-group-content">';
					$html .= $this->get_module_main_content( $content );
					$html .= '</div>';

				}

				if ( $this->is_show_cta( $content ) ) {
					$html .= $this->get_cta_button();
				}

					$html .= '</div>';

				$html .= '</div>';

			}

				// Image (Right).
			if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
				$html .= $this->get_feature_image();
			}

			$html .= '</div>';

		}

		return $html;
	}

	// ====================================
	// Opt-in only markup.
	// ====================================

	/**
	 * Get the body of Opt-In modules.
	 *
	 * @since 4.0
	 *
	 * @return string
	 */
	public function get_optin_body() {

		$layout  = $this->module->design->form_layout;
		$content = $this->module->content;

		$module_layout = 'default';

		if ( 'two' === $layout ) {
			$module_layout = 'compact';
		}

		if ( 'three' === $layout ) {
			$module_layout = 'focus-optin';
		}

		if ( 'four' === $layout ) {
			$module_layout = 'focus-content';
		}

		$html = sprintf(
			'<div class="hustle-optin hustle-optin--%s">',
			$module_layout
		);

		if ( 'two' === $layout ) {
			$html .= '<div class="hustle-optin-content">';
		}

				$html .= $this->maybe_get_success_message();

				$html .= '<div class="hustle-layout'
						. ( $this->has_whole_cta( $content ) ? ' hustle-whole-module-cta' : '' ) . '">';

					$html .= '<div class="hustle-main-wrapper">';

					$html .= $this->get_close_button();

					$html .= $this->get_optin_body_content();
					$html .= $this->maybe_get_hidden_whole_cta_button();

					$html .= '</div>';

					$html .= $this->get_optin_nsa_link();

				$html .= '</div>';

		if ( 'two' === $layout ) {
			$html .= '</div>';
		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get opt-in success message.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function maybe_get_success_message() {

		$html            = '';
		$emails          = $this->module->emails;
		$success_option  = $emails->after_successful_submission;
		$success_message = $emails->success_message;
		$auto_close      = '1' === $emails->auto_close_success_message ? Hustle_Time_Helper::to_microseconds( $emails->auto_close_time, $emails->auto_close_unit ) : 'false';

		if ( 'show_success' === $success_option || ( 'redirect' === $success_option && '' === $emails->redirect_url )
				|| 'newtab_thankyou' === $emails->redirect_tab ) {

			$html .= sprintf(
				'<div class="hustle-success" data-close-delay="%s" style="display: none;">',
				esc_attr( $auto_close )
			);

				$html .= '<span class="hustle-icon-check" aria-hidden="true"></span>';

			if ( '' !== $success_message ) {

				$html .= '<div class="hustle-success-content">';

				if ( is_admin() ) {
					$html .= do_shortcode( $success_message );
				}

				$html .= '</div>';

			}

			$html .= '</div>';

		}

		return $html;
	}

	/**
	 * Get the right optin body according to the design.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_optin_body_content() {

		switch ( $this->module->design->form_layout ) {
			case 'one':
				return $this->get_optin_design_default();

			case 'two':
				return $this->get_optin_design_compact();

			case 'three':
				return $this->get_optin_design_focus_optin();

			default: // four.
				return $this->get_optin_design_focus_content();
		}
	}

	/**
	 * Get the markup according to design "one".
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_optin_design_default() {

		$html    = '';
		$content = $this->module->content;
		$design  = $this->module->design;

		$html .= '<div class="hustle-layout-body">';

		if (
					'' !== $content->title ||
					'' !== $content->sub_title ||
					'' !== $content->feature_image ||
					'' !== $content->main_content ||
					$this->is_show_cta( $content )
			) {

			$html .= sprintf( '<div class="hustle-layout-content hustle-layout-position--%s">', esc_attr( $design->feature_image_position ) );

			if ( '' !== $content->feature_image && ( 'left' === $design->feature_image_position || 'above' === $design->feature_image_position ) ) {
				$html .= $this->get_feature_image();
			}

			if (
					'' !== $content->title ||
					'' !== $content->sub_title ||
					'' !== $content->main_content ||
					$this->is_show_cta( $content )
			) {

				$html .= '<div class="hustle-content">';

					$html .= '<div class="hustle-content-wrap">';

				if ( '' !== $content->title || '' !== $content->sub_title ) {

					$html .= '<div class="hustle-group-title">';

					if ( '' !== $content->title ) {
						$html .= '<h3 class="hustle-title">';
						$html .= $this->input_sanitize( $content->title );
						$html .= '</h3>';
					}

					if ( '' !== $content->sub_title ) {
						$html .= '<h4 class="hustle-subtitle">';
						$html .= $this->input_sanitize( $content->sub_title );
						$html .= '</h4>';
					}

						$html .= '</div>';
				}

				if ( '' !== $content->main_content ) {
					$html .= '<div class="hustle-group-content">';
					$html .= $this->get_module_main_content( $content );
					$html .= '</div>';
				}

				if ( $this->is_show_cta( $content ) ) {

					$html .= $this->get_cta_button();

				}

						$html .= '</div>';

						$html .= '</div>';
			}

			if ( '' !== $content->feature_image && ( 'right' === $design->feature_image_position || 'below' === $design->feature_image_position ) ) {
				$html .= $this->get_feature_image();
			}

			$html .= '</div>';

		}

			$html .= $this->get_form();

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get the markup according to design "two".
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_optin_design_compact() {

		$html    = '';
		$content = $this->module->content;

		$html .= '<div class="hustle-layout-body">';

		if ( '' !== $content->feature_image && 'left' === $this->module->design->feature_image_position ) {
			$html .= $this->get_feature_image();
		}

			$html .= '<div class="hustle-layout-content">';

		if (
					'' !== $content->title ||
					'' !== $content->sub_title ||
					'' !== $content->main_content ||
					$this->is_show_cta( $content )
				) {

			$html .= '<div class="hustle-content">';

				$html .= '<div class="hustle-content-wrap">';

			if ( '' !== $content->title || '' !== $content->sub_title ) {

				$html .= '<div class="hustle-group-title">';

				if ( '' !== $content->title ) {
					$html     .= '<h3 class="hustle-title">';
						$html .= $this->input_sanitize( $content->title );
					$html     .= '</h3>';
				}

				if ( '' !== $content->sub_title ) {
					$html     .= '<h4 class="hustle-subtitle">';
						$html .= $this->input_sanitize( $content->sub_title );
					$html     .= '</h4>';
				}

				$html .= '</div>';

			}

			if ( '' !== $content->main_content ) {
				$html     .= '<div class="hustle-group-content">';
					$html .= $this->get_module_main_content( $content );
				$html     .= '</div>';
			}

			if ( $this->is_show_cta( $content ) ) {

				$html .= $this->get_cta_button();

			}

				$html .= '</div>';

					$html .= '</div>';

		}

				$html .= $this->get_form();

			$html .= '</div>';

		if ( '' !== $content->feature_image && 'right' === $this->module->design->feature_image_position ) {
			$html .= $this->get_feature_image();
		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get the markup according to design "three".
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_optin_design_focus_optin() {

		$html    = '';
		$content = $this->module->content;

		$html .= '<div class="hustle-layout-body">';

		if ( 'right' === $this->module->design->feature_image_position ) {
			$html .= $this->get_form();
		}

		if (
				'' !== $content->title ||
				'' !== $content->sub_title ||
				'' !== $content->feature_image ||
				'' !== $content->main_content ||
				$this->is_show_cta( $content )
			) {

			$html .= '<div class="hustle-layout-content">';

			if ( '' !== $content->feature_image ) {
				$html .= $this->get_feature_image();
			}

			if (
					'' !== $content->title ||
					'' !== $content->sub_title ||
					'' !== $content->main_content ||
					$this->is_show_cta( $content )
				) {

				$html .= '<div class="hustle-content">';

					$html .= '<div class="hustle-content-wrap">';

				if ( '' !== $content->title || '' !== $content->sub_title ) {

					$html .= '<div class="hustle-group-title">';

					if ( '' !== $content->title ) {
						$html     .= '<h3 class="hustle-title">';
							$html .= $this->input_sanitize( $content->title );
						$html     .= '</h3>';
					}

					if ( '' !== $content->sub_title ) {
						$html     .= '<h4 class="hustle-subtitle">';
							$html .= $this->input_sanitize( $content->sub_title );
						$html     .= '</h4>';
					}

						$html .= '</div>';

				}

				if ( '' !== $content->main_content ) {
					$html     .= '<div class="hustle-group-content">';
						$html .= $this->get_module_main_content( $content );
					$html     .= '</div>';
				}

				if ( $this->is_show_cta( $content ) ) {

					$html .= $this->get_cta_button();

				}

						$html .= '</div>';

							$html .= '</div>';

			}

				$html .= '</div>';

		}

		if ( 'left' === $this->module->design->feature_image_position ) {
			$html .= $this->get_form();
		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get the markup according to design "four".
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_optin_design_focus_content() {

		$html    = '';
		$content = $this->module->content;

		$html .= '<div class="hustle-layout-body">';

		if ( 'left' === $this->module->design->feature_image_position ) {

			$html .= '<div class="hustle-layout-sidebar">';

			if ( '' !== $content->feature_image ) {
				$html .= $this->get_feature_image();
			}

				$html .= $this->get_form();

				$html .= '</div>';

		}

		if (
				'' !== $content->title ||
				'' !== $content->sub_title ||
				'' !== $content->main_content ||
				$this->is_show_cta( $content )
			) {

			$html .= '<div class="hustle-layout-content">';

				$html .= '<div class="hustle-content">';

					$html .= '<div class="hustle-content-wrap">';

			if ( '' !== $content->title || '' !== $content->sub_title ) {

				$html .= '<div class="hustle-group-title">';

				if ( '' !== $content->title ) {
					$html     .= '<h3 class="hustle-title">';
						$html .= $this->input_sanitize( $content->title );
					$html     .= '</h3>';
				}

				if ( '' !== $content->sub_title ) {
					$html     .= '<h4 class="hustle-subtitle">';
						$html .= $this->input_sanitize( $content->sub_title );
					$html     .= '</h4>';
				}

					$html .= '</div>';

			}

			if ( '' !== $content->main_content ) {
				$html     .= '<div class="hustle-group-content">';
					$html .= $this->get_module_main_content( $content );
				$html     .= '</div>';
			}

			if ( $this->is_show_cta( $content ) ) {

				$html .= $this->get_cta_button();

			}

						$html .= '</div>';

						$html .= '</div>';

						$html .= '</div>';

		}

		if ( 'right' === $this->module->design->feature_image_position ) {

			$html .= '<div class="hustle-layout-sidebar">';

			if ( '' !== $content->feature_image ) {
				$html .= $this->get_feature_image();
			}

				$html .= $this->get_form();

				$html .= '</div>';

		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get module fields
	 *
	 * @return array
	 */
	private function form_elements() {
		/**
		 * Edit module fields
		 *
		 * @since 4.1.1
		 * @param string $form_elements Current module fields.
		 */
		$fields = apply_filters( 'hustle_form_elements', $this->module->emails->form_elements );

		return $fields;
	}

	/**
	 * Get the opt-in form markup.
	 *
	 * @since 4.0
	 * @param bool $show_recaptcha Show recaptcha.
	 * @return string
	 */
	private function get_form( $show_recaptcha = true ) {

		$html   = '';
		$fields = $this->form_elements();
		$design = $this->module->design;

		$distribution = $design->optin_form_layout;

		if ( ! $this->is_admin ) {
			$tag        = 'form';
			$extra_data = ' novalidate="novalidate"';
		} else {
			$tag        = 'div';
			$extra_data = '';
		}

		$html .= sprintf(
			'<%s class="hustle-layout-form"%s>',
			$tag,
			$extra_data
		);

		// Form fields.
		$html .= sprintf(
			'<div class="hustle-form%s">',
			'inline' === $distribution ? ' hustle-form-inline' : ''
		);

			$html .= $this->get_form_fields( $fields );

			$html .= $this->get_custom_fields();

		$html .= '</div>';

		// Common hidden fields with useful data.
		$html .= $this->get_common_hidden_fields();

		// GDPR checkbox.
		$html .= $this->get_field_gdpr( $fields );

		// reCaptchaget_recaptcha_container.
		if ( $show_recaptcha ) {
			$html .= $this->get_recaptcha_container( $fields );
			$html .= $this->get_turnstile_container( $fields );
		}

		// Error message.
		$html .= $this->get_form_error( $fields );

		$html .= sprintf( '</%s>', $tag );

		return $html;
	}

	/**
	 * Get opt-in form fields markup.
	 *
	 * @since 4.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_form_fields( $fields ) {
		$html = '';
		// Keeping the `hustle-proximity-%s` class just for users. Don't use it.
		$html .= sprintf(
			'<div class="hustle-form-fields hustle-proximity-%s">',
			( '0' === $this->module->design->customize_form_fields_proximity ? 'joined' : 'separated' )
		);

		$hidden_fields_markup = '';
		if ( is_array( $fields ) ) {

			foreach ( $fields as $name => $field ) {
				if ( in_array( $field['type'], array( 'submit', 'gdpr', 'recaptcha', 'turnstile' ), true ) ) {
					continue;
				}

				if ( 'hidden' !== $field['type'] ) {
					$html .= $this->get_form_input( $field );
				} else {
					$hidden_fields_markup .= $this->get_form_hidden_input( $field );
				}
			}
		}
		$html .= $this->get_form_submit( $fields );
		$html .= $hidden_fields_markup;
		$html .= '</div>';
		return $html;
	}

	/**
	 * Get opt-in form input field markup.
	 *
	 * @since 4.0
	 * @param array $field Field.
	 * @return string
	 */
	private function get_form_input( $field ) {
		$type        = isset( $field['type'] ) ? $field['type'] : 'text';
		$name        = isset( $field['name'] ) ? $field['name'] : 'first_name';
		$label       = ( '' !== $field['placeholder'] ) ? $this->input_sanitize( $field['placeholder'] ) : $this->input_sanitize( $field['label'] );
		$sr_label    = ( '' !== $field['label'] ) ? $this->input_sanitize( $field['label'] ) : '';
		$required    = isset( $field['required'] ) && 'true' === $field['required'] ? true : false;
		$to_validate = isset( $field['validate'] ) && 'true' === $field['validate'] ? true : false;

		$module_id       = $this->module->module_id;
		$icon            = $type;
		$value           = '';
		$field_icon      = '';
		$class_icon      = '';
		$class_input     = '';
		$class_status    = $required ? ' hustle-field-required' : '';
		$data_attributes = sprintf( 'data-validate="%s" ', esc_attr( $to_validate ) );

		if ( $required && ! empty( $field['required_error_message'] ) ) {
			$data_attributes .= sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['required_error_message'] ) ) ) );
		}
		if ( $to_validate && ! empty( $field['validation_message'] ) ) {
			$data_attributes .= sprintf( 'data-validation-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $field['validation_message'] ) ) ) );
		}

		switch ( $type ) {

			case 'email':
				$type = 'email';
				break;

			case 'phone':
				$type             = 'text';
				$data_attributes .= 'data-type="phone"';
				break;

			case 'url':
				$icon = 'website';
				break;

			case 'website':
				$type = 'url';
				break;

			case 'timepicker':
				$class_input = 'hustle-time';

				if ( '24' === $field['time_format'] ) {
					$time_format    = 'HH:mm';
					$time_hours     = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
					$time_minutes   = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
					$time_structure = $time_hours . ':' . $time_minutes;
					$time_default   = ( '' !== $time_hours && '' !== $time_minutes ) ? $time_structure : '';
					$value          = $time_default;
				} else {
					$time_format    = 'hh:mm p';
					$time_hours     = ! empty( $field['time_hours'] ) ? $field['time_hours'] : '';
					$time_minutes   = ! empty( $field['time_minutes'] ) ? $field['time_minutes'] : '';
					$time_period    = ! empty( $field['time_period'] ) ? $field['time_period'] : 'am';
					$time_structure = $time_hours . ':' . $time_minutes . ' ' . $time_period;
					$time_default   = ( '' !== $time_hours && '' !== $time_minutes && '' !== $time_period ) ? $time_structure : '';
					$value          = $time_default;
				}

				$data_attributes .= 'data-time-format="' . esc_attr( $time_format ) . '" data-time-default="' . esc_attr( $time_default ) . '" data-time-interval="1" data-time-dropdown="true"';
				break;

			case 'datepicker':
				$date_format = $field['date_format'];

				// These formats come from 4.0, so we keep the same display as in there, even though it was a bug.
				if ( in_array( $field['date_format'], array( 'm/d/Y', 'Y/m/d', 'd/m/Y' ), true ) ) {
					$date_format = 'MM d, yy';
				}

				$class_input      = 'hustle-date';
				$change_year      = wp_json_encode( ! empty( $field['change_year'] ) );
				$change_month     = wp_json_encode( ! empty( $field['change_month'] ) );
				$min_year_range   = ! empty( $field['min_year_range'] ) ? $field['min_year_range'] : 'c-10';
				$max_year_range   = ! empty( $field['max_year_range'] ) ? $field['max_year_range'] : 'c+10';
				$year_range       = $min_year_range . ':' . $max_year_range;
				$data_attributes .= 'data-min-date="null" data-rtl-support="false" data-change-year="' . esc_attr( $change_year ) . '" data-change-month="' . esc_attr( $change_month ) . '" data-year-range="' . esc_attr( $year_range ) . '" data-format="' . esc_attr( $date_format ) . '"';
				break;

			default:
				break;
		}

		if ( 'none' !== $this->module->design->form_fields_icon ) {
			$field_icon = sprintf( '<span class="hustle-icon-%s"></span>', esc_attr( $icon ) );
			$class_icon = ' hustle-field-icon--' . $this->module->design->form_fields_icon;
		}

		$classes = array(
			sprintf(
				'hustle-field%s%s',
				esc_attr( $class_icon ),
				esc_attr( $class_status )
			),
		);

		if ( ! empty( $value ) ) {
			$classes[] = 'hustle-field-filled';
		}

		if ( isset( $field['css_classes'] ) ) {
			$classes[] = $field['css_classes'];
		}

		$html = sprintf(
			'<div class="%s">',
			esc_attr( implode( ' ', $classes ) )
		);

		if ( '' !== $sr_label ) {

			$html .= sprintf(
				'<label for="hustle-field-%s-module-%s" id="hustle-field-%s-module-%s-label" class="hustle-screen-reader">%s</label>',
				esc_attr( $name ),
				esc_attr( $module_id ),
				esc_attr( $name ),
				esc_attr( $module_id ),
				$sr_label
			);

		}

			$html .= sprintf(
				'<input id="hustle-field-%s-module-%s" type="%s" class="hustle-input %s" name="%s" value="%s" aria-labelledby="hustle-field-%s-module-%s-label" %s/>', // TODO: add autocomplete here or to form.
				esc_attr( $name ),
				esc_attr( $module_id ),
				esc_attr( $type ),
				esc_attr( $class_input ),
				esc_attr( $name ),
				esc_attr( $value ),
				esc_attr( $name ),
				esc_attr( $module_id ),
				$data_attributes
			);

			$html     .= '<span class="hustle-input-label" aria-hidden="true" style="flex-flow: row nowrap;">';
				$html .= $field_icon;
				$html .= sprintf( '<span>%s</span>', $label );
			$html     .= '</span>';

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get hidden value.
	 *
	 * @param array $field_data Current field data.
	 * @return string
	 */
	public static function get_hidden_value( $field_data ) {
		$value = '';

		switch ( $field_data['default_value'] ) {

			case 'user_ip':
				$value = Opt_In_Geo::get_user_ip();
				break;
			case 'date_mdy':
				$value = date_i18n( 'm/d/Y', Hustle_Time_Helper::get_local_timestamp(), true );
				break;
			case 'date_dmy':
				$value = date_i18n( 'd/m/Y', Hustle_Time_Helper::get_local_timestamp(), true );
				break;
			case 'embed_id':
				$value = Opt_In_Utils::get_post_data( 'ID' );
				break;
			case 'embed_title':
				$value = Opt_In_Utils::get_post_data( 'post_title' );
				break;
			case 'embed_url':
				$value = Opt_In_Utils::get_current_url();
				break;
			case 'user_agent':
				$value = filter_input( INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_SPECIAL_CHARS );
				break;
			case 'refer_url':
				$value = filter_input( INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_SPECIAL_CHARS );
				if ( ! $value ) {
					$value = Opt_In_Utils::get_current_url();
				}
				break;
			case 'user_id':
				$value = Opt_In_Utils::get_user_data( 'ID' );
				break;
			case 'user_name':
				$value = Opt_In_Utils::get_user_data( 'display_name' );
				break;
			case 'user_email':
				$value = Opt_In_Utils::get_user_data( 'user_email' );
				break;
			case 'user_login':
				$value = Opt_In_Utils::get_user_data( 'user_login' );
				break;
			case 'custom_value':
				$value = $field_data['custom_value'];
				break;
			case 'query_parameter':
				$value = $field_data['query_parameter'] ? (string) filter_input( INPUT_GET, $field_data['query_parameter'], FILTER_SANITIZE_SPECIAL_CHARS ) : '';
				break;

			default:
				break;
		}

		return $value;
	}

	/**
	 * Get the markup for hidden fields.
	 *
	 * @since 4.0.4
	 * @param array $field Current field data.
	 * @return string
	 */
	private function get_form_hidden_input( $field ) {
		$value = self::get_hidden_value( $field );

		/**
		 * Edit the value of the hidden field.
		 *
		 * @since 4.0.4
		 * @param string $value Current value.
		 * @param array $field Current field data.
		 * @param Hustle_Module_Model $this->module Instance of the current module.
		 */
		$value = apply_filters( 'hustle_field_hidden_field_value', $value, $field, $this->module );

		$html = sprintf(
			'<input type="hidden" name="%s" value="%s"/>',
			esc_attr( $field['name'] ),
			esc_attr( $value )
		);

		return $html;
	}

	/**
	 * Get opt-in form submit button markup.
	 *
	 * @since 4.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_form_submit( $fields ) {

		$html = '';

		$label   = __( 'Submit', 'hustle' );
		$loading = __( 'Form is being submitted, please wait a bit.', 'hustle' );

		$classes = isset( $fields['submit']['css_classes'] ) ? $fields['submit']['css_classes'] : '';

		if ( isset( $fields['submit'] ) && isset( $fields['submit']['label'] ) ) {
			$label = wp_strip_all_tags( html_entity_decode( $fields['submit']['label'] ) );
		}

		$html .= sprintf( '<button class="hustle-button hustle-button-submit %s" aria-live="polite" data-loading-text="%s">', esc_attr( $classes ), esc_attr( $loading ) );

			$html .= sprintf( '<span class="hustle-button-text">%s</span>', esc_html( $label ) );

			$html .= '<span class="hustle-icon-loader hustle-loading-icon" aria-hidden="true"></span>';

		$html .= '</button>';

		return $html;
	}

	/**
	 * Get opt-in custom fields markup.
	 * These custom fields are added by provider's, for example: Mailchimp groups.
	 *
	 * @since 4.0
	 * @return string
	 * @throws Exception Addon field isn't a string.
	 */
	private function get_custom_fields() {

		$html = '';

		$connected_addons = Hustle_Provider_Utils::get_addons_instance_connected_with_module( $this->module->module_id );

		foreach ( $connected_addons as $connected_addon ) {

			try {

				$form_hooks = $connected_addon->get_addon_form_hooks( $this->module->module_id );

				if ( $form_hooks instanceof Hustle_Provider_Form_Hooks_Abstract ) {
					$addon_fields = $form_hooks->add_front_form_fields( $this->module );

					// Log errors.
					if ( ! is_string( $addon_fields ) ) {
						throw new Exception( 'The returned markup should be a string.' );
					}

					$html .= $addon_fields;
				}
			} catch ( Exception $e ) {
				Hustle_Provider_Utils::maybe_log( $connected_addon->get_slug(), 'failed to add custom front form fields.', $e->getMessage() );
			}
		}

		return $html;
	}

	/**
	 * Get common hidden fields with data about the displayed module.
	 *
	 * @since 4.0
	 * @return string
	 */
	private function get_common_hidden_fields() {

		$html = '<input type="hidden" name="hustle_module_id" value="' . esc_attr( $this->module->module_id ) . '">';

		$html .= '<input type="hidden" name="post_id" value="' . esc_attr( $this->get_post_id() ) . '">';

		if ( ! empty( $this->sub_type ) ) {
			$html .= '<input type="hidden" name="hustle_sub_type" value="' . esc_attr( $this->sub_type ) . '">';
		}

		return $html;
	}

	/**
	 * Get the GDPR checkbox field markup.
	 *
	 * @since 4.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_field_gdpr( $fields ) {

		$html      = '';
		$module_id = $this->module->module_id;
		$render_id = self::$render_ids[ $module_id ];
		$classes   = isset( $fields['gdpr']['css_classes'] ) ? $fields['gdpr']['css_classes'] : '';

		if ( isset( $fields['gdpr'] ) ) {

			$html .= sprintf(
				'<label for="hustle-gdpr-module-%d-%d" class="hustle-checkbox hustle-gdpr %s">',
				esc_attr( $module_id ),
				esc_attr( $render_id ),
				esc_attr( $classes )
			);

			$data_attributes = ! empty( $fields['gdpr']['required_error_message'] ) ?
				sprintf( 'data-required-error="%s" ', esc_attr( wp_strip_all_tags( html_entity_decode( $fields['gdpr']['required_error_message'] ) ) ) ) : '';

			$html .= sprintf(
				'<input type="checkbox" name="gdpr" id="hustle-gdpr-module-%d-%d" %s />',
				esc_attr( $module_id ),
				esc_attr( $render_id ),
				$data_attributes
			);

			$html .= '<span aria-hidden="true"></span>';

			$html .= sprintf(
				'<span>%s</span>',
				$this->input_sanitize( $fields['gdpr']['gdpr_message'] )
			);

			$html .= '</label>';

		}

		return $html;
	}

	/**
	 * Get the filtered and parsed main content for the module.
	 *
	 * @since 4.0
	 *
	 * @param object $content Content.
	 * @return string
	 */
	private function get_module_main_content( $content ) {

		$allowed_html = wp_kses_allowed_html( 'post' );

		// iframe.
		$allowed_html['iframe'] = array(
			'src'             => array(),
			'height'          => array(),
			'width'           => array(),
			'frameborder'     => array(),
			'allowfullscreen' => array(),
		);
		// Form.
		$allowed_html['form'] = array(
			'class'          => array(),
			'action'         => true,
			'accept'         => true,
			'accept-charset' => true,
			'enctype'        => true,
			'method'         => true,
			'name'           => true,
			'target'         => true,
			'role'           => array(),
		);
		// Inputs.
		$allowed_html['input'] = array(
			'class'       => array(),
			'id'          => array(),
			'name'        => array(),
			'value'       => array(),
			'type'        => array(),
			'placeholder' => array(),
		);
		// Select.
		$allowed_html['select'] = array(
			'class' => array(),
			'id'    => array(),
			'name'  => array(),
			'value' => array(),
			'type'  => array(),
		);
		// Select options.
		$allowed_html['option'] = array(
			'selected' => array(),
		);
		// Style.
		$allowed_html['style'] = array(
			'types' => array(),
		);

		// i for fontawesome.
		$allowed_html['i'] = array(
			'class' => array(),
		);
		// Keep allowing scripts because users are using it.
		$allowed_html['script'] = array(
			'async'          => array(),
			'crossorigin'    => array(),
			'integrity'      => array(),
			'referrerpolicy' => array(),
			'nomodule'       => array(),
			'src'            => array(),
			'type'           => array(),
			'defer'          => array(),
			'charset'        => array(),
		);

		/**
		 * Allows editing the allowed html tags for the modules' main content.
		 *
		 * @since 4.0.0.1
		 */
		$allowed_html = apply_filters( 'hustle_module_main_content_allowed_html', $allowed_html, $this->module );
		$content      = wpautop( wp_kses( $content->main_content, $allowed_html ) );

		/**
		 * Allows editing the escaped main content before doing the shortcodes.
		 *
		 * @since 4.0.0.1
		 */
		$content = apply_filters( 'hustle_module_main_content', $content, $this->module );

		// Process the [embed] shortcode.
		if ( has_shortcode( $content, 'embed' ) ) {
			$wp_embed = new WP_Embed();
			$content  = $wp_embed->run_shortcode( $content );
		}

		return do_shortcode( $content );
	}

	/**
	 * Get the filtered and parsed content for the module.
	 *
	 * @since 4.2.1
	 *
	 * @param object $content Contet.
	 * @return string
	 */
	public function input_sanitize( $content ) {
		$allowed_html = wp_kses_allowed_html( 'post' );

		/**
		 * Custom filtering for every other field than main content.
		 * By default it uses wp_kses post rules.
		 *
		 * @since 4.2.1
		 */
		$allowed_html = apply_filters( 'hustle_module_input_content_allowed_html', $allowed_html, $this->module );

		$content = wp_kses( $content, $allowed_html );
		return $content;
	}

	/**
	 * Get the opt-in form error message markup.
	 *
	 * @since 4.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_form_error( $fields ) {

		if ( isset( $fields['submit'] ) && ! empty( $fields['submit']['error_message'] ) ) {
			$default_error = esc_attr( wp_strip_all_tags( html_entity_decode( $fields['submit']['error_message'] ) ) );
		} else {
			$default_error = __( 'There was an error submitting the form', 'hustle' );
		}

		$html = sprintf(
			'<div class="hustle-error-message" style="display: none;" data-default-error="%s">',
			esc_attr( $default_error )
		);

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get opt-in never see link markup.
	 *
	 * @since 4.0
	 * @param bool $wrapper Wrapper.
	 * @return string
	 */
	private function get_optin_nsa_link( $wrapper = true ) {

		$html = '';

		if ( Hustle_Module_Model::EMBEDDED_MODULE !== $this->module->module_type ) {

			$content = $this->module->content;
			$message = ( '' !== $content->never_see_link_text ) ? $content->never_see_link_text : esc_html__( 'Never see this message again', 'hustle' );

			if ( (int) $content->show_never_see_link ) {
				$html         .= ( true === $wrapper ) ? '<div class="hustle-layout-footer">' : '';
					$html     .= '<p class="hustle-nsa-link">';
						$html .= '<a href="#">' . $this->input_sanitize( $message ) . '</a>';
					$html     .= '</p>';
				$html         .= ( true === $wrapper ) ? '</div>' : '';
			}
		}
		return $html;
	}

	/**
	 * Get recaptcha container if configured.
	 *
	 * @since 4.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_recaptcha_container( $fields ) {

		$html   = '';
		$fields = $this->module->emails->form_elements;

		// Check if this module has a recaptcha field, and that its creds were added.
		if ( $this->is_recaptcha_active( $fields ) ) {

			$recaptcha = $fields['recaptcha'];

			$recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
			$site_key_key      = $recaptcha_version . '_site_key';

			if ( 'v2_checkbox' === $recaptcha_version ) {
				$size       = $recaptcha['recaptcha_type'];
				$badge      = '';
				$show_badge = true;

			} else {
				$size       = 'invisible';
				$show_badge = '1' === $recaptcha[ $recaptcha_version . '_show_badge' ];
				$badge      = 'inline';
			}

			$recaptcha_classes = isset( $recaptcha['css_classes'] ) ? $recaptcha['css_classes'] : '';

			if ( ! $show_badge ) {
				$recaptcha_classes .= ' hustle-recaptcha-nobadge';
			}

			$recaptcha_theme = ( 'v2_invisible' === $recaptcha['version'] ) ? $recaptcha['v2_invisible_theme'] : $recaptcha['recaptcha_theme'];

			$extra_data = sprintf(
				'data-size="%s" data-theme="%s" data-badge="%3$s"',
				esc_attr( $size ),
				esc_attr( $recaptcha_theme ),
				esc_attr( $badge )
			);

			$recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();
			$render_id          = self::$render_ids[ $this->module->module_id ];
			$html              .= sprintf(
				'<div id="hustle-modal-recaptcha-%1$d-%2$d" class="hustle-recaptcha %3$s" data-required-error="%4$s" data-sitekey="%5$s" data-version=%6$s %7$s></div>',
				esc_attr( $this->module->id ),
				esc_attr( $render_id ),
				esc_attr( $recaptcha_classes ),
				esc_attr( wp_strip_all_tags( html_entity_decode( $fields['recaptcha']['validation_message'] ) ) ),
				esc_attr( $recaptcha_settings[ $site_key_key ] ),
				esc_attr( $recaptcha_version ),
				$extra_data
			);

			// Display custom text instead of badge if hidden.
			if ( ! $show_badge ) {

				$html .= sprintf(
					'<div class="hustle-recaptcha-copy">%s</div>',
					$this->input_sanitize( $recaptcha[ $recaptcha_version . '_badge_replacement' ] )
				);
			}

			// The input that will hold the recaptcha's response for backend validation on form submit.
			$html .= '<input type="hidden" name="recaptcha-response" class="recaptcha-response-input" value="">';

			/**
			 * Filter the markup for the recaptcha container
			 *
			 * @since 4.1.1
			 *
			 * @param object $this->module       Current module. Instance of Hustle_Module_Model.
			 * @param array  $recaptcha          Module's recaptcha field settings.
			 * @param array  $recaptcha_settings Global stored recaptcha credentials.
			 * @param string $render_id          The render ID of the currently rendered module instance.
			 */
			$html = apply_filters( 'hustle_get_module_recaptcha_container', $html, $this->module, $recaptcha, $recaptcha_settings, $render_id );
		}

		return $html;
	}

	/**
	 * Get Cloudflare Turnstile container if configured.
	 *
	 * @since 4.0.0
	 * @param array $fields Fields.
	 * @return string
	 */
	private function get_turnstile_container( $fields ) {

		$html   = '';
		$fields = $this->module->emails->form_elements;

		if ( $this->is_turnstile_active( $fields ) ) {

			$turnstile          = $fields['turnstile'];
			$turnstile_settings = Hustle_Settings_Admin::get_turnstile_settings();
			$render_id          = self::$render_ids[ $this->module->module_id ];
			$css_classes        = isset( $turnstile['css_classes'] ) ? $turnstile['css_classes'] : '';
			$theme              = isset( $turnstile['turnstile_theme'] ) ? $turnstile['turnstile_theme'] : 'auto';
			$size               = isset( $turnstile['turnstile_size'] ) ? $turnstile['turnstile_size'] : 'normal';
			$language           = isset( $turnstile['language'] ) ? $turnstile['language'] : $turnstile_settings['language'];

			$html .= sprintf(
				'<div id="hustle-modal-turnstile-%1$d-%2$d" style="margin-top: 10px;" class="hustle-turnstile %3$s" data-required-error="%4$s" data-sitekey="%5$s" data-theme="%6$s" data-size="%7$s" data-language="%8$s"></div>',
				esc_attr( $this->module->id ),
				esc_attr( $render_id ),
				esc_attr( $css_classes ),
				esc_attr( wp_strip_all_tags( html_entity_decode( $turnstile['validation_message'] ) ) ),
				esc_attr( $turnstile_settings['turnstile_api_key'] ),
				esc_attr( $theme ),
				esc_attr( $size ),
				esc_attr( $language )
			);
		}

		return $html;
	}

	/**
	 * Whether the current module's Turnstile can be displayed.
	 * Check whether this module has a turnstile field,
	 * and if the corresponding credentials were already stored.
	 *
	 * @since 4.0.0
	 * @param array $fields This module's fields.
	 * @return bool
	 */
	private function is_turnstile_active( $fields = array() ) {

		if ( empty( $fields ) ) {
			$fields = $this->module->emails->form_elements;
		}

		if ( isset( $fields['turnstile'] ) ) {
			$turnstile_settings = Hustle_Settings_Admin::get_turnstile_settings();

			if ( ! empty( $turnstile_settings['turnstile_api_key'] ) && ! empty( $turnstile_settings['turnstile_client_secret'] ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Whether the current module's recaptcha can be displayed
	 * Check whether this module has a recaptcha field,
	 * and if the corresponding credentials were already stored.
	 *
	 * @since 4.1.1
	 * @param array $fields This module's fields.
	 */
	private function is_recaptcha_active( $fields = array() ) {

		if ( empty( $fields ) ) {
			$fields = $this->module->emails->form_elements;
		}

		// The module does have recaptcha.
		if ( isset( $fields['recaptcha'] ) ) {

			$recaptcha = $fields['recaptcha'];

			$recaptcha_version = empty( $recaptcha['version'] ) ? 'v2_checkbox' : $recaptcha['version'];
			$site_key_key      = $recaptcha_version . '_site_key';
			$secret_key_key    = $recaptcha_version . '_secret_key';

			$recaptcha_settings = Hustle_Settings_Admin::get_recaptcha_settings();

			// Make sure the creds for the selected recaptcha type has been added.
			if ( ! empty( $recaptcha_settings[ $site_key_key ] ) && ! empty( $recaptcha_settings[ $secret_key_key ] ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Handle AJAX display
	 *
	 * @since 4.0
	 * @param Hustle_Module_Model $module Module.
	 * @param array               $data Data.
	 * @param bool                $is_preview Is preview.
	 * @return string
	 */
	public function ajax_display( Hustle_Module_Model $module, $data = array(), $is_preview = true ) {

		self::$is_preview = $is_preview;

		if ( ! empty( $data ) ) {
			$this->module = $module->load_preview( $data );
		} else {
			$this->module = $module->load();
		}

		$response = array(
			'html'   => '',
			'style'  => array(),
			'script' => array(),
			'module' => $this->module,
		);

		$subtype          = Hustle_Module_Model::EMBEDDED_MODULE !== $module->module_type ? null : 'shortcode';
		$response['html'] = $this->get_module( $subtype, 'hustle-preview' );

		$styles = Hustle_Module_Front::print_front_fonts( $module->get_google_fonts(), true );

		// Add the recaptcha script inline for previews.
		if ( $is_preview && Hustle_Model::OPTIN_MODE === $this->module->module_mode ) {
			$fields = $this->module->emails->form_elements;

			// Load the recaptcha script if the module has it, and if the credentials are stored.
			if ( $this->is_recaptcha_active( $fields ) ) {

				$recaptcha = $fields['recaptcha'];
				$source    = Hustle_Module_Front::add_recaptcha_script( $recaptcha['recaptcha_language'], true, true );

				// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
				$response['script'] = '<script src="' . esc_url( $source ) . '" async defer></script>';
			}

			// Load the Turnstile script if the module has it, and if the credentials are stored.
			if ( $this->is_turnstile_active( $fields ) ) {
				// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
				$response['script'] = ( isset( $response['script'] ) ? $response['script'] : '' ) . '<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>';
			}
		}

		// This might be used later for ajax loading.
		ob_start();
		$this->print_styles();
		$styles           .= ob_get_clean();
		$response['style'] = $styles;

		return $response;
	}
}
