<?php

	// Exit if accessed directly
	if ( ! defined( 'ABSPATH' ) ) {
		exit;
	}

	#[AllowDynamicProperties]
	class WS_Form_Public {

		// The ID of this plugin.
		private $plugin_name;

		// The version of this plugin.
		private $version;

		// Customizer
		private $customizer;

		// Conversational
		private $conversational = false;

		// CSS inline
		private $css_inline;

		// Form index (Incremented for each rendered with a shortcode)
		public $form_instance = 1;

		// Error index
		public $error_index = 0;

		// Debug
		public $debug = false;

		// Styler
		public $styler = false;

		// JSON
		public $wsf_form_json = array();

		// Footer JS
		public $footer_js = '';

		// Style IDs to render
		public $style_ids = array();

		// Deregister scripts
		private $deregister_scripts = array();

		// Is ACF activated?
		public $acf_activated;

		// CSS
		public $ws_form_css;

		// Style
		public $ws_form_style;

		// Default style ID
		public $style_id_default = 0;

		// Enqueuing - CSS
		public $enqueue_css_layout = false;
		public $enqueue_css_skin = false;
		public $enqueue_css_debug = false;
		public $enqueue_css_styler = false;
		public $enqueue_css_style = false;
		// Enqueuing - CSS - V2
		public $enqueue_css_base = false;
		public $enqueue_css_button = false;
		public $enqueue_css_checkbox = false;
		public $enqueue_css_color = false;
		public $enqueue_css_number = false;
		public $enqueue_css_radio = false;
		public $enqueue_css_select = false;
		public $enqueue_css_tel = false;
		public $enqueue_css_tab = false;
		public $enqueue_css_textarea = false;
		public $enqueue_css_custom = false;

		// Enqueuing - JS
		public $enqueue_js_common = false;
		public $enqueue_js_public = false;
		public $enqueue_js_styler = false;
		public $enqueue_js_styler_scheme = false;

		public $enqueue_js_captcha = false;
		public $enqueue_js_checkbox = false;
		public $enqueue_js_color = false;
		public $enqueue_js_color_picker = false;
		public $enqueue_js_input_mask = false;
		public $enqueue_js_intl_tel_input = false;
		public $enqueue_js_radio = false;
		public $enqueue_js_select = false;
		public $enqueue_js_sortable = false;
		public $enqueue_js_tab = false;
		public $enqueue_js_tel = false;
		public $enqueue_js_custom = false;

		// Enqueued - CSS
		public $enqueued_css_layout = false;
		public $enqueued_css_skin = false;
		public $enqueued_css_style = array();
		public $enqueued_css_custom = false;

		// Enqueued - JS
		public $enqueued_js_custom = false;
		public $enqueued_js_google_address = false;
		public $enqueued_js_google_map = false;
		public $enqueued_js_google_route = false;

		public $enqueued_all = false;
		public $enqueued_visual_builder = false;
		public $enqueued_core = false;

		// Public dependencies
		public $public_dependencies_js;
		public $public_dependencies_css;

		// Config filtering
		public $field_types = array();

		// Initialize the class and set its properties.
		public function __construct() {

			$this->plugin_name = WS_FORM_NAME;
			$this->version = WS_FORM_VERSION;
			$this->customizer = (WS_Form_Common::get_query_var('customize_theme') !== '');
			$this->css_inline = (WS_Form_Common::option_get('css_inline'));
			$this->acf_activated = class_exists('ACF');
			$this->ws_form_css = new WS_Form_CSS();
			$this->ws_form_css->init();

			if(WS_Form_Common::styler_enabled()) {

				$this->ws_form_style = new WS_Form_Style();
			}

			add_action('wsf_enqueue_all', array($this, 'enqueue_all'), 10, 0);
			add_action('wsf_enqueue_visual_builder', array($this, 'enqueue_visual_builder'), 10, 0);
			add_action('wsf_enqueue_core', array($this, 'enqueue_core'), 10, 0);

			// Dynamic enqueuing
			if(!WS_Form_Common::option_get('enqueue_dynamic', true)) {

				add_action('wp_enqueue_scripts', function() {

					// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
					do_action('wsf_enqueue_all');

				}, 10, 0);
			}
		}

		public function enqueue_core() {

			if(WS_Form_Common::styler_enabled()) {

				// Load all styles
				$this->style_ids = $this->ws_form_style->get_style_ids();
			}

			// JavaScript

			// Core
			add_filter('wsf_enqueue_js_common', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_public', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_custom', function($enqueue) { return true; }, 99999, 1);

			if(WS_Form_Common::styler_enabled()) {

				// Disable styler
				add_filter('wsf_enqueue_js_styler', function($enqueue) { return false; }, 99999, 1);
			}

			// CSS

			// Core
			add_filter('wsf_enqueue_css_skin', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_style', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_layout', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_custom', function($enqueue) { return true; }, 99999, 1);

			// Process enqueues
			self::enqueue();

			$this->enqueued_core = true;
		}

		public function enqueue_visual_builder() {

			if(WS_Form_Common::styler_enabled()) {

				// Load all styles
				$this->style_ids = $this->ws_form_style->get_style_ids();
			}

			// JavaScript

			// Core
			add_filter('wsf_enqueue_js_common', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_public', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_sortable', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_select2', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_input_mask', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_custom', function($enqueue) { return true; }, 99999, 1);

			// Field types
			add_filter('wsf_enqueue_js_captcha', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_checkbox', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_select', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_radio', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_tab', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_tel', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_intl_tel_input', function($enqueue) { return true; }, 99999, 1);
			// CSS

			// Core
			add_filter('wsf_enqueue_css_skin', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_style', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_layout', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_custom', function($enqueue) { return true; }, 99999, 1);

			// Field types
			add_filter('wsf_enqueue_css_base', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_button', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_checkbox', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_color', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_number', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_radio', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_select', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_tab', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_tel', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_textarea', function($enqueue) { return true; }, 99999, 1);
			// Intentionally disabled
			add_filter('wsf_enqueue_js_debug', function($enqueue) { return false; }, 99999, 1);
			add_filter('wsf_enqueue_js_wp_media', function($enqueue) { return false; }, 99999, 1);

			if(WS_Form_Common::styler_enabled()) {

				add_filter('wsf_enqueue_js_styler', function($enqueue) { return false; }, 99999, 1);
			}

			// Process enqueues
			self::enqueue();

			$this->enqueued_visual_builder = true;
		}

		public function enqueue_all() {

			// JavaScript

			// Core
			add_filter('wsf_enqueue_js_common', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_public', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_sortable', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_select2', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_input_mask', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_custom', function($enqueue) { return true; }, 99999, 1);

			// Field types
			add_filter('wsf_enqueue_js_captcha', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_checkbox', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_select', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_radio', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_tab', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_tel', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_js_intl_tel_input', function($enqueue) { return true; }, 99999, 1);
			// CSS

			// Core
			add_filter('wsf_enqueue_css_skin', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_style', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_layout', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_loader', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_custom', function($enqueue) { return true; }, 99999, 1);

			// Field types
			add_filter('wsf_enqueue_css_base', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_button', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_checkbox', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_color', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_number', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_radio', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_select', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_tab', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_tel', function($enqueue) { return true; }, 99999, 1);
			add_filter('wsf_enqueue_css_textarea', function($enqueue) { return true; }, 99999, 1);
			// Process enqueues
			self::enqueue();

			$this->enqueued_all = true;
		}

		public function init() {

			// Preview engine
			new WS_Form_Preview();

		}

		public function wp() {

			// Get post
			global $post;
			$GLOBALS['ws_form_post_root'] = isset($post) ? $post : null;
		}

		// Shortcode: ws_form
		public function shortcode_ws_form($atts, $content = null) {

			// Form ID
			$form_id = isset($atts['id']) ? absint($atts['id']) : false;

			// Element
			$element = isset($atts['element']) ? $atts['element'] : 'form';

			// Element ID
			$element_id = isset($atts['element_id']) ? $atts['element_id'] : false;

			// Class
			$class = isset($atts['class']) ? $atts['class'] : false;

			// Published?
			$published = isset($atts['published']) ? ($atts['published'] == 'true') : true;

			// Preview?
			$preview = isset($atts['preview']) ? ($atts['preview'] == 'true') : false;

			// Field populate array (Used to populate fields from field_<id> attributes)
			$field_populate_array = array();


			// Form HTML?
			$form_html = isset($atts['form_html']) ? ($atts['form_html'] == 'true') : true;

			// Visual builder?
			$visual_builder = isset($atts['visual_builder']) ? ($atts['visual_builder'] == 'true') : false;

			// Query string overrides
			if(WS_Form_Common::get_query_var('wsf_published') === 'false') { $published = false; }

			// Check for preview mode
			if($preview) {

				// Reset form instance (This is required to ensure wp_head calls resulting in do_shortcode('ws-form') don't stack up on each other)
				if(isset($this->wsf_form_json[$form_id])) { unset($this->wsf_form_json[$form_id]); }
				$this->form_instance = 1;
			}

			// Template ID
			$form_object = false;
			$template_id = isset($atts['template_id']) ? $atts['template_id'] : false;
			if($template_id !== false) {
				
				$ws_form_template = new WS_Form_Template();
				$ws_form_template->id = $template_id;

				try {
					
					$ws_form_template->read();
					$form_object = $ws_form_template->object;

					// Change meta data
					$form_object->meta->class_form_wrapper = 'wsf-demo';
					$form_object->meta->label_render = '';
					$form_object->meta->form_action = '#';

					$published = false;

				} catch (Exception $e) {}
			}

			// Visual builder
			if($visual_builder) {

				$published = false;
			}

			if(
				(($form_id > 0) && ($form_object === false)) ||
				(($form_id === false) && ($form_object !== false))
			) {

				$ws_form_form = new WS_Form_Form();

				if($form_id > 0) {

					// Embed form data (Avoids an API call)
					$ws_form_form->id = $form_id;

					try {

						if($published) {

							$form_object = $ws_form_form->db_read_published(true);

						} else {

							$form_object = $ws_form_form->db_read(true, true, false, true, false, $preview);
						}

					} catch(Exception $e) { return $e->getMessage(); }
				}

				if($form_object !== false) {

					// Process field_values attribute
					if(isset($atts['field_values'])) {

						// Attempt to parse field values as query string
						parse_str($atts['field_values'], $field_values_array);

						// If array returned then add to field populate array
						if(is_array($field_values_array)) {

							$field_populate_array = self::field_populate_array_from_atts($field_populate_array, $field_values_array);
						}
					}

					// Process field_<id> attributes
					$field_populate_array = self::field_populate_array_from_atts($field_populate_array, $atts);

					// Filter
					if(!WS_Form_Common::styler_preview_template_shown()) {

						// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
						$form_object = apply_filters('wsf_pre_render_' . $form_id, $form_object, $preview);
						// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
						$form_object = apply_filters('wsf_pre_render', $form_object, $preview);
					}

					// Pre-process form data
					self::form_pre_process($form_object, $field_populate_array);

					// Change form so it is public ready
					$ws_form_form->form_public($form_object);
				}

				// Get form HTML
				$return_value = ($form_html ? self::form_html($this->form_instance++, $form_object, $element, $published, $preview, $element_id, $visual_builder, $class) : '');

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$return_value = apply_filters('wsf_shortcode', $return_value, $atts, $content);

				return $return_value;

			} else {

				// Error
				return __('Invalid form ID', 'ws-form');
			}
		}

		public function field_populate_array_from_atts($field_populate_array, $atts) {

			if(
				!is_array($atts) ||
				(count($atts) == 0)
			) {
				return $field_populate_array;
			}

			// Shortcode field population
			foreach($atts as $att_key => $att_value) {

				if(
					(strpos($att_key, 'field_') === 0) &&
					(strlen($att_key) > 6)
				) {
					$field_id = absint(substr($att_key, 6));

					if($field_id > 0) {

						// Add sanitized value to field populate array
						$field_populate_array[$field_id] = sanitize_text_field($att_value);
					}
				}
			}

			return $field_populate_array;
		}

		// Footer scripts
		// Note that we can't use wp_add_inline_script for this because third party plugins, 
		// e.g. Elementor might enqueue scripts after wp_footer outputs those scripts.
		// We simulate it instead and pass it through the script_loader_tag filter hook
		public function wp_footer() {

			// Skip if no forms
			if(empty($this->wsf_form_json)) {

				return;
			}

			// Check field types
			if(empty($this->field_types)) {

				$this->field_types = array();
			}

			// Reset field types if visual builder enqueued
			if($this->enqueued_visual_builder) {

				$this->field_types = array();
			}

			// Filter field types
			if(count($this->field_types) > 0) {

				$this->field_types = array_unique($this->field_types);
			}

			// Build config data
			$json_config = wp_json_encode(WS_Form_Config::get_config(false, $this->field_types));

			// Build data
			$data = "window.wsf_form_json_config = {$json_config};\nwindow.wsf_form_json = [];\nwindow.wsf_form_json_populate = [];\n{$this->footer_js}";

			// Build script handle
			$handle = sprintf('%s-footer', $this->plugin_name);

			// Build script ID
			$script_id = sprintf(

				'%s-inline-js',
				$handle
			);

			// Base script attributes
			$attributes = array(

				'id'   => $script_id,
				'type' => 'text/javascript',
			);

			/**
			 * Apply 'wp_script_attributes' filter for compatibility
			 * (this allows CSP nonces or other attributes to be injected)
			 */
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$attributes = apply_filters('wp_script_attributes', $attributes);

			/**
			 * Apply 'wsf_footer_script_attributes' filter for compatibility
			 * (this allows CSP nonces or other attributes to be injected)
			 */
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$attributes = apply_filters('wsf_footer_script_attributes', $attributes);

			// Build attribute string
			$attribute_string = '';
			foreach ($attributes as $key => $value) {

				$attribute_string .= sprintf(' %s="%s"', esc_attr($key), esc_attr($value));
			}

			// Build script tag
			$tag = sprintf(

				"<script%s>\n/* <![CDATA[ */\n%s/* ]]> */\n</script>\n",
				$attribute_string,
				$data
			);

			/**
			 * Apply 'script_loader_tag' filter for compatibility
			 * (this allows CSP nonces or other attributes to be injected)
			 */
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- PCP error, WordPress hook
			$tag = apply_filters('script_loader_tag', $tag, $handle, '');

			echo "\n". $tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Like WordPress core we have to assume script_loader_tag return is safe
		}

		// Footer scripts - Pre-Process
		public function form_pre_process(&$form_object, $field_populate_array = array()) {

			// If REST request, abandon this
			if(WS_Form_Common::is_rest_request()) { return; }

			// Enqueue WS Form
			$this->enqueue_js_common = true;
			$this->enqueue_js_public = true;
			$this->enqueue_js_custom = true;

			$this->enqueue_css_base = true;
			$this->enqueue_css_layout = true;
			$this->enqueue_css_skin = true;
			$this->enqueue_css_style = true;
			$this->enqueue_css_custom = true;

			// Enqueue styler
			$this->styler_visible_public = WS_Form_Common::styler_visible_public();
			if($this->styler_visible_public) {

				// Styler
				$this->enqueue_js_styler = true;
				$this->enqueue_css_styler = true;

				// Coloris
				$this->enqueue_js_color_picker = true;

				// Color
				$this->enqueue_js_color = true;
			}

			if(WS_Form_Common::styler_enabled()) {

				// Get form style ID
				$style_id = $this->ws_form_style->get_style_id_from_form_object($form_object, $this->conversational);

				// Add style to be rendered
				if(!in_array($style_id, $this->style_ids)) {

					$this->style_ids[] = $style_id;
				}
			}

			// Apply restrictions
			$ws_form_form = new WS_Form_Form();
			$ws_form_form->apply_restrictions($form_object);

			// Field types
			$field_types = WS_Form_Config::get_field_types_flat();

			// Determine enqueues
			$groups = isset($form_object->groups) ? $form_object->groups : array();

			// Enqueue tabs
			if(count($groups) > 1) {

				$this->enqueue_js_tab = true;
				$this->enqueue_css_tab = true;
			}

			foreach($groups as $group_key => $group) {

				$sections = isset($group->sections) ? $group->sections : array();

				foreach($sections as $section_key => $section) {

					// Enqueue section repeatable
					if(WS_Form_Common::get_object_meta_value($section, 'section_repeatable') === 'on') {

						$this->enqueue_js_section_repeatable = true;
					}

					$fields = isset($section->fields) ? $section->fields : array();

					// Process fields
					foreach($fields as $field_key => $field) {

						// Get field ID
						if(!isset($field->id)) { continue; }
						$field_id = $field->id;

						// Get field type
						if(!isset($field->type)) { continue; }
						$field_type = $field->type;

						// Check field type
						if(!isset($field_types[$field_type])) { continue; }

						// Check for att population
						if(isset($field_populate_array[$field_id])) {

							$field->meta->default_value = $field_populate_array[$field_id];
						}

						// Add field type to array (This is used later on to filter the field configs rendered on the page)
						$this->field_types[] = $field_type;

						// Check to see if an input_mask is set
						if(!$this->enqueue_js_input_mask) {

							$input_mask = WS_Form_Common::get_object_meta_value($field, 'input_mask', '');
							if($input_mask !== '') { $this->enqueue_js_input_mask = true; }

						}

						// Check by field type
						switch($field_type) {

							// Number
							case 'number' :

								$this->enqueue_css_number = true;
								break;

							// Select, checkbox and radios
							case 'select' :
							case 'checkbox' :
							case 'radio' :
								$data_grid_clear = false;

								$cascade = (WS_Form_Common::get_object_meta_value($field, sprintf('%s_cascade', $field_type), '') === 'on');

								// Cascade
								if($cascade) {

									$cascade_ajax = (WS_Form_Common::get_object_meta_value($field, sprintf('%s_cascade_ajax', $field_type), '') === 'on');

									if($cascade_ajax) {

										$data_grid_clear = true;
									}

									// Enqueue cascade
									$this->enqueue_js_cascade = true;
								}

								switch($field_type) {

									// Select
									case 'select' :
										$this->enqueue_js_select = true;
										$this->enqueue_css_select = true;

										// E-commerce
										if($field_type === 'price_select') {

											$this->enqueue_js_ecommerce = true;
										}

										// Select2 AJAX
										if(WS_Form_Common::get_object_meta_value($field, 'select2', '') === 'on') {

											if(!$cascade && WS_Form_Common::get_object_meta_value($field, 'select2_ajax', '')) {

												$data_grid_clear = true;
											}

											// Enqueue JS
											$this->enqueue_js_select2 = true;
										}

										break;

									// Checkbox
									case 'checkbox' :
										// E-commerce
										if($field_type === 'price_checkbox') {

											$this->enqueue_js_ecommerce = true;
										}

										if(
											WS_Form_Common::get_object_meta_value($field, 'select_all', '') ||
											WS_Form_Common::get_object_meta_value($field, 'checkbox_min', '') ||
											WS_Form_Common::get_object_meta_value($field, 'checkbox_max', '')
										) {

											$this->enqueue_js_checkbox = true;
										}

										// Enqueue CSS
										$this->enqueue_css_checkbox = true;

										break;

									// Radio
									case 'radio' :
										$this->enqueue_js_radio = true;
										$this->enqueue_css_radio = true;

										if($field_type === 'price_radio') {

											$this->enqueue_js_ecommerce = true;
										}

										break;
								}

								if($data_grid_clear) {

									// Get data grid meta key
									$field_type = $field->type;

									switch($field_type) {

										case 'select' :

											$data_grid_meta_key = 'data_grid_select';
											break;

										case 'checkbox' :

											$data_grid_meta_key = 'data_grid_checkbox';
											break;

										case 'radio' :

											$data_grid_meta_key = 'data_grid_radio';
											break;
									}

									// Clear group data
									if(
										isset($field->meta->{$data_grid_meta_key}->groups) &&
										is_array($field->meta->{$data_grid_meta_key}->groups) &&
										isset($field->meta->{$data_grid_meta_key}->groups[0])
									) {
										if(isset($field->meta->{$data_grid_meta_key}->groups[0]->rows)) {

											$field->meta->{$data_grid_meta_key}->groups[0]->rows = array();
										}

										$group_index = 1;

										while(isset($field->meta->{$data_grid_meta_key}->groups[$group_index])) {

											unset($field->meta->{$data_grid_meta_key}->groups[$group_index++]);
										}
									}
								}

								break;

							// Buttons
							case 'submit' :
							case 'reset' :
							case 'tab_previous' :
							case 'tab_next' :
								$this->enqueue_css_button = true;
								break;

							// Telephone
							case 'tel' :

								// International telephone input
								if(WS_Form_Common::get_object_meta_value($field, 'intl_tel_input', '')) {

									$this->enqueue_js_intl_tel_input = true;
									$this->enqueue_js_tel = true;
									$this->enqueue_css_tel = true;

									if(WS_Form_Common::get_object_meta_value($field, 'intl_tel_input_initial_country', '')) {

										$this->enqueue_js_geo = true;
									}
								}

								break;

							// Text Area
							case 'textarea' :
								$this->enqueue_css_textarea = true;

								break;

							// Captcha
							case 'recaptcha' :
							case 'hcaptcha' :
							case 'turnstile' :
							case 'captchafox' :

								$this->enqueue_js_captcha = true;
								break;
						}

						// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
						do_action('wsf_form_pre_process_field', $field, $this);

						$field = null;
					}
				}
			}

			self::enqueue();
		}

		// Enqueue
		public function enqueue() {

			$this->public_dependencies_js = array($this->plugin_name . '-common');
			$this->public_dependencies_css = array($this->plugin_name . '-base');

			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			if(apply_filters('wsf_public_enqueue', true)) {

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				if(apply_filters('wsf_public_enqueue_external', true)) {

					self::enqueue_external();
				}

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				if(apply_filters('wsf_public_enqueue_internal', true)) {

					self::enqueue_internal();
				}
			}

			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			do_action('wsf_enqueue');
		}

		// Enqueue - External
		public function enqueue_external() {

			// Get enqueue args
			$enqueue_args = WS_Form_Common::get_enqueue_args();

			// External scripts
			$external = WS_Form_Config::get_external();

			// Base dependencies
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$dependencies_base = apply_filters('wsf_enqueue_js_dependencies', array('jquery'));

			// JS - Input Mask - 5.0.3
			if(
				!wp_script_is($this->plugin_name . '-external-inputmask', 'enqueued') &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_js_input_mask', $this->enqueue_js_input_mask)
			) {
				// External - Input Mask Bundle
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$dependencies = apply_filters('wsf_enqueue_js_input_mask_dependencies', $dependencies_base);
				wp_enqueue_script($this->plugin_name . '-external-inputmask', $external['inputmask_js']['path'], $dependencies, $external['inputmask_js']['version'], $enqueue_args);
			}

			// JS - International telephone input - Version 17.0.13
			if(
				!wp_script_is($this->plugin_name . '-external-intl-tel-input', 'enqueued') &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_js_intl_tel_input', $this->enqueue_js_intl_tel_input)
			) {
				// External - International telephone input - JS
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$dependencies = apply_filters('wsf_enqueue_js_intl_tel_input_dependencies', $dependencies_base);
				wp_enqueue_script($this->plugin_name . '-external-intl-tel-input', $external['intl_tel_input_js']['path'], $dependencies, $external['intl_tel_input_js']['version'], $enqueue_args);

				// External - International telephone input - CSS
				wp_enqueue_style($this->plugin_name . '-external-intl-tel-input', $external['intl_tel_input_css']['path'], array(), $external['intl_tel_input_css']['version'], 'all');
			}

			// JS - Color picker - Version 0.24.0
			if(
				!wp_script_is($this->plugin_name . '-external-color-picker', 'enqueued') &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_js_color_picker', $this->enqueue_js_color_picker)
			) {
				// External - Color picker - JS
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$dependencies = apply_filters('wsf_enqueue_js_color_picker_dependencies', $dependencies_base);
				wp_enqueue_script($this->plugin_name . '-external-color-picker', $external['coloris_js']['path'], $dependencies, $external['coloris_js']['version'], $enqueue_args);

				// External - Color picker - CSS
				wp_enqueue_style($this->plugin_name . '-external-color-picker', $external['coloris_css']['path'], array(), $external['coloris_css']['version'], 'all');
			}
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			do_action('wsf_enqueue_external');
		}

		// Enqueue - External - Google Maps JS API
		public function enqueue_external_google_maps_js_api() {

			// Only enqueue this once
			if(
				$this->enqueued_js_google_address ||
				$this->enqueued_js_google_map ||
				$this->enqueued_js_google_route
			) {
				return true;
			}

			// Get API key
			$api_key_google_map = trim(WS_Form_Common::option_get('api_key_google_map'));
			if(empty($api_key_google_map)) { return true; }

			// Build script handle
			$handle = sprintf('%s-external-google-maps-js-api', $this->plugin_name);

			// Get version of Places API to use
			switch(WS_Form_Common::option_get('google_maps_js_api_version')) {

				case '2' :

					wp_register_script(

						$handle,
						'',       // Dummy
						array(),
						WS_FORM_VERSION,
						array(
								// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
							'in_footer' => apply_filters('wsf_enqueue_script_in_footer', (WS_Form_Common::option_get('jquery_footer', '') == 'on'))
						)
					);

					wp_enqueue_script($handle);

					wp_add_inline_script(

						$handle,
						"(g=>{var h,a,k,p='The Google Maps JavaScript API',c='google',l='importLibrary',q='__ib__',m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement('script'));e.set('libraries',[...r]+'');for(k in g)e.set(k.replace(/[A-Z]/g,t=>'_'+t[0].toLowerCase()),g[k]);e.set('callback',c+'.maps.'+q);a.src=`https://maps.`+c+`apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+' could not load.'));a.nonce=m.querySelector('script[nonce]')?.nonce||'';m.head.append(a)}));d[l]?console.warn(p+' only loads once. Ignoring:',g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({key: '" . esc_js($api_key_google_map) . "', v: 'weekly'});"
					);

					break;

				// Places API (Legacy)
				default :

					// Get API key
					$api_key_google_map = trim(WS_Form_Common::option_get('api_key_google_map'));
					if(empty($api_key_google_map)) { return true; }

					// Base URL
					$url = 'https://maps.googleapis.com/maps/api/js';

					// Query string parameters
					$params = array(

						'key' => $api_key_google_map,
						'libraries' => 'places,marker',
						'v' => 'weekly',
						'loading' => 'async'
					);
					$path = WS_Form_Common::wsf_add_query_args($params, $url);

					// Enqueue script
					wp_enqueue_script(

						$handle,
						$path,
						array(),
						WS_FORM_VERSION,
						WS_Form_Common::get_enqueue_args()
					);
			}

			return true;
		}

		// Enqueue - Internal
		public function enqueue_internal() {

			// Minified scripts?
			$min = SCRIPT_DEBUG ? '' : '.min';

			// RTL?
			$rtl = is_rtl() ? '.rtl' : '';

			// Get enqueue args
			$enqueue_args = WS_Form_Common::get_enqueue_args();

			// Base dependencies
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$dependencies_base = apply_filters('wsf_enqueue_js_dependencies', array('jquery'));

			// Get uploads base directory and ensure paths are https (Known bug with wp_upload_dir)
			$upload_dir_base_url = WS_Form_Common::get_upload_dir_base_url();

			// JS - Common
			if(
				!wp_script_is($this->plugin_name . '-common', 'enqueued') &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_js_common', $this->enqueue_js_common)
			) {
				// Enqueued scripts settings
				$ws_form_settings = self::localization_object();

				// WS Form script - Common
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$dependencies = apply_filters('wsf_enqueue_js_common_dependencies', $dependencies_base);
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$dependencies = apply_filters('wsf_enqueue_js_form_common_dependencies', $dependencies_base);	// Legacy

				wp_register_script(

					$this->plugin_name . '-common',
					sprintf('%sshared/js/ws-form%s.js', WS_FORM_PLUGIN_DIR_URL, $min), $dependencies,
					$this->version,
					$enqueue_args
				);

				wp_localize_script(

					$this->plugin_name . '-common',
					'ws_form_settings',
					$ws_form_settings
				);

				wp_enqueue_script(

					$this->plugin_name . '-common'
				);
			}

			// Base
			self::enqueue_internal_css('base', false);

			// Button
			self::enqueue_internal_css('button');

			// Captcha
			self::enqueue_internal_js('captcha');

			// Checkbox
			self::enqueue_internal_js('checkbox');
			self::enqueue_internal_css('checkbox');

			// Color
			self::enqueue_internal_js('color');
			self::enqueue_internal_css('color');

			// Number
			self::enqueue_internal_css('number');

			// Radio
			self::enqueue_internal_js('radio');
			self::enqueue_internal_css('radio');

			// Select
			self::enqueue_internal_js('select');
			self::enqueue_internal_css('select');

			// Tab
			self::enqueue_internal_js('tab');
			self::enqueue_internal_css('tab');

			// Tab
			self::enqueue_internal_js('tel');
			self::enqueue_internal_css('tel');

			// Tab
			self::enqueue_internal_js('textarea');
			self::enqueue_internal_css('textarea');
			// Public
			self::enqueue_internal_js('public', true);
			// Styler
			self::enqueue_internal_js('styler', 'public');
			self::enqueue_internal_css('styler', false, false, true);

			// CSS - Layout
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			if(apply_filters('wsf_enqueue_css_layout', $this->enqueue_css_layout)) {

				if(WS_Form_Common::is_block_editor()) {

					// If we are in the block editor, we enqueue the layout CSS using WS Form framework
					wp_enqueue_style($this->plugin_name . '-layout', WS_Form_Common::get_api_path('helper/ws-form-css?wsf_block_editor=true'), array(), $this->version, 'all');

				} else {

					// Regular enqueue
					if(
						(
							WS_Form_Common::option_get('css_layout', true) &&
							(WS_Form_Common::option_get('framework', 'ws-form') == 'ws-form')
						) ||
						$this->conversational
					) {

						if($this->customizer || ($this->css_inline && !is_admin())) {

							if(!$this->enqueued_css_layout) {

								add_action('wp_footer', function() {

									// Output public CSS
									$css = $this->ws_form_css->get_layout(null, $this->customizer, is_rtl());
									WS_Form_Common::echo_esc_css_inline($css);

								}, 100);
							}

						} else {

							$css_compile = WS_Form_Common::option_get('css_compile', false);

							wp_enqueue_style($this->plugin_name . '-layout', $css_compile ? sprintf('%s/ws-form/%s/public.layout%s%s.css', $upload_dir_base_url, WS_FORM_CSS_FILE_PATH, $rtl, $min) : WS_Form_Common::get_api_path('helper/ws-form-css'), array(), $this->version, 'all');
						}
					}
				}
	
				$this->enqueued_css_layout = true;
			}

			// CSS - Skin
			if(
				WS_Form_Common::customizer_enabled() &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_css_skin', $this->enqueue_css_skin)
			) {
				if(WS_Form_Common::is_block_editor()) {

					// If we are in the block editor, we enqueue the skin CSS using WS Form framework
					wp_enqueue_style($this->plugin_name . '-skin', WS_Form_Common::get_api_path('helper/ws-form-css-skin?wsf_block_editor=true'), array(), $this->version, 'all');

				} else {

					if(
						(
							WS_Form_Common::option_get('css_skin', true) &&
							(WS_Form_Common::option_get('framework', 'ws-form') == 'ws-form')
						) ||
						$this->conversational
					) {

						if($this->customizer || ($this->css_inline && !is_admin())) {

							if(!$this->enqueued_css_skin) {

								add_action('wp_footer', function() {

									// Output skin CSS
									$css_skin = $this->ws_form_css->get_skin(null, $this->customizer, is_rtl());
									WS_Form_Common::echo_esc_css_inline($css_skin);

								}, 100);
							}

						} else {

							$css_compile = WS_Form_Common::option_get('css_compile', false);

							$this->ws_form_css->skin_load();

							wp_enqueue_style($this->plugin_name . '-skin', $css_compile ? sprintf('%s/ws-form/%s/public.skin%s%s%s.css', $upload_dir_base_url, WS_FORM_CSS_FILE_PATH, $this->ws_form_css->skin_file, $rtl, $min) : WS_Form_Common::get_api_path(sprintf('helper/ws-form-css-skin?wsf_skin_id=%s', $this->ws_form_css->skin_id)), array(), $this->version, 'all');
						}
					}
				}

				$this->enqueued_css_skin = true;
			}

			// CSS - Style
			if(
				WS_Form_Common::styler_enabled() &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				apply_filters('wsf_enqueue_css_style', $this->enqueue_css_style)
			) {
				foreach($this->style_ids as $style_id) {

					$enqueue_id = sprintf(

						'%s-style-%u',
						esc_attr($this->plugin_name),
						esc_attr($style_id)
					);

					if(WS_Form_Common::is_block_editor()) {

						// If we are in the block editor, we enqueue the style CSS using WS Form framework
						wp_enqueue_style($enqueue_id, WS_Form_Common::get_api_path(sprintf(

							'style/%u/css/?wsf_block_editor=true',
							$style_id

						)), array(), $this->version, 'all');

					} elseif($this->enqueue_js_styler) {

						// If we are using the styler, ensure the alt classes are force to render
						if(!isset($this->enqueued_css_style[$style_id])) {

							add_action('wp_footer', function() use ($style_id) {

								// Output style CSS
								$this->ws_form_style->id = $style_id;
								$css_style = $this->ws_form_style->get_css_vars_markup(true, true, false, true, true, false);
								WS_Form_Common::echo_esc_css_inline($css_style);

							}, 100);
						}

					} else {

						if(
							(
								WS_Form_Common::option_get('css_style', true) &&
								(WS_Form_Common::option_get('framework', 'ws-form') == 'ws-form')
							) ||
							$this->conversational
						) {

							if($this->css_inline && !is_admin()) {

								if(!isset($this->enqueued_css_style[$style_id])) {

									add_action('wp_footer', function() use ($style_id) {

										// Output style CSS
										$this->ws_form_css->style_id = $style_id;
										$css_style = $this->ws_form_css->get_style(null, true, is_rtl());
										WS_Form_Common::echo_esc_css_inline($css_style);

									}, 100);
								}

							} else {

								$css_compile = WS_Form_Common::option_get('css_compile', false);

								wp_enqueue_style($enqueue_id, $css_compile ? sprintf('%s/ws-form/%s/public.style.%u%s.css', $upload_dir_base_url, WS_FORM_CSS_FILE_PATH, $style_id, $min) : WS_Form_Common::get_api_path(sprintf('style/%u/css/', $style_id)), array(), $this->version, 'all');
							}
						}
					}

					$this->enqueued_css_style[$style_id] = true;
				}
			}

			// JS - Custom
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			if(apply_filters('wsf_enqueue_js_custom', $this->enqueue_js_custom)) {

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				do_action('wsf_enqueue_scripts', $enqueue_args);

				$this->enqueued_js_custom = true;
			}

			// CSS - Custom
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			if(apply_filters('wsf_enqueue_css_custom', $this->enqueue_css_custom)) {

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				do_action('wsf_enqueue_styles');

				$this->enqueued_css_custom = true;
			}

			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			do_action('wsf_enqueue_internal');
		}

		// Enqueue internal script
		public function enqueue_internal_js($script, $dependency = 'common') {

			// Minified scripts?
			$min = SCRIPT_DEBUG ? '' : '.min';

			// ID
			$id = sprintf('%s-%s', $this->plugin_name, $script);

			// JavaScript
			$prop = str_replace('-', '_', $script);
			$prop_enqueue = sprintf('enqueue_js_%s', $prop);
			$hook_name = sprintf('wsf_enqueue_js_%s', $prop);

			// Prefix with public
			if($script !== 'public') {

				$script = sprintf('public-%s', $script);
			}

			// Check if already enqueued
			if(
				!wp_script_is($id, 'enqueued') &&
				property_exists($this, $prop_enqueue) &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound,WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Dynamic hook name
				apply_filters($hook_name, $this->{$prop_enqueue})
			) {
				// Add script to dependencies for public
				if(
					($dependency !== true) &&
					!in_array($id, $this->public_dependencies_js)
				) {
					$this->public_dependencies_js[] = $id;
				}

				// Determine dependency
				$dependency_js = ($dependency !== false) ? (($dependency === true) ? $this->public_dependencies_js : array(sprintf('%s-%s', $this->plugin_name, $dependency))) : array();

				// Check dependencies
				foreach($dependency_js as $dependency_js_handle) {

					if(!wp_script_is($dependency_js_handle, 'enqueued')) {

						return false;
					}
				}

				// Enqueue JavaScript
				wp_enqueue_script(

					$id,
					sprintf('%spublic/js/ws-form-%s%s.js', WS_FORM_PLUGIN_DIR_URL, $script, $min),
					$dependency_js,
					$this->version,
					WS_Form_Common::get_enqueue_args()
				);

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound,WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- All hooks prefixed with wsf_
				do_action(sprintf('wsf_enqueue_js_%s', $script));
			}
		}

		// Enqueue internal CSS
		public function enqueue_internal_css($script, $dependency = 'base', $styler_required = true, $bypass_options = false) {

			// Check if styler should be enabled
			if(!$bypass_options) {

				if($styler_required) {

					// Ensure CSS style should be rendered
					if(
						!WS_Form_Common::option_get('css_style') ||
						!WS_Form_Common::styler_enabled()
					) {
						return;
					}

				} else {

					// Ensure CSS skin should be rendered
					if(!WS_Form_Common::option_get('css_skin')) {

						return;
					}
				}
			}

			// Minified scripts?
			$min = SCRIPT_DEBUG ? '' : '.min';

			// ID
			$id = sprintf('%s-%s', $this->plugin_name, $script);

			// CSS
			$prop = str_replace('-', '_', $script);
			$prop_enqueue = sprintf('enqueue_css_%s', $prop);
			$hook_name = sprintf('wsf_enqueue_css_%s', $prop);

			// Check if already enqueued
			if(
				!wp_style_is($id, 'enqueued') &&
				property_exists($this, $prop_enqueue) &&
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound,WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Dynamic hook name
				apply_filters($hook_name, $this->{$prop_enqueue})
			) {
				// Add script to dependencies for public
				if(
					($dependency !== true) &&
					!in_array($id, $this->public_dependencies_css)
				) {
					$this->public_dependencies_css[] = $id;
				}

				// Determine dependency
				$dependency_css = ($dependency !== false) ? (($dependency === true) ? $this->public_dependencies_css : array(sprintf('%s-base', $this->plugin_name))) : array();

				// Check dependencies
				foreach($dependency_css as $dependency_css_handle) {

					if(!wp_style_is($dependency_css_handle, 'enqueued')) {

						return false;
					}
				}

				// Enqueue CSS (All require base dependency)
				wp_enqueue_style(

					$id,
					sprintf('%spublic/css/ws-form-public-%s%s.css', WS_FORM_PLUGIN_DIR_URL, $script, $min),
					$dependency_css,
					$this->version,
					'all'
				);

				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound,WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- All hooks prefixed with wsf_
				do_action(sprintf('wsf_enqueue_css_%s', $script));
			}
		}

		// WP print scripts
		public function wp_print_scripts() {

			// Get registered scripts
			global $wp_scripts;
			if(!isset($wp_scripts->registered)) { return; }

			// Do not run if there are no deregister scripts
			if(count($this->deregister_scripts) === 0) { return; }

			foreach($wp_scripts->registered as $handle => $script) {

				if(!isset($script->src)) { continue; }

				foreach($this->deregister_scripts as $deregister_script) {

					if(strpos($script->src, $deregister_script) !== false) {

						unset($wp_scripts->registered[$handle]);
					}
				}
			}
		}

		// Form - Divi - AJAX - Form
		public function ws_form_divi_form() {

			$atts = array('id' => WS_Form_Common::get_query_var('form_id'), 'visual_builder' => true);

			$this->form_instance = WS_Form_Common::get_query_var('instance_id');

			echo self::shortcode_ws_form($atts);	// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			wp_die();
		}

		// Form - HTML
		public function form_html($form_instance, $form_object, $element = 'form', $published = true, $preview = false, $element_id = false, $visual_builder = false, $class = false) {

			if($form_object === false) { return __('Unpublished form', 'ws-form'); }
			if(!is_object($form_object)) { return __('Invalid form data', 'ws-form'); }
			if(!isset($form_object->id)) { return __('Invalid form data', 'ws-form'); }

			// Get form ID
			$form_id = $form_object->id;

			// Do not render if draft or trash
			switch($form_object->status) {
				case 'draft' :
				case 'trash' :
					if($published) { return ''; };
			}

			// Init framework config
			$framework_id = WS_Form_Common::option_get('framework', WS_FORM_DEFAULT_FRAMEWORK);
			$frameworks = WS_Form_Config::get_frameworks();
			$framework = $frameworks['types'][$framework_id];

			if(!$preview) {
				// Check form limits
				$ws_form_form = new WS_Form_Form();
				$check_limit_response = $ws_form_form->apply_limits($form_object);
				if($check_limit_response !== false) { return $check_limit_response; }
			}

			// Check for form attributes
			$attributes = array();

			// Preview attribute
			if(!$published) { 
				$attributes['data-preview'] = ''; 
			}

			// Visual builder attribute
			if($visual_builder) { 
				$attributes['data-visual-builder'] = ''; 
			}

			// CSS - Framework
			if((isset($framework['css_file'])) && ($framework['css_file'] != '')) {
				$css_file_path = plugin_dir_url(__FILE__) . 'css/frameworks/' . $framework['css_file'];
				wp_enqueue_style($this->plugin_name . '-framework', $css_file_path, array(), $this->version, 'all');
			}

			// Form action
			$form_action = WS_Form_Common::get_api_path('submit');

			// Check for custom form action
			$form_action_custom = trim(WS_Form_Common::get_object_meta_value($form_object, 'form_action', ''));
			if($form_action_custom != '') { 
				$form_action = $form_action_custom; 
			}

			// Filter - Form action
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$form_action = apply_filters('wsf_shortcode_form_action', $form_action);

			// Form method
			$form_method = 'POST';
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$form_method = apply_filters('wsf_shortcode_form_method', $form_method);

			// Form attribute - id
			$form_attr_id = ($element_id === false) ? 'ws-form-' . $form_instance : $element_id;

			// Form attribute - data-wsf-custom-id
			if($element_id !== false) {
				$attributes['data-wsf-custom-id'] = '';
			}

			// Form attribute - data-instance-id
			if($visual_builder) { 
				$form_instance = false; 
			}
			if($form_instance !== false) {
				$attributes['data-instance-id'] = $form_instance;
			}

			// Form attribute - class
			$form_attr_class = ($class === false) ? '' : ' ' . esc_attr($class);

			// RTL
			if(is_rtl()) { 
				$form_attr_class .= ' wsf-rtl'; 
			}

			// Style attributes
			if(WS_Form_Common::styler_enabled()) {
				// Style ID
				$style_id = $this->ws_form_style->get_style_id_from_form_object($form_object, $this->conversational);
				$attributes['data-wsf-style-id'] = $style_id;

				// Styler ALT
				$this->ws_form_style->id = $style_id;
				if($this->ws_form_style->has_alt()) {
					$attributes['data-wsf-style-has-alt'] = '';
				}
			}

			// Sanitize element tag name (whitelist allowed elements)
			$allowed_elements = array('form', 'div', 'section', 'article');
			$element = in_array($element, $allowed_elements, true) ? $element : 'form';

			// Form wrapper
			switch($element) {
				case 'form' :
					$return_value = sprintf(
						'<form action="%s" class="wsf-form wsf-form-canvas%s" id="%s" data-id="%u" method="%s"%s></form>', 
						esc_url($form_action), 
						esc_attr($form_attr_class), 
						esc_attr($form_attr_id), 
						$form_id, 
						esc_attr($form_method), 
						WS_Form_Common::esc_attributes($attributes)
					);
					break;

				default :
					$return_value = sprintf(
						'<%1$s class="wsf-form wsf-form-canvas%2$s" id="%3$s" data-id="%4$u"%5$s></%1$s>', 
						$element, 
						esc_attr($form_attr_class), 
						esc_attr($form_attr_id), 
						$form_id, 
						WS_Form_Common::esc_attributes($attributes)
					);
					break;
			}

			// Shortcode filter
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
			$return_value = apply_filters('wsf_shortcode', $return_value);

			// Build JSON
			$form_json = wp_json_encode($form_object);

			// Form data (Only render once per form ID)
			if(!isset($this->wsf_form_json[$form_id])) {
				// Form JSON
				$this->footer_js .= sprintf("window.wsf_form_json[%u] = %s;", $form_id, $form_json) . "\n";

				// Form JSON populate
				$populate_array = self::get_populate_array($form_json);
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hooks prefixed with wsf_
				$populate_array = apply_filters('wsf_populate', $populate_array);
				if(($populate_array !== false) && count($populate_array) > 0) {
					$this->footer_js .= sprintf("window.wsf_form_json_populate[%u] = %s;", $form_id, wp_json_encode($populate_array)) . "\n";
				}

				$this->wsf_form_json[$form_id] = true;
			}

			// Add view
			$ws_form_form_stat = new WS_Form_Form_Stat();
			if($ws_form_form_stat->form_stat_check() && (WS_Form_Common::option_get('add_view_method') == 'server')) {
				// Log view
				$ws_form_form_stat->form_id = $form_id;
				$ws_form_form_stat->db_add_view();
			}

			return $return_value;
		}

		public function localization_object() {

			global $post, $wp_version;

			// Stats
			$ws_form_form_stat = new WS_Form_Form_Stat();

			// API path
			$api_path = WS_Form_Common::get_api_path();

			// Skin
			$enable_cache = !(WS_Form_Common::get_query_var('customize_theme') !== '');

			// Skin - Spacing small
			$skin_spacing_small = WS_Form_Common::option_get('skin_spacing_small', '', false, $enable_cache, true);
			if($skin_spacing_small == '') { $skin_spacing_small = 5; }

			// Skin - Spacing small
			$skin_grid_gutter = WS_Form_Common::option_get('skin_grid_gutter', '', false, $enable_cache, true);
			if($skin_grid_gutter == '') { $skin_grid_gutter = 20; }

			// NONCE enabled
			$nonce_enabled = is_admin() || is_user_logged_in() || WS_Form_Common::option_get('security_nonce');

			// Localization array
			$return_array = array(

				// Nonce - WordPress
				'x_wp_nonce'			=> $nonce_enabled ? wp_create_nonce('wp_rest') : '',
				'wsf_nonce_field_name'	=> WS_FORM_POST_NONCE_FIELD_NAME,
				'wsf_nonce'				=> $nonce_enabled ? wp_create_nonce(WS_FORM_POST_NONCE_ACTION_NAME) : '',

				// Custom permalinks
				'use_rest_route'		=> (get_option('permalink_structure') === ''),

				// URL
				'url_ajax'				=> $api_path,
				'url_ajax_namespace'	=> WS_FORM_RESTFUL_NAMESPACE,
				'url_plugin'			=> WS_FORM_PLUGIN_DIR_URL,
				// Should framework CSS be rendered? (WS Form framework only)
				'css_framework'			=> WS_Form_Common::option_get('css_framework', true),

				// Styler
				'styler_enabled'		=> WS_Form_Common::styler_enabled(),	
				'styler_visible_public'	=> WS_Form_Common::styler_visible_public(),

				// Scheme
				'scheme'				=> WS_Form_Common::option_get('scheme', 'light'),
				'scheme_auto'			=> WS_Form_Common::option_get('scheme_auto', 'on'),

				// Conversational?
				'conversational'		=> $this->conversational,

				// Max upload size
				'max_upload_size'		=> absint(WS_Form_Common::option_get('max_upload_size', 0)),

				// Field prefix
				'field_prefix'			=> WS_FORM_FIELD_PREFIX,

				// Date / time format
				'date_format'			=> get_option('date_format'),
				'time_format'			=> get_option('time_format'),
				'gmt_offset'			=> get_option('gmt_offset'),

				// Locale
				'locale'				=> get_locale(),

				// Stat
				'stat'					=> ($ws_form_form_stat->form_stat_check() && (WS_Form_Common::option_get('add_view_method', '') != 'server')),

				// Skin - Spacing small
				'skin_spacing_small'	=> $skin_spacing_small,

				// Skin - Grid gutter
				'skin_grid_gutter'		=> $skin_grid_gutter,

				// RTL
				'rtl'					=> is_rtl(),

				// Submit hash
				'wsf_hash'				=> self::get_submit_hash(),

				// Geolocation by IP lookup method
				'ip_lookup_method'		=> WS_Form_Common::option_get('ip_lookup_method')
			);
			// Pass through post ID
			if(isset($post) && ($post->ID > 0)) {

				$return_array['post_id'] = $post->ID;
			}

			return $return_array;
		}

		// Get submit hash
		public function get_submit_hash() {

			// Get hash from query string
			$wsf_hash = WS_Form_Common::get_query_var('wsf_hash');

			if($wsf_hash !== '') {

				// Decode wsf_hash
				$wsf_hash_array = json_decode($wsf_hash);

				// Chech wsf_hash
				if(
					is_null($wsf_hash_array) ||
					!is_array($wsf_hash_array)

				) {

					WS_Form_Common::throw_error(__('Invalid hash ID (get_submit_hash)', 'ws-form'));
					wp_die();
				}

				foreach($wsf_hash_array as $wsf_hash) {

					// Check hash
					if(
						!isset($wsf_hash->id) ||
						!isset($wsf_hash->hash) ||
						!WS_Form_Common::check_submit_hash($wsf_hash->hash)
					) {

						WS_Form_Common::throw_error(__('Invalid hash ID (get_submit_hash)', 'ws-form'));
						wp_die();
					}
				}

				return $wsf_hash_array;

			} else {

				return '';
			}
		}

		// Populate from action
		public function get_populate_array($form_json) {

			// Get populate data
			$form_object = json_decode($form_json);

			// Check form populate is enabled
			$form_populate_enabled = WS_Form_Common::get_object_meta_value($form_object, 'form_populate_enabled', '');
			if(!$form_populate_enabled) { return false; }

			// Read form populate data - Action ID
			$form_populate_action_id = WS_Form_Common::get_object_meta_value($form_object, 'form_populate_action_id', '');
			if($form_populate_action_id == '') { return false; }
			if(!isset(WS_Form_Action::$actions[$form_populate_action_id])) { return false; }

			// Get action
			$action = WS_Form_Action::$actions[$form_populate_action_id];

			// Check get method exists
			if(!method_exists($action, 'get')) { return false; }

			// Read form populate data - List ID
			$action_get_require_list_id = isset($action->get_require_list_id) ? $action->get_require_list_id : true;
			if($action_get_require_list_id) {

				$form_populate_list_id = WS_Form_Common::get_object_meta_value($form_object, 'form_populate_list_id', '');
				if($form_populate_list_id == '') { return false; }
			}

			// Read form populate data - Field mapping
			$form_populate_field_mapping = WS_Form_Common::get_object_meta_value($form_object, 'form_populate_field_mapping', array());

			if(method_exists($action, 'get_tags')) {

				// Read form populate data - Tag mapping
				$form_populate_tag_mapping = WS_Form_Common::get_object_meta_value($form_object, 'form_populate_tag_mapping', array());
			}

			// Get user data
			$current_user = wp_get_current_user();

			// Set list ID
			if($action_get_require_list_id) {

				$action->list_id = $form_populate_list_id;
			}

			// Try to get action data
			try {

				$get_array = $action->get($form_object, $current_user);

			} catch(Exception $e) { return false; }

			if(
				($get_array === false) ||
				!is_array($get_array)

			) { return false; }

			$data = array();

			// Process field mapping data
			$field_mapping_lookup = array();
			if(is_array($form_populate_field_mapping)) {

				foreach($form_populate_field_mapping as $field_mapping) {

					$action_field = $field_mapping->form_populate_list_fields;
					$ws_form_field = $field_mapping->ws_form_field;

					if(!isset($field_mapping_lookup[$action_field])) {

						$field_mapping_lookup[$action_field] = $ws_form_field;
					}
				}
			}

			// Map fields
			if(
				isset($get_array['fields']) &&
				is_array($get_array['fields'])
			) {

				foreach($get_array['fields'] as $id => $value) {

					if($id === '') { continue; }

					if(is_numeric($id)) {

						$data[WS_FORM_FIELD_PREFIX . $id] = $value;

					} else {

						if(isset($field_mapping_lookup[$id])) {

							$data[WS_FORM_FIELD_PREFIX . $field_mapping_lookup[$id]] = $value;
						}
					}
				}
			}

			// Map fields (Repeatable)
			if(
				isset($get_array['fields_repeatable']) &&
				is_array($get_array['fields_repeatable'])
			) {

				foreach($get_array['fields_repeatable'] as $id => $values) {

					if($id === '') { continue; }

					if(!is_array($values)) { continue; }

					foreach($values as $repeatable_index => $value) {

						if(is_numeric($id)) {

							$data[WS_FORM_FIELD_PREFIX . $id . '_' . ($repeatable_index + 1)] = $value;

						} else {

							if(isset($field_mapping_lookup[$id])) {

								$data[WS_FORM_FIELD_PREFIX . $field_mapping_lookup[$id] . '_' . ($repeatable_index + 1)] = $value;
							}
						}
					}
				}
			}

			// Tag mapping
			if(
				method_exists($action, 'get_tags') &&
				is_array($form_populate_tag_mapping) &&
				isset($get_array['tags']) &&
				is_array($get_array['tags'])
			) {

				$tag_mapping_array = array();
				foreach($get_array['tags'] as $id => $value) {

					if($value) {

						$tag_mapping_array[] = $id;
					}
				}

				foreach($form_populate_tag_mapping as $tag_mapping) {

					$ws_form_field = $tag_mapping->ws_form_field;

					$data[WS_FORM_FIELD_PREFIX . $ws_form_field] = $tag_mapping_array;
				}
			}

			// Section repeatable
			if(
				isset($get_array['section_repeatable']) &&
				is_array($get_array['section_repeatable'])
			) {

				$section_repeatable = $get_array['section_repeatable'];

			} else {

				$section_repeatable = array();
			}

			return array('action_label' => $action->label, 'data' => $data, 'section_repeatable' => $section_repeatable);
		}

		public function nonce_user_logged_out($uid = 0, $action = false) {

			return ($action === WS_FORM_POST_NONCE_ACTION_NAME) ? 0 : $uid;
		}

	}
