<?php
defined( 'ABSPATH' ) || exit;

/**
 * WPEX_Customizer Class.
 *
 * @package TotalTheme
 * @subpackage Customizer
 * @version 5.5
 */
class WPEX_Customizer {

	/**
	 * Customizer sections.
	 *
	 * @var array
	 */
	public $sections = array();

	/**
	 * Is postMessage enabled?
	 *
	 * @var bool
	 */
	protected $enable_postMessage = true;

	/**
	 * Check if currently in customizer preview mode.
	 *
	 * @var array
	 */
	protected $is_customize_preview = null;

	/**
	 * Constructor.
	 *
	 * @since 3.0.0
	 */
	public function __construct() {
		define( 'WPEX_CUSTOMIZER_DIR', WPEX_INC_DIR . 'customizer/' );
		$this->init_hooks();
		$this->load_customizer_manager();
	}

	/**
	 * Hook into actions and filters.
	 */
	public function init_hooks() {
		if ( wpex_is_request( 'frontend' ) || $this->is_customize_preview() ) {
			add_action( 'wp_loaded', array( $this, 'add_to_customizer' ), 1 );
			add_action( 'init', array( $this, 'inline_css' ) );
		}
	}

	/**
	 * Include Customizer Manager.
	 */
	public function load_customizer_manager() {
		if ( get_theme_mod( 'customizer_panel_enable', true ) && is_admin() ) {
			require_once WPEX_CUSTOMIZER_DIR . 'class-wpex-customizer-manager.php';
		}
	}

	/**
	 * Check if currently in customize mode.
	 */
	protected function is_customize_preview() {
		if ( null === $this->is_customize_preview ) {
			$this->is_customize_preview = is_customize_preview();
		}
		return $this->is_customize_preview;
	}

	/**
	 * Define panels.
	 *
	 * @todo remove "condition" param.
	 */
	public function panels() {
		$panels = array(
			'global_styles' => array(
				'title' => esc_html__( 'Global Styles', 'total' ),
			),
			'general' => array(
				'title' => esc_html__( 'General Theme Options', 'total' ),
			),
			'layout' => array(
				'title' => esc_html__( 'Layout', 'total' ),
			),
			'typography' => array(
				'title' => esc_html__( 'Typography', 'total' ),
			),
			'togglebar' => array(
				'title' => esc_html__( 'Toggle Bar', 'total' ),
				'is_section' => true,
			),
			'topbar' => array(
				'title' => esc_html__( 'Top Bar', 'total' ),
			),
			'header' => array(
				'title' => esc_html__( 'Header', 'total' ),
			),
			'sidebar' => array(
				'title' => esc_html__( 'Sidebar', 'total' ),
				'is_section' => true,
			),
			'blog' => array(
				'title' => esc_html__( 'Blog', 'total' ),
			),
			'portfolio' => array(
				'title' => wpex_get_portfolio_name(),
				'condition' => 'wpex_is_total_portfolio_enabled',
			),
			'staff' => array(
				'title' => wpex_get_staff_name(),
				'condition' => 'wpex_is_total_staff_enabled',
			),
			'testimonials' => array(
				'title' => wpex_get_testimonials_name(),
				'condition' => 'wpex_is_total_testimonials_enabled',
			),
			// @todo rename to footer_callout
			'callout' => array(
				'title' => esc_html__( 'Callout', 'total' ),
			),
			'footer_widgets' => array(
				'title' => esc_html__( 'Footer Widgets', 'total' ),
				'is_section' => true,
			),
			'footer_bottom' => array(
				'title' => esc_html__( 'Footer Bottom', 'total' ),
				'is_section' => true,
			),
		);

		/**
		 * Filters the customizer panels.
		 *
		 * @param array $panels
		 */
		$panels = (array) apply_filters( 'wpex_customizer_panels', $panels );

		return $panels;
	}

	/**
	 * Returns array of enabled panels.
	 *
	 * @todo make this better, instead of saving enabled panels we should save array of disabled panels.
	 */
	public function enabled_panels() {
		$panels = $this->panels();
		$disabled_panels = (array) get_option( 'wpex_disabled_customizer_panels' );
		if ( $disabled_panels ) {
			foreach ( $panels as $key => $val ) {
				if ( in_array( $key, $disabled_panels ) ) {
					unset( $panels[ $key ] );
				}
			}
		}
		return $panels;
	}

	/**
	 * Check if customizer section is enabled.
	 */
	public function is_section_enabled( $section, $section_id ) {
		$section_panel = ! empty( $section[ 'panel' ] ) ? $section[ 'panel' ] : $section_id;
		if ( $section_panel ) {
			$enabled_panels = $this->enabled_panels();
			$section_panel = str_replace( 'wpex_', '', $section_panel );
			if ( empty( $enabled_panels[ $section_panel ] ) ) {
				return false;
			}
		}
		return true; // all enabled by default
	}

