<?php
if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

class FrmFormsHelper {

	/**
	 * Store and re-use field type data for the insert_opt_html function (to avoid multiple calls to FrmField::all_field_selection).
	 *
	 * @since 6.10
	 *
	 * @var array|null
	 */
	private static $field_type_data_for_insert_opt_html;

	/**
	 * @since 2.2.10
	 *
	 * @return string
	 */
	public static function form_error_class() {
		return apply_filters( 'frm_form_error_class', 'frm_error_style' );
	}

	/**
	 * @param string       $key
	 * @param false|object $form
	 *
	 * @return string
	 */
	public static function get_direct_link( $key, $form = false ) {
		$target_url = esc_url( admin_url( 'admin-ajax.php?action=frm_forms_preview&form=' . $key ) );
		return apply_filters( 'frm_direct_link', $target_url, $key, $form );
	}

	/**
	 * @param string     $field_name
	 * @param int|string $field_value
	 * @param array      $args
	 *
	 * @return void
	 */
	public static function forms_dropdown( $field_name, $field_value = '', $args = array() ) {
		$defaults = array(
			'blank'        => true,
			'field_id'     => false,
			'onchange'     => false,
			'exclude'      => false,
			'class'        => '',
			'inc_children' => 'exclude',
		);
		$args     = wp_parse_args( $args, $defaults );

		if ( ! $args['field_id'] ) {
			$args['field_id'] = $field_name;
		}

		$query = array();

		if ( $args['exclude'] ) {
			$query['id !'] = $args['exclude'];
		}

		$where    = apply_filters( 'frm_forms_dropdown', $query, $field_name );
		$forms    = FrmForm::get_published_forms( $where, 999, $args['inc_children'] );
		$add_html = array();
		self::add_html_attr( $args['onchange'], 'onchange', $add_html );
		self::add_html_attr( $args['class'], 'class', $add_html );

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		?>
		<select name="<?php echo esc_attr( $field_name ); ?>"
			id="<?php echo esc_attr( $args['field_id'] ); ?>"
			<?php echo wp_strip_all_tags( implode( ' ', $add_html ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<?php if ( $args['blank'] ) { ?>
				<option value=""><?php echo $args['blank'] == 1 ? ' ' : '- ' . esc_attr( $args['blank'] ) . ' -'; // phpcs:ignore Universal.Operators.StrictComparisons ?></option>
			<?php } ?>
			<?php foreach ( $forms as $form ) { ?>
				<option value="<?php echo esc_attr( $form->id ); ?>" <?php selected( $field_value, $form->id ); ?>>
					<?php echo esc_html( '' === $form->name ? self::get_no_title_text() : FrmAppHelper::truncate( $form->name, 50 ) . ( $form->parent_form_id ? __( ' (child)', 'formidable' ) : '' ) ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
				</option>
			<?php } ?>
		</select>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * @since 2.0.6
	 *
	 * @param string $class
	 * @param string $param
	 * @param array  $add_html
	 *
	 * @return void
	 */
	public static function add_html_attr( $class, $param, &$add_html ) {
		if ( $class ) {
			$add_html[ $param ] = sanitize_title( $param ) . '="' . esc_attr( sanitize_text_field( $class ) ) . '"';
		}
	}

	/**
	 * @param false|object|string $selected - The label for the placeholder, or the form object.
	 *
	 * @return void
	 */
	public static function form_switcher( $selected = false ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded, SlevomatCodingStandard.Files.LineLength.LineTooLong
		$where = apply_filters( 'frm_forms_dropdown', array(), '' );
		$forms = FrmForm::get_published_forms( $where );

		$args = array(
			'id'   => 0,
			'form' => 0,
		);

		if ( isset( $_GET['id'] ) && ! isset( $_GET['form'] ) ) {
			unset( $args['form'] );
		} elseif ( isset( $_GET['form'] ) && ! isset( $_GET['id'] ) ) {
			unset( $args['id'] );
		}

		$frm_action = FrmAppHelper::simple_get( 'frm_action', 'sanitize_title' );

		if ( FrmAppHelper::is_admin_page( 'formidable-entries' ) && in_array( $frm_action, array( 'edit', 'show', 'destroy', 'destroy_all' ), true ) ) {
			$args['frm_action'] = 'list';
			$args['form']       = 0;
		} elseif ( FrmAppHelper::is_admin_page( 'formidable' ) && in_array( $frm_action, array( 'new', 'duplicate' ), true ) ) {
			$args['frm_action'] = 'edit';
		} elseif ( FrmAppHelper::is_style_editor_page() ) {
			// Avoid passing style into form switcher on style page.
			unset( $args['id'] );
			$query_args = array(
				'page' => 'formidable-styles',
			);

			if ( $frm_action ) {
				$query_args['frm_action'] = $frm_action;
			}

			$base = add_query_arg( $query_args, admin_url( 'admin.php' ) );
		} elseif ( isset( $_GET['post'] ) ) {
			$args['form'] = 0;
			$base         = admin_url( 'edit.php?post_type=frm_display' );
		}//end if

		$form_id = 0;

		if ( is_object( $selected ) ) {
			$form_id  = $selected->id;
			$selected = $selected->name;
		}

		$name           = $selected === false ? __( 'Switch Form', 'formidable' ) : $selected;
		$name           = '' === $name || is_null( $name ) ? self::get_no_title_text() : strip_tags( $name );
		$truncated_name = FrmAppHelper::truncate( $name, 25 );

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		if ( count( $forms ) < 2 ) {
			?>
			<div id="frm_bs_dropdown">
				<h1>
					<span class="frm_bstooltip" title="<?php echo esc_attr( $truncated_name === $name ? '' : $name ); ?>" data-placement="right">
						<?php echo esc_html( $name ); ?>
					</span>
				</h1>
			</div>
			<?php
			return;
		}
		?>
		<div id="frm_bs_dropdown" class="dropdown <?php echo esc_attr( is_rtl() ? 'dropdown-menu-right' : 'dropdown-menu-left' ); ?>">
			<a href="#" id="frm-navbarDrop" class="frm-dropdown-toggle" data-toggle="dropdown">
				<h1>
					<span class="frm_bstooltip" title="<?php echo esc_attr( $truncated_name === $name ? '' : $name ); ?>" data-placement="right">
						<?php echo esc_html( $name ); ?>
					</span>
					<?php FrmAppHelper::icon_by_class( 'frmfont frm_arrowdown6_icon', array( 'aria-hidden' => 'true' ) ); ?>
				</h1>
			</a>
			<ul class="frm-dropdown-menu frm-on-top frm-inline-modal frm_code_list frm-full-hover" role="menu" aria-labelledby="frm-navbarDrop">
				<?php if ( count( $forms ) > 8 ) { ?>
				<li class="frm-with-search">
					<?php
					FrmAppHelper::show_search_box(
						array(
							'input_id'    => 'dropform',
							'placeholder' => __( 'Search Forms', 'formidable' ),
							'tosearch'    => 'frm-dropdown-form',
							// Specify a value to avoid the $_REQUEST['s'] default value.
							'value'       => '',
						)
					);
					?>
				</li>
				<?php } ?>
				<?php
				foreach ( $forms as $form ) {
					if ( $form->id === $form_id ) {
						// Don't include the selected form in the switcher since it does nothing.
						continue;
					}

					if ( isset( $args['id'] ) ) {
						$args['id'] = $form->id;
					}

					if ( isset( $args['form'] ) ) {
						$args['form'] = $form->id;
					}

					$url       = isset( $base ) ? add_query_arg( $args, $base ) : add_query_arg( $args );
					$form_name = ! empty( $form->name ) ? $form->name : self::get_no_title_text();
					?>
					<li class="frm-dropdown-form">
						<a href="<?php echo esc_url( $url ); ?>" tabindex="-1" class="frm-justify-between">
							<?php echo esc_html( $form_name ); ?>
							<span>
							<?php
							printf(
								/* translators: %d: Form ID */
								esc_html__( '(ID %d)', 'formidable' ),
								esc_attr( $form->id )
							);
							?>
							</span>
							<span class="frm_hidden"><?php echo esc_html( $form->form_key ); ?></span>
						</a>
					</li>
					<?php
					unset( $form );
				}//end foreach
				?>
			</ul>
		</div>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * @param string $col
	 * @param string $sort_col
	 * @param string $sort_dir
	 *
	 * @return void
	 */
	public static function get_sortable_classes( $col, $sort_col, $sort_dir ) {
		echo $sort_col == $col ? 'sorted' : 'sortable'; // phpcs:ignore Universal.Operators.StrictComparisons
		echo $sort_col == $col && $sort_dir === 'desc' ? ' asc' : ' desc'; // phpcs:ignore Universal.Operators.StrictComparisons
	}

	/**
	 * @since 3.0
	 *
	 * @param array|string $field_type
	 *
	 * @return string
	 */
	public static function get_field_link_name( $field_type ) {
		return is_array( $field_type ) ? $field_type['name'] : $field_type;
	}

	/**
	 * @since 3.0
	 *
	 * @param array|string $field_type
	 *
	 * @return string
	 */
	public static function get_field_link_icon( $field_type ) {
		return is_array( $field_type ) && isset( $field_type['icon'] ) ? $field_type['icon'] : 'frmfont frm_pencil_icon';
	}

	/**
	 * Get the invalid form error message
	 *
	 * @since 2.02.07
	 *
	 * @param array $args
	 *
	 * @return string
	 */
	public static function get_invalid_error_message( $args ) {
		$settings_args = $args;

		if ( isset( $args['form'] ) ) {
			$settings_args['current_form'] = $args['form']->id;
		}

		$frm_settings = FrmAppHelper::get_settings( $settings_args );
		$invalid_msg  = do_shortcode( $frm_settings->invalid_msg );
		return apply_filters( 'frm_invalid_error_message', $invalid_msg, $args );
	}

	/**
	 * @param array $atts {
	 *     The success message details.
	 *
	 *     @type string   $message
	 *     @type stdClass $form
	 *     @type int      $entry_id
	 *     @type string   $class
	 * }
	 *
	 * @return string
	 */
	public static function get_success_message( $atts ) {
		$message = apply_filters( 'frm_content', $atts['message'], $atts['form'], $atts['entry_id'] );

		// Only autop if the message includes line breaks.
		$autop = str_contains( $message, "\n" );

		/**
		 * Filters whether to autop the success message.
		 * This is false by default if the message does not include line breaks.
		 *
		 * @since 6.23
		 *
		 * @param bool   $autop
		 * @param string $message
		 * @param object $form
		 */
		$autop = (bool) apply_filters( 'frm_wpautop_success_message', $autop, $message, $atts['form'] );

		if ( $autop ) {
			$message = FrmAppHelper::use_wpautop( $message );
		}

		$message = do_shortcode( $message );
		return '<div class="' . esc_attr( $atts['class'] ) . '" role="status">' . $message . '</div>';
	}

	/**
	 * Used when a form is created
	 *
	 * @param array $values
	 *
	 * @return array
	 */
	public static function setup_new_vars( $values = array() ) {
		global $wpdb;

		if ( $values ) {
			$post_values = $values;
		} else {
			$values      = array();
			$post_values = ! empty( $_POST ) ? $_POST : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}

		$defaults = array(
			'name'        => '',
			'description' => '',
		);

		foreach ( $defaults as $var => $default ) {
			if ( ! isset( $values[ $var ] ) ) {
				$values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
			}
		}

		$values['description'] = FrmAppHelper::use_wpautop( $values['description'] );

		$defaults = array(
			'form_id'        => '',
			'logged_in'      => '',
			'editable'       => '',
			'is_template'    => 0,
			'status'         => 'published',
			'parent_form_id' => 0,
		);

		foreach ( $defaults as $var => $default ) {
			if ( ! isset( $values[ $var ] ) ) {
				$values[ $var ] = FrmAppHelper::get_param( $var, $default, 'get', 'sanitize_text_field' );
			}
		}
		unset( $defaults );

		if ( ! isset( $values['form_key'] ) ) {
			$values['form_key'] = $post_values && isset( $post_values['form_key'] ) ? $post_values['form_key'] : FrmAppHelper::get_unique_key( '', $wpdb->prefix . 'frm_forms', 'form_key' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
		}

		$values                 = self::fill_default_opts( $values, false, $post_values );
		$values['custom_style'] = FrmAppHelper::custom_style_value( $post_values );

		return apply_filters( 'frm_setup_new_form_vars', $values );
	}

	/**
	 * Used when editing a form
	 *
	 * @param array  $values
	 * @param object $record
	 * @param array  $post_values
	 *
	 * @return array
	 */
	public static function setup_edit_vars( $values, $record, $post_values = array() ) {
		if ( ! $post_values ) {
			$post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
		}

		$values['form_key']    = $post_values['form_key'] ?? $record->form_key;
		$values['is_template'] = $post_values['is_template'] ?? $record->is_template;
		$values['status']      = $record->status;

		$values = self::fill_default_opts( $values, $record, $post_values );

		return apply_filters( 'frm_setup_edit_form_vars', $values );
	}

	/**
	 * @param array        $values
	 * @param false|object $record
	 * @param array|false  $post_values
	 *
	 * @return array
	 */
	public static function fill_default_opts( $values, $record, $post_values ) { // phpcs:ignore SlevomatCodingStandard.Complexity.Cognitive.ComplexityTooHigh

		$defaults = self::get_default_opts();

		foreach ( $defaults as $var => $default ) {
			if ( is_array( $default ) ) {
				if ( ! isset( $values[ $var ] ) ) {
					$values[ $var ] = $record && isset( $record->options[ $var ] ) ? $record->options[ $var ] : array();
				}

				foreach ( $default as $k => $v ) {
					$values[ $var ][ $k ] = $post_values && isset( $post_values[ $var ][ $k ] ) ? $post_values[ $var ][ $k ] : ( $record && isset( $record->options[ $var ] ) && isset( $record->options[ $var ][ $k ] ) ? $record->options[ $var ][ $k ] : $v ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong

					if ( is_array( $v ) ) {
						foreach ( $v as $k1 => $v1 ) {
							$values[ $var ][ $k ][ $k1 ] = $post_values && isset( $post_values[ $var ][ $k ][ $k1 ] ) ? $post_values[ $var ][ $k ][ $k1 ] : ( $record && isset( $record->options[ $var ] ) && isset( $record->options[ $var ][ $k ] ) && isset( $record->options[ $var ][ $k ][ $k1 ] ) ? $record->options[ $var ][ $k ][ $k1 ] : $v1 ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
							unset( $k1, $v1 );
						}
					}

					unset( $k, $v );
				}
			} else {
				$values[ $var ] = $post_values && isset( $post_values['options'][ $var ] ) ? $post_values['options'][ $var ] : ( $record && isset( $record->options[ $var ] ) ? $record->options[ $var ] : $default ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
			}

			unset( $var, $default );
		}//end foreach

		return $values;
	}

	/**
	 * @return array
	 */
	public static function get_default_opts() {
		$frm_settings = FrmAppHelper::get_settings();

		return array(
			'submit_value'     => $frm_settings->submit_value,
			'success_action'   => 'message',
			'success_msg'      => $frm_settings->success_msg,
			'show_form'        => 0,
			'akismet'          => '',
			'stopforumspam'    => 0,
			'antispam'         => 0,
			'no_save'          => 0,
			'ajax_load'        => 0,
			'js_validate'      => 0,
			'form_class'       => '',
			'custom_style'     => 1,
			'before_html'      => self::get_default_html( 'before' ),
			'after_html'       => '',
			'submit_html'      => self::get_default_html( 'submit' ),
			'show_title'       => 0,
			'show_description' => 0,
			'ajax_submit'      => 0,
		);
	}

	/**
	 * @since 2.0.6
	 *
	 * @param array $options
	 * @param array $values
	 *
	 * @return void
	 */
	public static function fill_form_options( &$options, $values ) {
		$defaults = self::get_default_opts();

		foreach ( $defaults as $var => $default ) {
			$options[ $var ] = $values['options'][ $var ] ?? $default;
			unset( $var, $default );
		}
	}

	/**
	 * @param string $loc
	 *
	 * @return string
	 */
	public static function get_default_html( $loc ) {
		if ( $loc === 'submit' ) {
			$draft_link   = self::get_draft_link();
			$start_over   = self::get_start_over_shortcode();
			$default_html = <<<SUBMIT_HTML
<div class="frm_submit frm_flex">
<button class="frm_button_submit" type="submit"  [button_action]>[button_label]</button>
[if back_button]<button type="submit" name="frm_prev_page" formnovalidate="formnovalidate" class="frm_prev_page" [back_hook]>[back_label]</button>[/if back_button]
$draft_link
$start_over
</div>
SUBMIT_HTML;
		} elseif ( $loc === 'before' ) {
			$default_html = <<<BEFORE_HTML
<legend class="frm_screen_reader">[form_name]</legend>
[if form_name]<h3 class="frm_form_title">[form_name]</h3>[/if form_name]
[if form_description]<div class="frm_description">[form_description]</div>[/if form_description]
BEFORE_HTML;
		} else {
			$default_html = '';
		}

		return $default_html;
	}

	/**
	 * @return string
	 */
	public static function get_draft_link() {
		return '[if save_draft]<button class="frm_save_draft" [draft_hook]>[draft_label]</button>[/if save_draft]';
	}

	/**
	 * Gets start over button shortcode.
	 *
	 * @since 5.4
	 *
	 * @return string
	 */
	public static function get_start_over_shortcode() {
		return '[if start_over]<a href="#" tabindex="0" class="frm_start_over" [start_over_hook]>[start_over_label]</a>[/if start_over]';
	}

	/**
	 * @param string      $html
	 * @param object      $form
	 * @param string      $submit
	 * @param object|null $form_action
	 * @param array       $values
	 *
	 * @return void
	 */
	public static function get_custom_submit( $html, $form, $submit, $form_action, $values ) {
		$button = self::replace_shortcodes( $html, $form, $submit, $form_action, $values );

		if ( ! str_contains( $button, '[button_action]' ) ) {
			echo FrmAppHelper::maybe_kses( $button ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			return;
		}

		/**
		 * @since 5.0.06
		 */
		$button = apply_filters( 'frm_submit_button_html', $button, compact( 'form' ) );

		if ( FrmAppHelper::should_never_allow_unfiltered_html() ) {
			$button = FrmAppHelper::kses_submit_button( $button );
		}

		$button_parts = explode( '[button_action]', $button );
		$classes      = apply_filters( 'frm_submit_button_class', array(), $form );

		if ( $classes ) {
			$classes      = implode( ' ', $classes );
			$button_class = 'frm_button_submit';

			if ( preg_match( '/\bclass="[^"]*?\b' . preg_quote( $button_class, '/' ) . '\b[^"]*?"/', $button_parts[0] ) ) {
				$button_parts[0] = str_replace( $button_class, $button_class . ' ' . esc_attr( $classes ), $button_parts[0] );
			} else {
				$button_parts[0] .= ' class="' . esc_attr( $classes ) . '"';
			}
		}

		echo $button_parts[0]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		do_action( 'frm_submit_button_action', $form, $form_action );
		echo $button_parts[1]; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * @since 4.0
	 *
	 * @return array
	 */
	public static function html_shortcodes() {
		$codes = array(
			'id'               => array(
				'label' => __( 'Field ID', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'key'              => array(
				'label' => __( 'Field Key', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'field_name'       => array(
				'label' => __( 'Field Name', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'description'      => array(
				'label' => __( 'Field Description', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'label_position'   => array(
				'label' => __( 'Label Position', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'required_label'   => array(
				'label' => __( 'Required Label', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'input'            => array(
				'label' => __( 'Input Field', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'input opt=1'      => array(
				'label' => __( 'Single Option', 'formidable' ),
				'title' => __( 'Show a single radio or checkbox option by replacing 1 with the order of the option', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'input label=0'    => array(
				'label' => __( 'Hide Option Label', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'required_class'   => array(
				'label' => __( 'Required Class', 'formidable' ),
				'title' => __( 'Add class name if field is required', 'formidable' ),
				'class' => 'show_field_custom_html',
			),
			'error_class'      => array(
				'label' => __( 'Error Class', 'formidable' ),
				'title' => __( 'Add class name if field has an error on form submit', 'formidable' ),
				'class' => 'show_field_custom_html',
			),

			'form_name'        => array(
				'label' => __( 'Form Name', 'formidable' ),
				'class' => 'show_before_html show_after_html',
			),
			'form_description' => array(
				'label' => __( 'Form Description', 'formidable' ),
				'class' => 'show_before_html show_after_html',
			),
			'form_key'         => array(
				'label' => __( 'Form Key', 'formidable' ),
				'class' => 'show_before_html show_after_html',
			),
			'deletelink'       => array(
				'label' => __( 'Delete Entry Link', 'formidable' ),
				'class' => 'show_before_html show_after_html',
			),

			'button_label'     => array(
				'label' => __( 'Button Label', 'formidable' ),
				'class' => 'show_submit_html',
			),
			'button_action'    => array(
				'label' => __( 'Button Hook', 'formidable' ),
				'class' => 'show_submit_html',
			),
		);

		/**
		 * @since 4.0
		 */
		return apply_filters( 'frm_html_codes', $codes );
	}

	/**
	 * @since 4.0
	 *
	 * @param array $args
	 *
	 * @return void
	 */
	public static function insert_opt_html( $args ) {
		$class  = $args['class'] ?? '';
		$fields = self::get_field_type_data_for_insert_opt_html();
		$field  = $fields[ $args['type'] ] ?? array();

		self::prepare_field_type( $field );

		if ( ! isset( $field['icon'] ) ) {
			$field['icon'] = 'frmfont frm_pencil_icon';
		}

		$possible_email_field = FrmFieldFactory::field_has_property( $args['type'], 'holds_email_values' );

		if ( $possible_email_field ) {
			$class .= ' show_frm_not_email_to';
		}

		if ( 'url' === $args['type'] ) {
			$class .= ' frm_insert_url';
		}

		$truncated_name = FrmAppHelper::truncate( $args['name'], 60 );

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		?>
		<li class="<?php echo esc_attr( $class ); ?>">
			<a href="javascript:void(0)" class="frmids frm_insert_code" data-code="<?php echo esc_attr( $args['id'] ); ?>">
				<?php
				if ( isset( $field['icon'] ) ) {
					FrmAppHelper::icon_by_class( $field['icon'], array( 'aria-hidden' => 'true' ) );
				}

				echo esc_html( $truncated_name );
				?>
				<span>[<?php echo esc_html( $args['id_label'] ?? $args['id'] ); ?>]</span>
			</a>
			<a href="javascript:void(0)" class="frmkeys frm_insert_code frm_hidden" data-code="<?php echo esc_attr( $args['key'] ); ?>">
				<?php
				if ( isset( $field['icon'] ) ) {
					FrmAppHelper::icon_by_class( $field['icon'], array( 'aria-hidden' => 'true' ) );
				}

				echo esc_html( $truncated_name );
				?>
				<span>[<?php echo esc_html( FrmAppHelper::truncate( $args['key_label'] ?? $args['key'], 7 ) ); ?>]</span>
			</a>
		</li>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * Store and re-use field selection data for use when outputting shortcodes options in shortcode pop up.
	 * This significantly improves performance by avoiding repeat calls to FrmField::all_field_selection.
	 *
	 * @since 6.10
	 *
	 * @return array
	 */
	private static function get_field_type_data_for_insert_opt_html() {
		if ( ! isset( self::$field_type_data_for_insert_opt_html ) ) {
			self::$field_type_data_for_insert_opt_html = FrmField::all_field_selection();
		}
		return self::$field_type_data_for_insert_opt_html;
	}

	/**
	 * @since 4.0
	 *
	 * @param array $args
	 *
	 * @return void
	 */
	public static function insert_code_html( $args ) {
		$defaults = array(
			'class' => '',
			'code'  => '',
			'label' => '',
			'title' => '',
		);

		$args        = array_merge( $defaults, $args );
		$has_tooltip = ! empty( $args['title'] );

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		?>
		<li class="<?php echo esc_attr( $args['class'] ); ?>">
			<a href="javascript:void(0)" class="frm_insert_code <?php echo $has_tooltip ? 'frm_help' : ''; ?>"
				<?php echo $has_tooltip ? 'title="' . esc_attr( $args['title'] ) . '"' : ''; ?>
				data-code="<?php echo esc_attr( $args['code'] ); ?>">
				<?php echo esc_html( FrmAppHelper::truncate( $args['label'], 60 ) ); ?>
				<span>
					[<?php echo esc_html( FrmAppHelper::truncate( $args['code'], 10 ) ); ?>]
				</span>
			</a>
		</li>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * Some field types in add-ons may have been added with only
	 * a field type and name.
	 *
	 * @since 4.0
	 *
	 * @param array|string $field
	 *
	 * @return void
	 */
	public static function prepare_field_type( &$field ) {
		if ( ! is_array( $field ) ) {
			$field = array(
				'name' => $field,
				'icon' => 'frmfont frm_pencil_icon',
			);
		}
	}

	/**
	 * Automatically add end section fields if they don't exist (2.0 migration)
	 *
	 * @since 2.0
	 *
	 * @param object $form
	 * @param array  $fields
	 * @param bool   $reset_fields
	 *
	 * @return void
	 */
	public static function auto_add_end_section_fields( $form, $fields, &$reset_fields ) {
		if ( ! $fields ) {
			return;
		}

		$end_section_values = apply_filters( 'frm_before_field_created', FrmFieldsHelper::setup_new_vars( 'end_divider', $form->id ) );
		$open               = false;
		$prev_order         = false;
		$add_order          = 0;
		$last_field         = false;

		foreach ( $fields as $field ) {
			if ( $prev_order === $field->field_order ) {
				++$add_order;
			}

			if ( $add_order ) {
				$reset_fields       = true;
				$field->field_order = $field->field_order + $add_order;
				FrmField::update( $field->id, array( 'field_order' => $field->field_order ) );
			}

			switch ( $field->type ) {
				case 'divider':
					// Create an end section if open.
					self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $field, 'move' );

					// Mark it open for the next end section.
					$open = true;
					break;
				case 'break':
					self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $field, 'move' );
					break;
				case 'end_divider':
					if ( ! $open ) {
						// The section isn't open, so this is an extra field that needs to be removed.
						FrmField::destroy( $field->id );
						$reset_fields = true;
					}

					// There is already an end section here, so there is no need to create one.
					$open = false;
			}//end switch
			$prev_order = $field->field_order;

			$last_field = $field;
			unset( $field );
		}//end foreach

		self::maybe_create_end_section( $open, $reset_fields, $add_order, $end_section_values, $last_field );
	}

	/**
	 * Create end section field if it doesn't exist. This is for migration from < 2.0
	 * Fix any ordering that may be messed up
	 *
	 * @param bool   $open
	 * @param bool   $reset_fields
	 * @param int    $add_order
	 * @param array  $end_section_values
	 * @param object $field
	 * @param string $move
	 *
	 * @return void
	 */
	public static function maybe_create_end_section( &$open, &$reset_fields, &$add_order, $end_section_values, $field, $move = 'no' ) {
		if ( ! $open ) {
			return;
		}

		$end_section_values['field_order'] = $field->field_order + 1;

		FrmField::create( $end_section_values );

		if ( $move === 'move' ) {
			// Bump the order of current field unless we're at the end of the form
			FrmField::update( $field->id, array( 'field_order' => $field->field_order + 2 ) );
		}

		$add_order   += 2;
		$open         = false;
		$reset_fields = true;
	}

	/**
	 * @param string $html
	 * @param object $form
	 * @param bool   $title
	 * @param bool   $description
	 * @param array  $values
	 *
	 * @return string
	 */
	public static function replace_shortcodes( $html, $form, $title = false, $description = false, $values = array() ) {
		$codes = array(
			'form_name'        => $title,
			'form_description' => $description,
			'entry_key'        => true,
		);

		foreach ( $codes as $code => $show ) {
			if ( $code === 'form_name' ) {
				$replace_with = $form->name;
			} elseif ( $code === 'form_description' ) {
				$replace_with = FrmAppHelper::use_wpautop( $form->description );
			} elseif ( $code === 'entry_key' && ! empty( $_GET ) && isset( $_GET['entry'] ) ) {
				$replace_with = FrmAppHelper::simple_get( 'entry' );
			} else {
				$replace_with = '';
			}

			FrmShortcodeHelper::remove_inline_conditions( FrmAppHelper::is_true( $show ) && $replace_with != '', $code, $replace_with, $html ); // phpcs:ignore Universal.Operators.StrictComparisons, SlevomatCodingStandard.Files.LineLength.LineTooLong
		}

		// Replace [form_key].
		$html = str_replace( '[form_key]', $form->form_key, $html );

		// Replace [frmurl].
		$html = str_replace( '[frmurl]', FrmFieldsHelper::dynamic_default_values( 'frmurl' ), $html );

		if ( str_contains( $html, '[button_label]' ) ) {
			add_filter( 'frm_submit_button', 'FrmFormsHelper::submit_button_label', 1 );
			$submit_label = apply_filters( 'frm_submit_button', $title, $form );
			$submit_label = esc_attr( do_shortcode( $submit_label ) );
			$html         = str_replace( '[button_label]', $submit_label, $html );
		}

		$html = apply_filters( 'frm_form_replace_shortcodes', $html, $form, $values );

		if ( str_contains( $html, '[if back_button]' ) ) {
			$html = preg_replace( '/(\[if\s+back_button\])(.*?)(\[\/if\s+back_button\])/mis', '', $html );
		}

		if ( str_contains( $html, '[if save_draft]' ) ) {
			$html = preg_replace( '/(\[if\s+save_draft\])(.*?)(\[\/if\s+save_draft\])/mis', '', $html );
		}

		if ( str_contains( $html, '[if start_over]' ) ) {
			$html = preg_replace( '/(\[if\s+start_over\])(.*?)(\[\/if\s+start_over\])/mis', '', $html );
		}

		if ( apply_filters( 'frm_do_html_shortcodes', true ) ) {
			return do_shortcode( $html );
		}

		return $html;
	}

	/**
	 * @param string $submit
	 *
	 * @return string
	 */
	public static function submit_button_label( $submit ) {
		if ( ! $submit ) {
			$frm_settings = FrmAppHelper::get_settings();
			$submit       = $frm_settings->submit_value;
		}

		return $submit;
	}

	/**
	 * If the Formidable styling isn't being loaded,
	 * use inline styling to hide the element
	 *
	 * @since 2.03.05
	 *
	 * @return void
	 */
	public static function maybe_hide_inline() {
		$frm_settings = FrmAppHelper::get_settings();

		if ( $frm_settings->load_style === 'none' ) {
			echo ' style="display:none;"';
		} elseif ( $frm_settings->load_style === 'dynamic' ) {
			FrmStylesController::enqueue_style();
		}
	}

	/**
	 * @param array|bool|int|object|string $form
	 *
	 * @return string|null
	 */
	public static function get_form_style_class( $form = false ) {
		$style = self::get_form_style( $form );
		$class = ' with_frm_style';

		if ( ! $style ) {
			return FrmAppHelper::is_admin_page( 'formidable-entries' ) ? $class : null;
		}

		// If submit button needs to be inline or centered.
		if ( is_object( $form ) ) {
			$form = $form->options;
		}

		if ( ! empty( $form['submit_align'] ) ) {
			$submit_align = $form['submit_align'];
		} elseif ( self::form_should_be_inline_and_missing_class( $form ) ) {
			$submit_align = 'inline';
		} else {
			$submit_align = '';
		}

		if ( 'inline' === $submit_align ) {
			$class .= ' frm_inline_form';
			$class .= self::maybe_align_fields_top( $form );
		} elseif ( 'center' === $submit_align ) {
			$class .= ' frm_center_submit';
		}

		return apply_filters( 'frm_add_form_style_class', $class, $style, compact( 'form' ) );
	}

	/**
	 * In order for frm_inline_submit to inline the submit button the form must also have the frm_inline_form that adds the grid-column style rules required for it to work.
	 *
	 * @since 5.0.12
	 *
	 * @param array $form
	 *
	 * @return bool
	 */
	private static function form_should_be_inline_and_missing_class( $form ) {
		if ( isset( $form['form_class'] ) && str_contains( ' ' . $form['form_class'] . ' ', ' frm_inline_form ' ) ) {
			// Not missing class, avoid adding it twice.
			return false;
		}
		return ! empty( $form['submit_html'] ) && str_contains( $form['submit_html'], 'frm_inline_submit' );
	}

	/**
	 * Returns appropriate class if form has top labels
	 *
	 * @param array $form
	 *
	 * @return string
	 */
	private static function maybe_align_fields_top( $form ) {
		return self::form_has_top_labels( $form ) ? ' frm_inline_top' : '';
	}

	/**
	 * Determine if a form has fields with top labels so submit button can be aligned properly
	 *
	 * @param array $form
	 *
	 * @return bool
	 */
	private static function form_has_top_labels( $form ) {
		if ( ! isset( $form['fields'] ) ) {
			return false;
		}

		$fields = $form['fields'];

		if ( count( $fields ) <= 0 ) {
			return false;
		}

		// Start from the fields closest to the submit button.
		$fields = array_reverse( $fields );

		foreach ( $fields as $field ) {
			$type      = $field['original_type'] ?? $field['type'];
			$has_input = FrmFieldFactory::field_has_property( $type, 'has_input' );

			if ( $has_input ) {
				return self::field_has_top_label( $field, $form );
			}
		}

		return false;
	}

	/**
	 * Check if a field's label position is set to "top"
	 *
	 * @param array              $field
	 * @param bool|object|string $form
	 *
	 * @return bool
	 */
	private static function field_has_top_label( $field, $form ) {
		$label_position = FrmFieldsHelper::label_position( $field['label'], $field, $form );
		return in_array( $label_position, array( 'top', 'inside', 'hidden' ), true );
	}

	/**
	 * @param array|bool|int|object|string $form
	 *
	 * @return int|string
	 */
	public static function get_form_style( $form ) {
		$style = 1;

		if ( ! $form || 'default' === $form ) {
			return $style;
		}

		if ( is_object( $form ) && $form->parent_form_id ) {
			// Get the parent form if this is a child
			$form = $form->parent_form_id;
		} elseif ( is_array( $form ) && ! empty( $form['parent_form_id'] ) ) {
			$form = $form['parent_form_id'];
		} elseif ( is_array( $form ) && isset( $form['custom_style'] ) ) {
			$style = $form['custom_style'];
		}

		if ( $form && ( is_string( $form ) || is_int( $form ) ) ) {
			$form = FrmForm::getOne( $form );
		}

		return $form && is_object( $form ) && isset( $form->options['custom_style'] ) ? $form->options['custom_style'] : $style;
	}

	/**
	 * Display the validation error messages when an entry is submitted
	 *
	 * @since 2.0.6
	 *
	 * @param array $args Includes img, errors.
	 *
	 * @return void
	 */
	public static function show_errors( $args ) {
		$invalid_msg = self::get_invalid_error_message( $args );

		if ( $invalid_msg ) {
			echo wp_kses_post( $invalid_msg );
			$show_img = true;
		} else {
			$show_img = false;
		}

		self::show_error(
			array(
				'img'      => $args['img'],
				'errors'   => $args['errors'],
				'show_img' => $show_img,
			)
		);
	}

	/**
	 * Display the error message in the front-end along with the image if set
	 * The image was removed from the styling settings, but it may still be set with a hook
	 * If the message in the global settings is empty, show every validation message in the error box
	 *
	 * @since 2.0.6
	 *
	 * @param array $args Includes img, errors, and show_img.
	 *
	 * @return void
	 */
	public static function show_error( $args ) {
		// Remove any blank messages
		$args['errors'] = array_filter( (array) $args['errors'] );

		$line_break_first = $args['show_img'];

		foreach ( $args['errors'] as $error_key => $error ) {
			if ( $line_break_first && ! is_numeric( $error_key ) && ( $error_key === 'cptch_number' || str_starts_with( $error_key, 'field' ) ) ) {
				continue;
			}

			$id = str_replace( 'field', 'field_', $error_key );
			echo '<div id="' . esc_attr( $id ) . '_error">';

			if ( $args['show_img'] && ! empty( $args['img'] ) ) {
				echo '<img src="' . esc_url( $args['img'] ) . '" alt="" />';
			} else {
				$args['show_img'] = true;
			}

			echo wp_kses_post( $error );

			echo '</div>';
		}
	}

	/**
	 * @param int|string $id
	 *
	 * @return void
	 */
	public static function maybe_get_scroll_js( $id ) {
		$offset = apply_filters( 'frm_scroll_offset', 4, array( 'form_id' => $id ) );

		// phpcs:ignore Universal.Operators.StrictComparisons
		if ( $offset != - 1 ) {
			self::get_scroll_js( $id );
		}
	}

	/**
	 * @param int|string $form_id
	 *
	 * @return void
	 */
	public static function get_scroll_js( $form_id ) {
		echo '<script type="text/javascript">document.addEventListener(\'DOMContentLoaded\',function(){frmFrontForm.scrollMsg(' . (int) $form_id . ');})</script>';
	}

	/**
	 * @since 3.0
	 *
	 * @param int|object|string $form_id
	 * @param mixed             $form
	 *
	 * @return array
	 */
	public static function get_action_links( $form_id, $form ) {
		if ( ! is_object( $form ) ) {
			$form = FrmForm::getOne( $form_id );
		}

		$actions     = array();
		$trash_links = self::delete_trash_links( $form_id );

		if ( 'trash' === $form->status ) {
			$actions['restore'] = $trash_links['restore'];

			if ( current_user_can( 'frm_delete_forms' ) ) {
				$actions['trash'] = $trash_links['delete'];
			}
		} elseif ( current_user_can( 'frm_edit_forms' ) ) {
			$duplicate_link = '?page=formidable&frm_action=duplicate&id=' . $form_id;

			if ( $form->is_template ) {
				$actions['frm_duplicate'] = array(
					'url'   => wp_nonce_url( $duplicate_link ),
					'label' => __( 'Create Form from Template', 'formidable' ),
					'icon'  => 'frmfont frm_clone_icon',
				);
			} else {
				$actions['duplicate'] = array(
					'url'   => wp_nonce_url( $duplicate_link ),
					'label' => __( 'Duplicate Form', 'formidable' ),
					'icon'  => 'frmfont frm_clone_icon',
				);
			}

			$actions['trash'] = self::delete_trash_info( $form_id, $form->status );
		}//end if

		return $actions;
	}

	/**
	 * @param int|object|string $data
	 *
	 * @return string
	 */
	public static function edit_form_link( $data ) {
		$form_id = self::get_form_id_from_data( $data );

		if ( ! $form_id ) {
			return '';
		}

		$label = self::edit_form_link_label( $data );
		return '<a href="' . esc_url( FrmForm::get_edit_link( $form_id ) ) . '">' . esc_html( $label ) . '</a>';
	}

	/**
	 * Returns a text used when no title is set.
	 *
	 * @since 6.16.1
	 *
	 * @return string
	 */
	public static function get_no_title_text() {
		return __( '(no title)', 'formidable' );
	}

	/**
	 * @param int|object|string $data
	 *
	 * @return string
	 */
	public static function edit_form_link_label( $data ) {
		$name = self::get_form_name_from_data( $data );

		if ( ! $name ) {
			return self::get_no_title_text();
		}

		return FrmAppHelper::truncate( $name, 40 );
	}

	/**
	 * @param int|object|string $data
	 *
	 * @return int|string
	 */
	private static function get_form_id_from_data( $data ) {
		return is_object( $data ) ? $data->id : $data;
	}

	/**
	 * @param mixed $data
	 *
	 * @return string
	 */
	private static function get_form_name_from_data( $data ) {
		if ( is_object( $data ) ) {
			return $data->name;
		}

		$form_id = $data;
		return FrmForm::getName( $form_id );
	}

	/**
	 * @param int    $id
	 * @param string $status
	 * @param string $length
	 *
	 * @return string
	 */
	public static function delete_trash_link( $id, $status, $length = 'label' ) {
		$link_details = self::delete_trash_info( $id, $status );
		return self::format_link_html( $link_details, $length );
	}

	/**
	 * @since 3.0
	 *
	 * @param array  $link_details
	 * @param string $length
	 *
	 * @return string
	 */
	public static function format_link_html( $link_details, $length = 'label' ) {
		$link = '';

		if ( ! $link_details ) {
			return $link;
		}

		$link = '<a href="' . esc_url( $link_details['url'] ) . '" class="frm-trash-link"';

		if ( isset( $link_details['data'] ) ) {
			foreach ( $link_details['data'] as $data => $value ) {
				$link .= ' data-' . esc_attr( $data ) . '="' . esc_attr( $value ) . '"';
			}
		} elseif ( isset( $link_details['confirm'] ) ) {
			$link .= ' onclick="return confirm(\'' . esc_attr( $link_details['confirm'] ) . '\')"';
		}

		$label = $link_details[ $length ] ?? $link_details['label'];

		if ( $length === 'icon' && isset( $link_details[ $length ] ) ) {
			$label = '<span class="' . $label . '" title="' . esc_attr( $link_details['label'] ) . '" aria-hidden="true"></span>';
			$link .= ' aria-label="' . esc_attr( $link_details['label'] ) . '"';
		}

		return $link . ( '>' . $label . '</a>' );
	}

	/**
	 * @since 3.0
	 *
	 * @param int    $id
	 * @param string $status
	 *
	 * @return array
	 */
	public static function delete_trash_info( $id, $status ) {
		$labels = self::delete_trash_links( $id );

		if ( 'trash' === $status ) {
			$info = $labels['restore'];
		} elseif ( current_user_can( 'frm_delete_forms' ) ) {
			$info = EMPTY_TRASH_DAYS ? $labels['trash'] : $labels['delete'];
		} else {
			$info = array();
		}

		return $info;
	}

	/**
	 * @since 3.0
	 *
	 * @param int $id
	 *
	 * @return array
	 */
	public static function delete_trash_links( $id ) {
		$current_page = FrmAppHelper::get_simple_request( array( 'param' => 'form_type' ) );
		$base_url     = '?page=formidable&form_type=' . $current_page . '&id=' . $id;

		return array(
			'restore' => array(
				'label' => __( 'Restore from Trash', 'formidable' ),
				'short' => __( 'Restore', 'formidable' ),
				'url'   => wp_nonce_url( $base_url . '&frm_action=untrash', 'untrash_form_' . absint( $id ) ),
			),
			'trash'   => array(
				'label' => __( 'Move Form to Trash', 'formidable' ),
				'short' => __( 'Trash', 'formidable' ),
				'url'   => wp_nonce_url( $base_url . '&frm_action=trash', 'trash_form_' . absint( $id ) ),
				'icon'  => 'frmfont frm_delete_icon',
				'data'  => array(
					'frmverify'     => __( 'Do you want to move this form to the trash?', 'formidable' ),
					'frmverify-btn' => 'frm-button-red',
				),
			),
			'delete'  => array(
				'label'   => __( 'Delete Permanently', 'formidable' ),
				'short'   => __( 'Delete', 'formidable' ),
				'url'     => wp_nonce_url( $base_url . '&frm_action=destroy', 'destroy_form_' . absint( $id ) ),
				'confirm' => __( 'Are you sure you want to delete this form and all its entries?', 'formidable' ),
				'icon'    => 'frmfont frm_delete_icon',
				'data'    => array(
					'frmverify'     => __( 'This will permanently delete the form and all its entries. This is irreversible. Are you sure you want to continue?', 'formidable' ),
					'frmverify-btn' => 'frm-button-red',
				),
			),
		);
	}

	/**
	 * @since 3.0
	 *
	 * @return array
	 */
	public static function css_classes() {
		$classes = array(
			'frm_total'       => array(
				'label' => __( 'Total', 'formidable' ),
				'title' => __( 'Add this to a read-only field to display the text in bold without a border or background.', 'formidable' ),
			),
			'frm_total_big'   => array(
				'label' => __( 'Big Total', 'formidable' ),
				'title' => __( 'Add this to a read-only field to display the text in large, bold text without a border or background.', 'formidable' ),
			),
			'frm_scroll_box'  => array(
				'label' => __( 'Scroll Box', 'formidable' ),
				'title' => __( 'If you have many checkbox or radio button options, you may add this class to allow your user to easily scroll through the options. Or add a scrolling area around content in an HTML field.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
			),
			'frm_first'       => array(
				'label' => __( 'First', 'formidable' ),
				'title' => __( 'Add this to the first field in each row along with a width. ie frm_first frm4', 'formidable' ),
			),
			'frm_alignright'  => __( 'Right', 'formidable' ),
			'frm_grid_first'  => __( 'First Grid Row', 'formidable' ),
			'frm_grid'        => __( 'Even Grid Row', 'formidable' ),
			'frm_grid_odd'    => __( 'Odd Grid Row', 'formidable' ),
			'frm_color_block' => array(
				'label' => __( 'Color Block', 'formidable' ),
				'title' => __( 'Add a background color to the field or section.', 'formidable' ),
			),
			'frm_capitalize'  => array(
				'label' => __( 'Capitalize', 'formidable' ),
				'title' => __( 'Automatically capitalize the first letter in each word.', 'formidable' ),
			),
		);

		return apply_filters( 'frm_layout_classes', $classes );
	}

	/**
	 * @return array
	 */
	public static function grid_classes() {
		return array(
			'frm_half'          => '1/2',
			'frm_third'         => '1/3',
			'frm_two_thirds'    => '2/3',
			'frm_fourth'        => '1/4',
			'frm_three_fourths' => '3/4',
			'frm_sixth'         => '1/6',
			'frm10'             => '5/6',
			'frm12'             => '100%',
		);
	}

	/**
	 * @since 3.0
	 *
	 * @param mixed  $style
	 * @param string $class
	 *
	 * @return string
	 */
	public static function style_class_label( $style, $class ) {
		$label = '';

		if ( ! $style ) {
			$label = $class;
		} elseif ( ! is_array( $style ) ) {
			$label = $style;
		} elseif ( isset( $style['label'] ) ) {
			$label = $style['label'];
		}

		return $label;
	}

	/**
	 * @param string $status
	 *
	 * @return string
	 */
	public static function status_nice_name( $status ) {
		$nice_names = array(
			'draft'   => __( 'Draft', 'formidable' ),
			'trash'   => __( 'Trash', 'formidable' ),
			'publish' => __( 'Published', 'formidable' ),
		);

		if ( ! array_key_exists( $status, $nice_names ) ) {
			$status = 'publish';
		}

		return $nice_names[ $status ];
	}

	/**
	 * Renders a template icon based on the given categories.
	 *
	 * @param array $categories The categories to render the icon for.
	 * @param array $atts {
	 *     Optional. An array of attributes for rendering.
	 *
	 *     @type string  $html 'span' or 'div'. Default 'span'.
	 *     @type bool $bg   Whether to add a background color or not. Default false.
	 * }
	 *
	 * @return void
	 */
	public static function template_icon( $categories, $atts = array() ) {
		// Define defaults.
		$defaults = array(
			'bg' => true,
		);
		$atts     = array_merge( $defaults, $atts );

		// Filter out ignored categories.
		$ignore     = self::get_license_types();
		$categories = array_diff( $categories, $ignore );

		// Define icons mapping.
		$icons = array(
			'WooCommerce'             => array( 'woocommerce', 'var(--purple)' ),
			'Post'                    => array( 'wordpress', 'rgb(0,160,210)' ),
			'User Registration'       => array( 'register', 'var(--pink)' ),
			'Registration and Signup' => array( 'register', 'var(--pink)' ),
			'PayPal'                  => array( 'paypal' ),
			'Stripe'                  => array( 'credit_card', 'var(--green)' ),
			'Twilio'                  => array( 'sms' ),
			'Payment'                 => array( 'credit_card' ),
			'Order Form'              => array( 'product' ),
			'Finance'                 => array( 'total' ),
			'Health and Wellness'     => array( 'heart', 'var(--pink)' ),
			'Event Planning'          => array( 'calendar', 'var(--orange)' ),
			'Real Estate'             => array( 'house' ),
			'Nonprofit'               => array( 'heart_solid' ),
			'Calculator'              => array( 'calculator', 'var(--purple)' ),
			'Quiz'                    => array( 'percent' ),
			'Registrations'           => array( 'address_card' ),
			'Customer Service'        => array( 'users_solid' ),
			'Education'               => array( 'pencil' ),
			'Marketing'               => array( 'eye' ),
			'Feedback'                => array( 'smile' ),
			'Business Operations'     => array( 'case' ),
			'Contact Form'            => array( 'email' ),
			'Conversational Forms'    => array( 'chat_forms' ),
			'Survey'                  => array( 'chat_forms', 'var(--orange)' ),
			'Application'             => array( 'align_right' ),
			'Signature'               => array( 'signature' ),
			''                        => array( 'align_right' ),
		);

		// Determine the icon to be used.
		$icon = $icons[''];

		if ( count( $categories ) === 1 ) {
			$category = reset( $categories );
			$icon     = $icons[ $category ] ?? $icon;
		} elseif ( $categories ) {
			$icons = array_intersect_key( $icons, array_flip( $categories ) );
			$icon  = reset( $icons );
		}

		// Prepare variables for output.
		$icon_name = $icon[0];
		$bg_color  = $icon[1] ?? '';

		// Render the icon.
		echo '<span class="frm-category-icon frm-icon-wrapper"';

		if ( $bg_color && $atts['bg'] ) {
			echo ' style="background-color:' . esc_attr( $bg_color ) . '"';
		}

		echo '>';
		FrmAppHelper::icon_by_class( 'frmfont frm_' . $icon_name . '_icon' );
		echo '</span>';
	}

	/**
	 * Get template install link.
	 *
	 * @since 4.02
	 *
	 * @param array $template Template details.
	 * @param array $args Additional arguments.
	 *
	 * @return array The link attributes.
	 */
	public static function get_template_install_link( $template, $args ) {
		$defaults = array(
			'class' => 'install-now',
			'href'  => 'href',
			'atts'  => true,
		);

		if ( ! empty( $template['url'] ) ) {
			$link = array(
				'url'   => $template['url'],
				'label' => __( 'Create Form', 'formidable' ),
				'class' => 'frm-install-template',
				'href'  => 'rel',
				'atts'  => '',
			);
		} else {
			$link = array(
				'url'   => $args['pricing'],
				'label' => __( 'Upgrade', 'formidable' ),
			);
		}

		return array_merge( $defaults, $link );
	}

	/**
	 * Is the template included with the license type?
	 *
	 * @since 4.02.02
	 *
	 * @param array $args
	 *
	 * @return bool
	 */
	public static function plan_is_allowed( $args ) {
		if ( empty( $args['license_type'] ) ) {
			return false;
		}

		$plans         = array( 'free', 'personal', 'business', 'elite' );
		$license_type  = strtolower( $args['license_type'] );
		$plan_required = strtolower( $args['plan_required'] );
		$included      = $license_type === $plan_required;

		if ( $included || ! in_array( $plan_required, $plans, true ) ) {
			return $included;
		}

		foreach ( $plans as $plan ) {
			if ( $included || $plan === $license_type ) {
				break;
			}

			$included = $plan === $plan_required;
		}

		return $included;
	}

	/**
	 * If a template or add-on cannot be installed, show a message
	 * about which plan is required.
	 *
	 * @since 4.0
	 *
	 * @param string $requires
	 * @param string $link
	 *
	 * @return void
	 */
	public static function show_plan_required( $requires, $link ) {
		if ( ! $requires ) {
			return;
		}

		// phpcs:disable Generic.WhiteSpace.ScopeIndent
		?>
		<p class="frm_plan_required">
			<?php esc_html_e( 'Plan required:', 'formidable' ); ?>
			<a href="<?php echo esc_url( $link ); ?>" target="_blank" rel="noopener">
				<?php echo esc_html( $requires ); ?>
			</a>
		</p>
		<?php
		// phpcs:enable Generic.WhiteSpace.ScopeIndent
	}

	/**
	 * @since 4.0
	 *
	 * @param array $item
	 *
	 * @return false|string
	 */
	public static function get_plan_required( &$item ) {
		if ( ! isset( $item['categories'] ) || ! is_array( $item['categories'] ) || ! empty( $item['url'] ) ) {
			return false;
		}

		$plans = self::get_license_types();

		foreach ( $item['categories'] as $k => $category ) {
			if ( in_array( $category, $plans, true ) ) {
				unset( $item['categories'][ $k ] );
				return self::convert_legacy_package_names( $category );
			}
		}

		return false;
	}

	/**
	 * Converts legacy package names to the current standard package name.
	 *
	 * @since 6.15
	 *
	 * @param string $package_name
	 *
	 * @return string The updated package name.
	 */
	public static function convert_legacy_package_names( $package_name ) {
		if ( in_array( $package_name, array( 'Creator', 'Personal' ), true ) ) {
			return 'Plus';
		}

		return $package_name;
	}

	/**
	 * Get the license types.
	 *
	 * @since 6.15
	 *
	 * @param array $args
	 *
	 * @return array
	 */
	public static function get_license_types( $args = array() ) {
		$defaults      = array(
			'include_all' => true,
			'case_lower'  => false,
		);
		$args          = wp_parse_args( $args, $defaults );
		$license_types = array( 'Basic', 'Plus', 'Business', 'Elite' );

		if ( $args['include_all'] ) {
			$license_types = array_merge( array( 'free', 'Personal', 'Creator' ), $license_types );
		}

		if ( $args['case_lower'] ) {
			return array_map( 'strtolower', $license_types );
		}

		return $license_types;
	}

	/**
	 * Checks for warnings to be displayed after form settings are saved.
	 *
	 * @since 4.04
	 *
	 * @param array $values The $_POST array, which contains values submitted in a form.
	 *
	 * @return array An array of warnings or an empty array.
	 */
	public static function check_for_warnings( $values ) {
		$warnings         = array();
		$redirect_warning = self::check_redirect_url_for_unsafe_params( $values );

		if ( $redirect_warning ) {
			$warnings[] = $redirect_warning;
		}

		return apply_filters( 'frm_check_for_warnings', $warnings, $values );
	}

	/**
	 * Checks the redirect URL for params whose names are reserved words.
	 *
	 * @since 4.04
	 *
	 * @param array $values The $_POST array, which contains the values submitted in a form.
	 *
	 * @return bool|string A warning message about unsafe params or false.
	 */
	private static function check_redirect_url_for_unsafe_params( $values ) {
		if ( ! isset( $values['options'] ) ) {
			return false;
		}

		$options = $values['options'];
		FrmAppHelper::sanitize_with_html( $options );

		if ( ! isset( $options['success_action'] ) || $options['success_action'] !== 'redirect' || ! isset( $options['success_url'] ) ) {
			return false;
		}

		$unsafe_params_in_redirect = self::get_unsafe_params( $options['success_url'] );

		return self::create_unsafe_param_warning( $unsafe_params_in_redirect );
	}

	/**
	 * Returns an array of params whose names are reserved words in the specified URL.
	 *
	 * @since 4.04
	 *
	 * @param string $url The URL whose params are being checked.
	 *
	 * @return array An array of params whose names are reserved words or an empty array.
	 */
	private static function get_unsafe_params( $url ) {
		$redirect_components = parse_url( $url );

		if ( empty( $redirect_components['query'] ) ) {
			return array();
		}

		parse_str( $redirect_components['query'], $redirect_params );
		$redirect_param_names      = array_keys( $redirect_params );
		$reserved_words            = self::reserved_words();
		$unsafe_params_in_redirect = array_intersect( $redirect_param_names, $reserved_words );

		return array_values( $unsafe_params_in_redirect );
	}

	/**
	 * Returns a warning if reserved words have been used as param names in the redirect URL.
	 *
	 * @since 4.04
	 *
	 * @param array $unsafe_params_in_redirect Array of params from the redirect URL whose names are reserved words.
	 *
	 * @return bool|string A string with an unsafe param message or false.
	 */
	private static function create_unsafe_param_warning( $unsafe_params_in_redirect ) {
		$count = count( $unsafe_params_in_redirect );

		if ( $count === 0 ) {
			return false;
		}

		$caution              = esc_html__( 'Is this intentional?', 'formidable' );
		$reserved_words_intro = esc_html__( 'See the list of reserved words in WordPress.', 'formidable' );
		$reserved_words_link  = '<a href="https://codex.wordpress.org/WordPress_Query_Vars" target="_blank"> ' . $reserved_words_intro . '</a>';

		if ( $count === 1 ) {
			/* translators: %s: the name of a single parameter in the redirect URL */
			return sprintf( esc_html__( 'The redirect URL is using the parameter "%s", which is reserved by WordPress. ', 'formidable' ), $unsafe_params_in_redirect[0] ) . $caution . $reserved_words_link; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
		}

		$unsafe_params_string = implode( '", "', $unsafe_params_in_redirect );

		/* translators: %s: the names of two or more parameters in the redirect URL, separated by commas */
		return sprintf( esc_html__( 'The redirect URL is using the parameters "%s", which are reserved by WordPress. ', 'formidable' ), $unsafe_params_string ) . $caution . $reserved_words_link; // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
	}

	/**
	 * Returns an array of common reserved words in WordPress.
	 *
	 * An edited list of reserved terms from the Codex.
	 * https://codex.wordpress.org/Reserved_Terms
	 *
	 * @since 4.04
	 *
	 * @return array Array of WordPress reserved words.
	 */
	public static function reserved_words() {
		return array(
			'id',
			'attachment',
			'author',
			'author_name',
			'calendar',
			'cat',
			'category',
			'category_name',
			'cpage',
			'custom',
			'day',
			'date',
			'error',
			'feed',
			'hour',
			'm',
			'minute',
			'more',
			'name',
			'order',
			'p',
			'page',
			'page_id',
			'paged',
			'pb',
			'post',
			'posts',
			'preview',
			's',
			'search',
			'second',
			'sentence',
			'tag',
			'taxonomy',
			'tb',
			'term',
			'terms',
			'theme',
			'title',
			'type',
			'w',
			'year',
		);
	}

	/**
	 * Make sure the field shortcodes in a url always add the sanitize_url=1 option if nothing is defined.
	 * This is to prevent some field characters like ', @, and | from being stripped from the redirect URL.
	 *
	 * @since 5.0.16
	 *
	 * @param string $url
	 * @param int    $form_id
	 *
	 * @return string
	 */
	public static function maybe_add_sanitize_url_attr( $url, $form_id ) {
		if ( ! str_contains( $url, '[' ) ) {
			// Do nothing if no shortcodes are detected.
			return $url;
		}

		$parsed = wp_parse_url( $url );

		if ( empty( $parsed['query'] ) ) {
			// Do nothing if no query can be detected in the url string.
			return $url;
		}

		$original_query = $parsed['query'];
		$query          = $parsed['query'];
		$shortcodes     = FrmFieldsHelper::get_shortcodes( $query, $form_id );

		if ( empty( $shortcodes[0] ) ) {
			// No shortcodes found, do nothing.
			return $url;
		}

		foreach ( $shortcodes[0] as $key => $shortcode ) {
			$options = trim( $shortcodes[3][ $key ] );

			if ( in_array( $shortcodes[1][ $key ], array( 'if ' ), true ) ) {
				// Skip if shortcodes.
				continue;
			}

			if ( str_contains( $options, 'sanitize_url=' ) || str_contains( $options, 'sanitize=' ) ) {
				// A sanitize option is already set so leave it alone.
				continue;
			}

			$new_shortcode = '[' . $shortcodes[2][ $key ];

			if ( $options ) {
				$new_shortcode .= ' ' . $options;
			}

			$new_shortcode .= ' sanitize_url=1]';
			$query          = str_replace( $shortcode, $new_shortcode, $query );
		}//end foreach

		if ( $query === $original_query ) {
			return $url;
		}

		return str_replace( $original_query, $query, $url );
	}

	/**
	 * Outputs the appropriate button text in the publish box.
	 *
	 * @return void
	 */
	public static function publish_box_button_text() {
		$is_new_template = FrmAppHelper::simple_get( 'new_template' );
		$action          = FrmAppHelper::simple_get( 'frm_action' );

		if ( ( 'edit' === $action || 'settings' === $action ) && $is_new_template ) {
			esc_html_e( 'Save', 'formidable' );
		} else {
			esc_html_e( 'Update', 'formidable' );
		}
	}

	/**
	 * Strip characters similar to the WordPress sanitize_html_class function, but allow for [ and ].
	 * This allows shortcodes inside of the layout classes setting.
	 *
	 * @since 6.16
	 *
	 * @param string $classname
	 *
	 * @return string
	 */
	public static function sanitize_layout_class( $classname ) {
		// Strip out any percent-encoded characters.
		$sanitized = preg_replace( '|%[a-fA-F0-9][a-fA-F0-9]|', '', $classname );

		// Limit to A-Z, a-z, 0-9, '_', '-', '[', ']'.
		return preg_replace( '/[^A-Za-z0-9_\-\[\]]/', '', $sanitized );
	}

	/**
	 * Returns true if the preview should be blocked.
	 *
	 * @since 6.20
	 *
	 * @param string $form_key
	 *
	 * @return bool
	 */
	public static function should_block_preview( $form_key ) {
		$should_block = in_array( $form_key, array( 'contact-form', 'contact-us' ), true ) && ! current_user_can( 'frm_view_forms' );
		/**
		 * Filters whether the form preview should be blocked.
		 *
		 * @since 6.20
		 *
		 * @param bool   $should_block
		 * @param string $form_key
		 */
		return (bool) apply_filters( 'frm_block_preview', $should_block, $form_key );
	}

	/**
	 * Checks if the form is loaded by API.
	 *
	 * @since 6.21
	 *
	 * @return bool
	 */
	public static function form_is_loaded_by_api() {
		global $frm_vars;

		if ( ! empty( $frm_vars['inplace_edit'] ) ) {
			return true;
		}

		return self::is_formidable_api_form() || self::is_block_or_page_builder_preview();
	}

	/**
	 * Checks if the form is rendered inside a block editor or page builder preview.
	 *
	 * @since 6.29
	 *
	 * @return bool
	 */
	public static function is_block_or_page_builder_preview() {
		return self::is_gutenberg_editor() || self::is_elementor_ajax() || self::is_visual_views_preview();
	}

	/**
	 * @since 6.21
	 *
	 * @return bool
	 */
	private static function is_visual_views_preview() {
		return 'frm_views_process_box_preview' === FrmAppHelper::get_post_param( 'action' );
	}

	/**
	 * @since 6.21
	 *
	 * @return bool
	 */
	private static function is_elementor_ajax() {
		return 'elementor_ajax' === FrmAppHelper::get_post_param( 'action' );
	}

	/**
	 * @since 6.21
	 *
	 * @return bool
	 */
	private static function is_gutenberg_editor() {
		$url = FrmAppHelper::get_server_value( 'REQUEST_URI' );

		if ( str_contains( $url, '/wp-json/wp/v2/block-renderer/formidable/simple-form' ) ) {
			return true;
		}

		if ( str_contains( urldecode( $url ), 'rest_route=/wp/v2/block-renderer/formidable/' ) ) {
			return true;
		}

		global $pagenow;
		return 'post.php' === $pagenow;
	}

	/**
	 * @since 6.21
	 *
	 * @return bool
	 */
	private static function is_formidable_api_form() {
		if ( ! class_exists( 'FrmAPIAppController' ) ) {
			return false;
		}

		$url = FrmAppHelper::get_server_value( 'REQUEST_URI' );

		if ( str_contains( $url, '/wp-json/frm/v2/forms/' ) ) {
			// Prevent the honeypot from appearing for an API loaded form.
			// This is to prevent conflicts where the script is not working.
			return true;
		}

		if ( is_callable( 'FrmProFormState::get_from_request' ) ) {
			$api = FrmProFormState::get_from_request( 'a', 0 );

			if ( $api ) {
				return true;
			}
		}

		return false;
	}
}