	/**
	 * Initialize customizer settings.
	 */
	public function add_to_customizer() {
		if ( ! $this->sections ) {
			$this->add_sections();
		}

		// Add scripts for custom controls.
		add_action( 'customize_controls_enqueue_scripts', array( $this, 'customize_controls_enqueue_scripts' ) );

		// Inline CSS for customizer icons.
		add_action( 'customize_controls_print_styles', array( $this, 'customize_controls_print_styles' ) );

		// Register custom controls.
		add_action( 'customize_register', array( $this, 'register_control_types' ) );

		// Customizer conditionals.
		add_action( 'customize_register', array( $this, 'active_callback_functions' ) );

		// Remove core panels and sections.
		add_action( 'customize_register', array( $this, 'remove_core_sections' ), 11 );

		// Add theme customizer sections and panels.
		add_action( 'customize_register', array( $this, 'add_customizer_panels_sections' ), 40 );

		// Load JS file for customizer.
		add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );

	}

	/**
	 * Inline css.
	 */
	public function inline_css() {
		if ( $this->is_customize_preview() && $this->enable_postMessage ) {
			add_action( 'wp_head', array( $this, 'live_preview_styles' ), 99999 );
		} else {
			add_filter( 'wpex_head_css', array( $this, 'head_css' ), 999 );
		}
	}

	/**
	 * Adds custom controls.
	 */
	public function customize_controls_enqueue_scripts() {
		// Chosen
		wp_enqueue_style( 'wpex-chosen' );
		wp_enqueue_script( 'wpex-chosen' );
		wp_enqueue_script( 'wpex-chosen-icon' );

		// Controls JS
		wp_enqueue_script(
			'wpex-customizer-controls',
			wpex_asset_url( 'js/dynamic/customizer/wpex-controls.min.js' ),
			array( 'customize-controls', 'wpex-chosen', 'jquery' ),
			WPEX_THEME_VERSION
		);

		wp_localize_script(
			'wpex-customizer-controls',
			'wpex_custom_controls',
			array(
				'deleteConfirm' => esc_html__( 'Are you sure you want to delete this?', 'total' ),
				'duplicate' => esc_html__( 'This item has already been added', 'total' ),
			)
		);

		wp_enqueue_script(
			'wpex-customizer-control-visibility',
			wpex_asset_url( 'js/dynamic/customizer/wpex-control-visibility.min.js' ),
			array( 'customize-controls' ),
			WPEX_THEME_VERSION
		);

		wp_localize_script(
			'wpex-customizer-control-visibility',
			'wpex_control_display',
			$this->get_control_visibility_settings()
		);

		// Customizer CSS
		wp_enqueue_style(
			'wpex-customizer-css',
			wpex_asset_url( 'css/wpex-customizer.css' ),
			array(),
			WPEX_THEME_VERSION
		);
	}

	/**
	 * Registered custom controls that are eligible to be rendered via JS and created dynamically.
	 *
	 * @link https://developer.wordpress.org/reference/classes/wp_customize_manager/register_control_type/
	 */
	public function register_control_types( $wp_customize ) {
		$wp_customize->register_control_type( 'TotalTheme\\Customizer\\Controls\\Hr' );
		$wp_customize->register_control_type( 'TotalTheme\\Customizer\\Controls\\Heading' );
		$wp_customize->register_control_type( 'TotalTheme\\Customizer\\Controls\\Notice' );
		$wp_customize->register_control_type( 'TotalTheme\\Customizer\\Controls\\Ticon_Select' );
	}

	/**
	 * Adds custom controls.
	 */
	public function active_callback_functions() {
		require_once WPEX_CUSTOMIZER_DIR . 'customizer-active-callback-functions.php';
	}

	/**
	 * Adds CSS for customizer custom controls.
	 *
	 * MUST Be added inline to get post type Icons.
	 */
	public function customize_controls_print_styles() {
		$portfolio_icon = wpex_dashicon_css_content( wpex_get_portfolio_menu_icon() );
		$staff_icon = wpex_dashicon_css_content( wpex_get_staff_menu_icon() );
		$testimonials_icon = wpex_dashicon_css_content( wpex_get_testimonials_menu_icon() );

		// Get admin color.
		// @todo remove once this becomes available as a CSS variable.
		$admin_color = get_user_option( 'admin_color' );

		switch ( $admin_color ) {
			case 'light':
				$icon_color = '#04a4cc';
				break;
			case 'blue':
				$icon_color = '#0073aa';
				break;
			case 'coffee':
				$icon_color = '#c7a589';
				break;
			case 'ectoplasm':
				$icon_color = '#a3b745';
				break;
			case 'midnight':
				$icon_color = '#e14d43';
				break;
			case 'ocean':
				$icon_color = '#9ebaa0';
				break;
			case 'sunrise':
				$icon_color = '#dd823b';
				break;
			case 'modern':
				$icon_color = '#3858e9';
				break;
			default:
				$icon_color = '#2271b1';
				break;
		}

		?>

		 <style id="wpex-customizer-controls-css">

			/* Icons */
			li.control-panel:not(.customize-pane-child)[id^="accordion-panel-wpex_"] > .accordion-section-title:before,
			li.control-section:not(.control-subsection)[id^="accordion-section-wpex_"] > .accordion-section-title:before {
				display: inline-block;
				width: 20px;
				height: 20px;
				font-size: 20px;
				line-height: 1;
				text-decoration: inherit;
				font-weight: 400;
				font-style: normal;
				vertical-align: top;
				text-align: center;
				transition: inherit;
				-webkit-font-smoothing: antialiased;
				-moz-osx-font-smoothing: grayscale;
				color:<?php echo esc_attr( $icon_color ) ?>;
				margin-right: 10px;
				font-family: "dashicons";
				content: "\f108";
			}

			<?php if ( is_rtl() ) { ?>
				li.control-panel:not(.customize-pane-child)[id^="accordion-panel-wpex_"] > .accordion-section-title:before,
				li.control-section:not(.control-subsection)[id^="accordion-section-wpex_"] > .accordion-section-title:before {
					margin-right: 0;
					margin-left: 10px;
				}
			<?php } ?>
			#accordion-panel-wpex_global_styles > .accordion-section-title:before { content: "\f100" }
			#accordion-panel-wpex_typography > .accordion-section-title:before { content: "\f216" }
			#accordion-panel-wpex_layout > .accordion-section-title:before { content: "\f535" }
			#accordion-section-wpex_togglebar > .accordion-section-title:before { content: "\f132" }
			#accordion-panel-wpex_topbar > .accordion-section-title:before { content: "\f157" }
			#accordion-panel-wpex_header > .accordion-section-title:before { content: "\f175" }
			#accordion-section-wpex_sidebar > .accordion-section-title:before { content: "\f135" }
			#accordion-panel-wpex_blog > .accordion-section-title:before { content: "\f109" }
			#accordion-panel-wpex_portfolio > .accordion-section-title:before { content: "\<?php echo esc_attr( $portfolio_icon ); ?>" }
			#accordion-panel-wpex_staff > .accordion-section-title:before { content: "\<?php echo esc_attr( $staff_icon ); ?>" }
			#accordion-panel-wpex_testimonials > .accordion-section-title:before { content: "\<?php echo esc_attr( $testimonials_icon ); ?>" }
			#accordion-panel-wpex_callout > .accordion-section-title:before { content: "\f488" }
			#accordion-section-wpex_footer_widgets > .accordion-section-title:before { content: "\f209" }
			#accordion-section-wpex_footer_bottom > .accordion-section-title:before { content: "\f209"; }
			#accordion-section-wpex_visual_composer > .accordion-section-title:before { content: ""; background-image: url("data:image/svg+xml,%3Csvg fill='%230473aa' height='20' width='20' viewBox='0.0004968540742993355 -0.00035214610397815704 65.50897979736328 49.80835723876953' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M51.345 9.041c-4.484-.359-6.371.747-8.509 1.039 2.169 1.135 5.125 2.099 8.708 1.89-3.3 1.296-8.657.853-12.355-1.406-.963-.589-1.975-1.519-2.733-2.262C33.459 5.583 31.247.401 21.683.018 9.687-.457.465 8.347.016 19.645s8.91 20.843 20.907 21.318c.158.008.316.006.472.006 3.137-.184 7.27-1.436 10.383-3.355-1.635 2.32-7.746 4.775-10.927 5.553.319 2.527 1.671 3.702 2.78 4.497 2.459 1.76 5.378-.73 12.11-.606 3.746.069 7.61 1.001 10.734 2.75l3.306-11.54c8.402.13 15.4-6.063 15.716-14.018.321-8.088-5.586-14.527-14.152-15.209h0z'%3E%3C/path%3E%3C/svg%3E"); }
			#accordion-panel-wpex_woocommerce > .accordion-section-title:before { content: ""; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath fill='%237F54B3' d='M612.192 426.336c0-6.896-3.136-51.6-28-51.6-37.36 0-46.704 72.256-46.704 82.624 0 3.408 3.152 58.496 28.032 58.496 34.192-.032 46.672-72.288 46.672-89.52zm202.192 0c0-6.896-3.152-51.6-28.032-51.6-37.28 0-46.608 72.256-46.608 82.624 0 3.408 3.072 58.496 27.952 58.496 34.192-.032 46.688-72.288 46.688-89.52zM141.296.768c-68.224 0-123.504 55.488-123.504 123.92v650.72c0 68.432 55.296 123.92 123.504 123.92h339.808l123.504 123.936V899.328h278.048c68.224 0 123.52-55.472 123.52-123.92v-650.72c0-68.432-55.296-123.92-123.52-123.92h-741.36zm526.864 422.16c0 55.088-31.088 154.88-102.64 154.88-6.208 0-18.496-3.616-25.424-6.016-32.512-11.168-50.192-49.696-52.352-66.256 0 0-3.072-17.792-3.072-40.752 0-22.992 3.072-45.328 3.072-45.328 15.552-75.728 43.552-106.736 96.448-106.736 59.072-.032 83.968 58.528 83.968 110.208zM486.496 302.4c0 3.392-43.552 141.168-43.552 213.424v75.712c-2.592 12.08-4.16 24.144-21.824 24.144-46.608 0-88.88-151.472-92.016-161.84-6.208 6.896-62.24 161.84-96.448 161.84-24.864 0-43.552-113.648-46.608-123.936C176.704 436.672 160 334.224 160 327.328c0-20.672 1.152-38.736 26.048-38.736 6.208 0 21.6 6.064 23.712 17.168 11.648 62.032 16.688 120.512 29.168 185.968 1.856 2.928 1.504 7.008 4.56 10.432 3.152-10.288 66.928-168.784 94.96-168.784 22.544 0 30.4 44.592 33.536 61.824 6.208 20.656 13.088 55.216 22.416 82.752 0-13.776 12.48-203.12 65.392-203.12 18.592.032 26.704 6.928 26.704 27.568zM870.32 422.928c0 55.088-31.088 154.88-102.64 154.88-6.192 0-18.448-3.616-25.424-6.016-32.432-11.168-50.176-49.696-52.288-66.256 0 0-3.888-17.92-3.888-40.896s3.888-45.184 3.888-45.184c15.552-75.728 43.488-106.736 96.384-106.736 59.104-.032 83.968 58.528 83.968 110.208z'/%3E%3C/svg%3E"); }
			#accordion-section-wpex_tribe_events > .accordion-section-title:before { content: "\f145" }
			#accordion-section-wpex_bbpress > .accordion-section-title:before { content: "\f449" }
			#accordion-panel-wpex_learndash > .accordion-section-title:before { content: "\f118" }
			#accordion-panel-wpex_buddypress > .accordion-section-title:before { content: "\f448" }
			#accordion-panel-wpex_sensei > .accordion-section-title:before { font-family: sensei; content: ""; }
		 </style>

	<?php }

	/**
	 * Removes core modules.
	 */
	public function remove_core_sections( $wp_customize ) {
		// Remove core sections.
		$wp_customize->remove_section( 'colors' );
		$wp_customize->remove_section( 'background_image' );

		// Remove core controls.
		$wp_customize->remove_control( 'blogdescription' );
		$wp_customize->remove_control( 'header_textcolor' );
		$wp_customize->remove_control( 'background_color' );
		$wp_customize->remove_control( 'background_image' );

		// Remove default settings.
		$wp_customize->remove_setting( 'background_color' );
		$wp_customize->remove_setting( 'background_image' );
	}

	/**
	 * Get all sections.
	 */
	public function add_sections() {
		$panels = $this->panels();

		if ( ! $panels ) {
			return;
		}

		foreach ( $panels as $id => $val ) {

			// These have their own sections outside the main class scope.
			if ( 'typography' === $id ) {
				continue;
			}

			// Continue if condition isn't me.
			if ( isset( $val['condition'] ) && ! call_user_func( $val['condition'] ) ) {
				continue;
			}

			// Section file location.
			if ( ! empty( $val['settings'] ) ) {
				$file = $val['settings'];
			} else {
				$file = WPEX_CUSTOMIZER_DIR . 'settings/' . $id . '.php';
			}

			// Include file and update sections var.
			if ( ! is_array( $file ) && file_exists( $file ) ) {
				require_once $file;
			}

		}

		// Loop through sections and set keys equal to ID for easier child theming.
		// Also remove anything that is only needed in the customizer to free up memory.
		$parsed_sections = array();
		if ( $this->sections && is_array( $this->sections ) ) {
			foreach ( $this->sections as $key => $val ) {
				$new_settings = array();
				if ( ! $this->is_customize_preview() ) {
					unset( $val['title'], $val['panel'], $val['description'] );
				}
				foreach ( $val['settings'] as $skey => $sval ) {
					if ( $this->is_customize_preview() ) {
						$new_settings[$sval['id']] = $sval;
					} else {
						if ( empty( $sval['inline_css'] ) ) {
							continue; // only inline_css needed for frontend.
						}
						if ( isset( $sval['control']['type'] ) ) {
							$sval['type'] = $sval['control']['type'];
						}
						unset( $sval['transport'], $sval['control'], $sval['control_display'], $sval['description'] );
						$new_settings[$sval['id']] = $sval;
					}
				}
				if ( $new_settings ) {
					$val['settings'] = $new_settings;
					$parsed_sections[$key] = $val;
				}
			}
		}

		/**
		 * Filters the customizer sections.
		 *
		 * @param array $sections
		 */
		$this->sections = (array) apply_filters( 'wpex_customizer_sections', $parsed_sections );
	}

	/**
	 * Registers new controls.
	 *
	 * Removes default customizer sections and settings
	 * Adds new customizer sections, settings & controls
	 */
	public function add_customizer_panels_sections( $wp_customize ) {
		$all_panels = $this->panels();
		$enabled_panels = $this->enabled_panels();

		if ( ! $enabled_panels ) {
			return;
		}

		$priority = 140;

		foreach ( $all_panels as $id => $val ) {

			$priority++;

			// Continue if panel is disabled or it's the typography panel.
			if ( ! isset( $enabled_panels[$id] ) || 'typography' === $id ) {
				continue;
			}

			// Continue if condition isn't met.
			if ( isset( $val['condition'] ) && ! $val['condition'] ) {
				continue;
			}

			// Get panel title.
			$title = $val['title'] ?? $val;

			// Check if panel is a section.
			$is_section = isset( $val['is_section'] );

			// Add section.
			if ( $is_section ) {
				$wp_customize->add_section( 'wpex_' . $id, array(
					'priority' => $priority,
					'title'    => $title,
				) );
			}

			// Add Panel.
			else {
				$wp_customize->add_panel( 'wpex_' . $id, array(
					'priority' => $priority,
					'title'    => $title,
				) );
			}

		}

		// Create the new customizer sections.
		if ( ! empty( $this->sections ) ) {
			$this->create_sections( $wp_customize, $this->sections );
		}

	}

	/**
	 * Creates the Sections and controls for the customizer.
	 */
	public function create_sections( $wp_customize ) {
		$enabled_panels = $this->enabled_panels();

		// Loop through sections and add create the customizer sections, settings & controls.
		foreach ( $this->sections as $section_id => $section ) {

			// Check if section panel is enabled.
			if ( ! $this->is_section_enabled( $section, $section_id ) ) {
				continue;
			}

			// Get section settings.
			$settings = ! empty( $section['settings'] ) ? $section['settings'] : null;

			// Return if no settings are found.
			if ( ! $settings ) {
				return;
			}

			// Add customizer section.
			if ( isset( $section['panel'] ) ) {
				$wp_customize->add_section( $section_id, array(
					'title'       => $section['title'],
					'panel'       => $section['panel'],
					'description' => $section['description'] ?? '',
				) );
			}

			// Add settings+controls.
			foreach ( $settings as $setting ) {

				if ( empty( $setting['id'] ) ) {
					continue;
				}

				// Get vals.
				$id           = $setting['id'];
				$transport    = $setting['transport'] ?? 'refresh';
				$default      = $setting['default'] ?? '';
				$control_type = $setting['control']['type'] ?? 'text';

				// Check partial refresh.
				if ( 'partialRefresh' === $transport ) {
					$transport = isset( $wp_customize->selective_refresh ) ? 'postMessage' : 'refresh';
				}

				// Set transport to refresh if postMessage is disabled.
				if ( ! $this->enable_postMessage || 'wpex_heading' === $control_type ) {
					$transport = 'refresh';
				}

				// Add values to control.
				$setting['control']['settings'] = $setting['id'];
				$setting['control']['section'] = $section_id;

				// Get global setting description.
				if ( empty( $setting['control']['description'] ) ) {
					$control_description = $this->get_control_description( $setting );
					if ( $control_description ) {
						$setting['control']['description'] = $control_description;
					}
				}

				// Control object.
				if ( ! empty( $setting['control']['object'] ) ) {
					$control_obj = $setting['control']['object'];
				} else {
					$control_obj = $this->get_control_object( $control_type );
				}

				// Add sanitize callbacks.
				// All customizer settings should have a sanitize callback - IMPORTANT !!!!
				if ( ! empty( $setting['sanitize_callback'] ) ) {
					$sanitize_callback = $setting['sanitize_callback'];
				} else {
					$sanitize_callback = $this->get_sanitize_callback( $control_type );
				}

				// Add setting.
				$wp_customize->add_setting( $id, array(
					'type'              => 'theme_mod',
					'transport'         => $transport,
					'default'           => $default,
					'sanitize_callback' => $sanitize_callback,
				) );

				if ( isset( $setting['control'] ) ) {

					// Add choices here so we don't have to store them in memory on the frontend.
					if ( isset( $setting['control']['choices'] ) ) {
						$setting['control']['choices'] = $this->parse_control_choices( $setting['control']['choices'] );
					}

					$wp_customize->add_control( new $control_obj( $wp_customize, $id, $setting['control'] ) );
				}
			}

		}

		// Load partial refresh functions.
		require_once WPEX_CUSTOMIZER_DIR . 'customizer-partial-refresh.php';
	}

	/**
	 * Returns object name for for given control type.
	 */
	public function get_control_object( $control_type ) {
		switch ( $control_type ) {
			case 'image':
				return 'WP_Customize_Image_Control';
				break;
			case 'media':
				return 'WP_Customize_Media_Control';
				break;
			case 'color':
				return 'WP_Customize_Color_Control';
				break;
			case 'hr':
				return 'TotalTheme\\Customizer\\Controls\\Hr';
				break;
			case 'wpex_blocks':
				return 'TotalTheme\\Customizer\\Controls\\Blocks';
				break;
			case 'wpex_social_profiles':
				return 'TotalTheme\\Customizer\\Controls\\Social_Profiles';
				break;
			case 'wpex-heading':
				return 'TotalTheme\\Customizer\\Controls\\Heading';
				break;
			case 'wpex_notice':
				return 'TotalTheme\\Customizer\\Controls\\Notice';
				break;
			case 'wpex_length_unit':
				return 'TotalTheme\\Customizer\\Controls\\Length_Unit';
				break;
			case 'wpex_trbl':
				return 'TotalTheme\\Customizer\\Controls\\Top_Right_Bottom_Left';
				break;
			case 'wpex_svg_select':
				return 'TotalTheme\\Customizer\\Controls\\SVG_Select';
				break;
			case 'wpex_pixel':
				return 'TotalTheme\\Customizer\\Controls\\Pixel';
				break;
			case 'wpex_toggle':
				return 'TotalTheme\\Customizer\\Controls\\Toggle';
				break;
			case 'wpex-sortable':
			case 'wpex_sortable':
				return 'TotalTheme\\Customizer\\Controls\\Sortable';
				break;
			case 'wpex_ticon_select':
			case 'wpex-fa-icon-select':
			case 'ticon-select':
				return 'TotalTheme\\Customizer\\Controls\\Ticon_Select';
				break;
			case 'wpex-dropdown-pages':
				return 'TotalTheme\\Customizer\\Controls\\Dropdown_Pages';
				break;
			case 'wpex-font-family':
				return 'TotalTheme\\Customizer\\Controls\\Font_Family';
				break;
			case 'wpex-card-select':
				return 'TotalTheme\\Customizer\\Controls\\Card_Select';
				break;
			case 'wpex-dropdown-templates':
				return 'TotalTheme\\Customizer\\Controls\\Dropdown_Templates';
				break;
			case 'wpex_textarea':
				return 'TotalTheme\\Customizer\\Controls\\Textarea';
				break;
			case 'wpex_bg_patterns':
				return 'TotalTheme\\Customizer\\Controls\\Bg_Patterns';
				break;
			case 'wpex_responsive_field':
				return 'TotalTheme\\Customizer\\Controls\\Responsive_Field';
				break;
			case 'wpex-columns':
			case 'wpex-grid-columns':
				return 'TotalTheme\\Customizer\\Controls\\Grid_Columns';
				break;
			case 'multi-select':
			case 'wpex_multi_select':
				return 'TotalTheme\\Customizer\\Controls\\Multiple_Select';
				break;
			case 'wpex-visibility-select':
				return 'TotalTheme\\Customizer\\Controls\\Visibility_Select';
				break;
			default :
				return 'WP_Customize_Control';
				break;
		}
	}

	/**
	 * Returns control description when not defined.
	 */
	protected function get_control_description( $control ) {
		$control_type = $control['type'] ?? 'text';
		switch ( $control_type ) {
			case 'wpex-dropdown-templates':
				return esc_html__( 'Create a dynamic template using Templatera to override the default content layout.', 'total' );
			break;
		}
	}

	/**
	 * Returns sanitize_callback for given control type.
	 */
	public function get_sanitize_callback( $control_type ) {
		switch ( $control_type ) {
			case 'checkbox':
			case 'wpex_toggle':
				return 'wpex_sanitize_checkbox';
				break;
			case 'wpex_length_unit':
				return __CLASS__ . '::sanitize_length_unit';
				break;
			case 'wpex_pixel':
				return __CLASS__ . '::sanitize_px_field';
				break;
			case 'select':
				return 'wpex_sanitize_customizer_select';
				break;
			case 'image':
				return 'esc_url';
				break;
			case 'wpex-dropdown-templates':
			case 'wpex-dropdown-pages':
			case 'media':
				return 'absint';
				break;
			case 'color':
				return 'sanitize_hex_color';
				break;
			case 'wpex_bg_patterns':
				return 'wp_strip_all_tags';
				break;
			case 'wpex-columns':
				return 'wpex_sanitize_customizer_columns';
				break;
			case 'wpex-card-select':
			case 'multiple-select':
				return 'sanitize_text_field';
				break;
			case 'wpex-visibility-select':
				return 'wpex_sanitize_visibility';
				break;
			case 'wpex_textarea':
			default:
				return 'wp_kses_post';
				break;
		}
	}

	/**
	 * Loads js file for customizer preview.
	 */
	public function customize_preview_init() {
		if ( ! $this->enable_postMessage ) {
			return;
		}

		wp_enqueue_script( 'wpex-customizer-preview',
			wpex_asset_url( 'js/dynamic/customizer/wpex-preview.min.js' ),
			array( 'customize-preview' ),
			WPEX_THEME_VERSION,
			true
		);

		wp_localize_script(
			'wpex-customizer-preview',
			'wpex_customizer_params',
			array(
				'inline_css' => $this->get_inline_css_settings(),
			)
		);
	}

	/**
	 * Loops through all settings and returns visibility settings.
	 *
	 * @since 5.1
	 */
	protected function get_control_visibility_settings() {
		$control_visibility = array();
		$settings = wp_list_pluck( $this->sections, 'settings' );

		if ( ! $settings || ! is_array( $settings ) ) {
			return array();
		}

		foreach ( $settings as $settings_array ) {
			foreach ( $settings_array as $setting ) {
				if ( isset( $setting['control_display'] ) ) {
					$control_visibility[$setting['id']] = $this->parse_control_display( $setting['control_display'] );
				}
			}
		}

		return $control_visibility;
	}

	/**
	 * Loops through all settings and returns array of online inline_css settings.
	 */
	public function get_inline_css_settings() {
		$css_settings = array();
		$settings = wp_list_pluck( $this->sections, 'settings' );

		if ( ! $settings || ! is_array( $settings ) ) {
			return;
		}

		$color_scheme_switcher_enabled = false; // @todo

		foreach ( $settings as $settings_array ) {
			foreach ( $settings_array as $setting ) {
				if ( isset( $setting['inline_css'] ) ) {
					if ( $color_scheme_switcher_enabled
						&& isset( $setting['type'] )
						&& 'color' === $setting['type']
						&& isset( $setting['inline_css']['target'] )
					) {
						$setting['inline_css']['target'] = $this->parse_inline_css_target( $setting['inline_css']['target'] );
					}
					$css_settings[$setting['id']] = $setting['inline_css'];
					if ( isset( $setting['default'] ) ) {
						$css_settings[$setting['id']]['default'] = $setting['default'];
					}
				}
			}
		}

		return $css_settings;
	}

	/**
	 * Parses the inline_css target element to see if it
	 * needs the ".wpex-color-scheme-default" class or not.
	 */
	protected function parse_inline_css_target( $target ) {
		if ( is_string( $target ) ) {
			$target = $this->inline_css_target_default_color_scheme( $target );
		} else {
			foreach ( $target as $k => $val ) {
				$target[$k] = $this->inline_css_target_default_color_scheme( $val );
			}
		}
		return $target;
	}

	/**
	 * Remove color scheme--default target.
	 */
	protected function inline_css_target_default_color_scheme( $target ) {
		if ( is_string( $target ) ) {
			if ( ':root' === $target ) {
				$target = str_replace( ':root', '.wpex-color-scheme-default', $target );
			} else {
				if ( str_starts_with( $target, 'body' ) ) {
					$target = str_replace( 'body', '.wpex-color-scheme-default', $target );
				} else {
					$target = '.wpex-color-scheme-default ' . $target;
				}
			}
		}
		return $target;
	}

	/**
	 * Generates inline CSS for styling options.
	 */
	public function loop_through_inline_css( $return = 'css' ) {
		$settings = $this->get_inline_css_settings();

		if ( ! $settings ) {
			return;
		}

		$elements_to_alter = array();
		$preview_styles    = array();
		$add_css           = '';

		// Combine and add media queries last for front-end CSS (not needed for live preview).
		$media_queries = array(
			'(min-width: 960px)'                        => null,
			'(min-width: 960px) and (max-width:1280px)' => null,
			'(min-width: 768px) and (max-width:959px)'  => null,
			'(max-width: 767px)'                        => null,
			'(min-width: 480px) and (max-width:767px)'  => null,
		);

		foreach ( $settings as $key => $inline_css ) {

			// Store setting ID.
			$setting_id = $key;

			// Conditional CSS check to add CSS or not.
			if ( isset( $inline_css['condition'] ) && ! call_user_func( $inline_css['condition'] ) ) {
				continue;
			}

			// Get theme mod value.
			$default   = $inline_css['default'] ?? null;
			$theme_mod = get_theme_mod( $setting_id, $default );

			// Checkboxes.
			if ( isset( $inline_css['value'] ) && wp_validate_boolean( $theme_mod ) ) {
				$theme_mod = $inline_css['value'];
			}

			// Get css params.
			$sanitize       = $inline_css['sanitize'] ?? false;
			$alter          = $inline_css['alter'] ?? '';
			$media_query    = $inline_css['media_query'] ?? false;
			$target         = $inline_css['target'] ?? '';
			$important      = isset( $inline_css['important'] ) ? '!important' : false;
			$multi_prop_val = array(); // @todo move loop into it's own function so we can use recursive instead.

			// If alter is set to "display" and type equals 'checkbox' then set correct value.
			if ( 'display' === $alter && 'checkbox' === $sanitize ) {
				$theme_mod = $theme_mod ? '' : 'none';
			}

			// Theme mod can't be empty (prevent 0 inputs).
			if ( ! $theme_mod ) {
				continue;
			}

			// Add to preview_styles array.
			if ( 'preview_styles' === $return ) {
				$preview_styles[$setting_id] = '';
			}

			// Target and alter vars are required, if they are empty continue onto the next setting.
			if ( ! $target || ! $alter ) {
				continue;
			}

			// Multi target element (trbl) - currently only supported for padding.
			if ( 'padding' === $alter && false !== strpos( $theme_mod, ':' ) ) {
				$multi_prop_val = $this->parse_css_multi_property( $theme_mod, $alter );
				if ( $multi_prop_val && is_array( $multi_prop_val ) ) {
					$og_alter = $alter;
					$alter = array();
					foreach ( $multi_prop_val as $prop => $val ) {
						$alter[] = $prop;
					}
				}
			}

			// Sanitize output.
			if ( $sanitize ) {
				$theme_mod = wpex_sanitize_data( $theme_mod, $sanitize );
				if ( '' === $theme_mod || null === $theme_mod ) {
					continue; // value is empty after sanitization.
				}
			}

			// Set to array if not.
			$target = is_array( $target ) ? $target : array( $target );
			$target = array_filter( $target ); // remove empty targets (some targets maybe conditionally added).

			// Loop through items.
			foreach ( $target as $element ) {

				// Add each element to the elements to alter to prevent undefined indexes.
				if ( 'css' === $return && ! $media_query && ! isset( $elements_to_alter[$element] ) ) {
					$elements_to_alter[$element] = '';
				}

				// Return CSS or js.
				if ( is_array( $alter ) ) {

					// Loop through elements to alter.
					foreach ( $alter as $alter_val ) {

						// Modify theme_mod for multi properties.
						if ( $multi_prop_val && is_array( $multi_prop_val ) ) {
							$theme_mod = $multi_prop_val[$alter_val] ?? null;
							if ( ! $theme_mod ) {
								continue;
							}
						}

						// Define el css output.
						$el_css = $alter_val . ':' . $theme_mod . $important . ';';

						// Inline CSS.
						if ( 'css' === $return ) {

							if ( $media_query ) {
								$media_queries[$media_query][$element][] = $el_css;
							} else {
								$elements_to_alter[$element] .= $el_css;
							}
						}

						// Live preview styles.
						elseif ( 'preview_styles' === $return ) {

							if ( $media_query ) {
								$preview_styles[$setting_id] .= '@media only screen and '. $media_query . '{' . $element . '{ ' . $el_css . '; }}';
							} else {
								$preview_styles[$setting_id] .= $element . '{ ' . $el_css . '; }';
							}
						}
					}
				}

				// Single element to alter.
				else {

					// Add url to background-image params.
					if ( 'background-image' === $alter ) {
						$theme_mod = 'url(' . esc_url( $theme_mod ) . ')';
					}

					// Define el css output.
					$el_css = $alter .':' . $theme_mod . $important . ';';

					// Inline CSS.
					if ( 'css' === $return ) {

						if ( $media_query ) {
							$media_queries[$media_query][$element][] = $el_css;
						} else {
							$elements_to_alter[$element] .= $el_css;
						}

					}

					// Live preview styles.
					elseif ( 'preview_styles' === $return ) {

						if ( $media_query ) {
							$preview_styles[$setting_id] .= '@media only screen and ' . $media_query . '{' . $element . '{ ' . $el_css . '; }}';
						} else {
							$preview_styles[$setting_id] .= $element . '{ ' . $el_css . '; }';
						}

					}

				}

			}

		} // End settings loop.

		// Loop through elements and return CSS.
		if ( 'css' === $return ) {

			if ( $elements_to_alter && is_array( $elements_to_alter ) ) {
				foreach ( $elements_to_alter as $element => $attributes ) {
					if ( is_string( $attributes ) && $attributes = trim( $attributes ) ) {
						$add_css .= $element . '{' . $attributes . '}';
					}
				}
			}

			if ( $media_queries && is_array( $media_queries ) ) {
				foreach ( $media_queries as $media_query => $elements ) {
					if ( is_array( $elements ) && $elements ) {
						$add_css .= '@media only screen and ' . $media_query . '{';
						foreach ( $elements as $element => $attributes ) {
							if ( $attributes && is_array( $attributes ) ) {
								$add_css .= $element . '{' . implode( '', $attributes ) . '}';
							}
						}
						$add_css .= '}';
					}
				}
			}

			return $add_css;

		}

		// Return preview styles.
		if ( 'preview_styles' === $return ) {
			return $preview_styles;
		}

	}

	/**
	 * Returns CSS to output to wp_head.
	 */
	public function head_css( $output ) {
		$inline_css = $this->loop_through_inline_css( 'css' );
		if ( $inline_css ) {
			$output .= '/*CUSTOMIZER STYLING*/' . $inline_css;
		}
		unset( $this->sections );
		return $output;
	}

	/**
	 * Returns CSS to output to wp_head.
	 */
	public function live_preview_styles() {
		$live_preview_styles = $this->loop_through_inline_css( 'preview_styles' );
		if ( $live_preview_styles ) {
			foreach ( $live_preview_styles as $key => $val ) {
				if ( ! empty( $val ) ) {
					echo '<style id="wpex-customizer-' . esc_attr( trim( $key ) ) . '"> ' . $val . '</style>';
				}
			}
		}
	}

	/**
	 * Parses the control_display so we dynamically add values rather then storing me memory.
	 */
	protected function parse_control_display( $display ) {
		if ( isset( $display['value'] ) ) {
			$display['value'] = $this->parse_control_display_value( $display['value'] );
		}
		return $display;
	}

	/**
	 * Parses the control_display value key.
	 */
	protected function parse_control_display_value( $value ) {
		switch ( $value ) {
			case 'header_has_aside':
				$value = wpex_get_header_styles_with_aside_support();
				break;
		}
		return $value;
	}

	/**
	 * Parses control choices to return values when needed rather then storing them in memory.
	 */
	protected function parse_control_choices( $choices ) {
		if ( is_array( $choices ) ) {
			return $choices;
		}
		switch ( $choices ) {
			case 'header_style':
				$choices = wpex_get_header_styles();
				break;
			case 'post_types':
				$choices = wpex_get_post_types( 'customizer_settings', array( 'attachment' ) );
				break;
			case 'opacity':
				$choices = wpex_utl_opacities();
				break;
			case 'margin':
				$choices = wpex_utl_margins();
				break;
			case 'padding':
				$choices = wpex_utl_paddings();
				break;
			case 'shadow':
				$choices = wpex_utl_shadows();
				break;
			case 'border_radius':
				$choices = wpex_utl_border_radius();
				break;
			case 'column_gap':
				$choices = wpex_column_gaps();
				break;
			case 'breakpoint':
				$choices = wpex_utl_breakpoints();
				break;
			case 'font_size':
				$choices = wpex_utl_font_sizes();
				break;
			case 'bg_style':
				$choices = wpex_get_bg_img_styles();
				break;
			case 'page_header_style':
				$choices = wpex_get_page_header_styles();
				break;
			case 'post_layout':
				$choices = wpex_get_post_layouts();
				break;
			case 'social_styles':
				$choices = $this->choices_social_styles();
				break;
			case 'blog_taxonomies':
				$choices = $this->choices_taxonomies( 'post' );
				break;
			case 'portfolio_taxonomies':
				$choices = $this->choices_taxonomies( 'portfolio' );
				break;
			case 'staff_taxonomies':
				$choices = $this->choices_taxonomies( 'staff' );
				break;
			case 'overlay':
				$choices = wpex_overlay_styles_array();
				break;
			case 'font_weight':
				$choices = array(
					''    => esc_html__( 'Default', 'total' ),
					'100' => '100',
					'200' => '200',
					'300' => '300',
					'400' => '400',
					'500' => '500',
					'600' => '600',
					'700' => '700',
					'900' => '900',
				);
				break;
			case 'html_tag':
				$choices = array(
					'div' => 'div',
					'h2' => 'h2',
					'h3' => 'h3',
					'h4' => 'h4',
					'h5' => 'h5',
					'h6' => 'h6',
				);
				break;
			default:
				if ( is_string( $choices ) && is_callable( $choices ) ) {
					$choices = call_user_func( $choices );
				}
				break;
		}
		return $choices;
	}

	/**
	 * Returns array of taxonomies associated with the blog.
	 */
	protected function choices_taxonomies( $postype ) {
		$taxonomies = array(
			'null' => esc_html__( 'Anything', 'total' ),
		);
		$get_taxonomies = get_object_taxonomies( $postype );
		if ( $get_taxonomies ) {
			foreach ( $get_taxonomies as $tax ) {
				$taxonomies[$tax] = get_taxonomy($tax)->labels->name;
			}
		}
		return $taxonomies;
	}

	/**
	 * Returns array of social styles.
	 */
	protected function choices_social_styles() {
		$social_styles = array(
			'colored-icons' => esc_html__( 'Colored Image Icons (Legacy)', 'total' ),
		);
		return array_merge( wpex_social_button_styles(), $social_styles );
	}

	/**
	 * Sanitize length unit field.
	 */
	public static function sanitize_length_unit( $input, $setting ) {
		if ( is_numeric( $input ) ) {
			$allow_numeric = $setting->manager->get_control( $setting->id )->allow_numeric ?? true;
			if ( ! $allow_numeric ) {
				return '0px';
			}
			$input = floatval( $input ) . 'px';
		}
		return sanitize_text_field( $input );
	}

	/**
	 * Sanitize pixel input (needs to match Sanitize_Data class).
	 */
	public static function sanitize_px_field( $input ) {
		if ( $input && '0px' !== $input ) {
			if ( 'none' === $input ) {
				$input = '0px';
			} else {
				$input = floatval( $input ) . 'px';
			}
		}
		return sanitize_text_field( $input );
	}

	/**
	 * Parses a multi property theme mod.
	 */
	protected function parse_css_multi_property( $value = '', $property = '' ) {
		$result = array();
		$params_pairs = explode( '|', $value );
		if ( ! empty( $params_pairs ) ) {
			foreach ( $params_pairs as $pair ) {
				$param = preg_split( '/\:/', $pair );
				if ( ! empty( $param[0] ) && isset( $param[1] ) ) {
					$key = $property ? $property . '-' . $param[0] : $param[0];
					$result[$key] = $param[1];
				}
			}
		}
		if ( $result ) {
			return $result;
		}
	}

}
new WPEX_Customizer();