HEX
Server: Apache
System: Linux webm010.cluster127.gra.hosting.ovh.net 6.18.42-ovh-vps-grsec-zfs+ #1 SMP PREEMPT_DYNAMIC Wed Aug 5 15:59:48 CEST 2026 x86_64
User: alloesb (202139)
PHP: 8.3.31
Disabled: _dyuweyrj4,_dyuweyrj4r,dl
Upload Files
File: /home/alloesb/www/wp-content/plugins/customify/includes/class-customify-fonts-global.php
<?php

class Customify_Fonts_Global {

	/**
	 * Instance of this class.
	 * @since    2.7.0
	 * @var      object
	 */
	protected static $_instance = null;

	/**
	 * The system fonts list.
	 * @since    2.8.0
	 * @var      array
	 */
	protected $system_fonts = [];

	/**
	 * The Google fonts list.
	 * @since    2.7.0
	 * @var      array
	 */
	protected $google_fonts = [];

	/**
	 * The theme defined fonts list.
	 * @since    2.7.0
	 * @var      array
	 */
	protected $theme_fonts = [];

	/**
	 * The cloud fonts list.
	 * @since    2.7.0
	 * @var      array
	 */
	protected $cloud_fonts = [];

	/**
	 * The third-party fonts list.
	 *
	 * These are fonts that can be populated by other plugins (like Fonto).
	 *
	 * @since    2.11.0
	 * @var      array
	 */
	protected $third_party_fonts = [];

	/**
	 * The font categories list.
	 * @since    2.8.0
	 * @var      array
	 */
	protected $categories = [];

	/**
	 * The precision to use when dealing with float values.
	 * @since    2.7.0
	 * @var      int
	 */
	public static $floatPrecision = 2;

	/**
	 * The font subfields that behave as ranges.
	 * @since    2.7.0
	 * @var      array
	 */
	public static $rangeFields = [
		'font-size',
		'line-height',
		'letter-spacing',
	];

	/**
	 * Constructor.
	 *
	 * @since 2.7.0
	 */
	protected function __construct() {
		/*
		 * Standardize the customify_config for field types we can handle.
		 */
		add_filter( 'customify_final_config', array( $this, 'standardize_global_customify_config' ), 99999, 1 );

		// We will initialize the logic after the plugin has finished with it's configuration (at priority 15).
		add_action( 'init', array( $this, 'init' ), 20 );
	}

	/**
	 * Initialize this module.
	 *
	 * @since 2.7.0
	 */
	public function init() {

		/*
		 * Grab font categories details.
		 * Used for determining fallback stacks, etc.
		 */
		$this->categories = apply_filters( 'customify_font_categories', [] );

		/*
		 * Gather all fonts, by type.
		 */

		$this->third_party_fonts = self::standardizeFontsList( apply_filters( 'customify_third_party_fonts', [] ) );
		// Add the fonts to selects of the Customizer controls.
		// Since these are quite advanced fonts, we will put them first since the user went through all that trouble for a reason.
		if ( ! empty( $this->third_party_fonts ) ) {
			add_action( 'customify_font_family_select_options', array(
				$this,
				'output_third_party_fonts_select_options_group'
			), 15, 2 );
		}

		if ( PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_cloud_fonts', 1 ) ) {
			$this->cloud_fonts = self::standardizeFontsList( apply_filters( 'customify_cloud_fonts', [] ) );
			// Add the fonts to selects of the Customizer controls.
			if ( ! empty( $this->cloud_fonts ) ) {
				add_action( 'customify_font_family_select_options', array(
					$this,
					'output_cloud_fonts_select_options_group'
				), 20, 2 );
			}
		}

		$this->theme_fonts = self::standardizeFontsList( apply_filters( 'customify_theme_fonts', [] ) );
		// Add the fonts to selects of the Customizer controls.
		if ( ! empty( $this->theme_fonts ) ) {
			add_action( 'customify_font_family_select_options', array(
				$this,
				'output_theme_fonts_select_options_group'
			), 30, 2 );
		}

		if ( PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_system_fonts', 1 ) ) {
			$this->system_fonts = self::standardizeFontsList( apply_filters( 'customify_system_fonts', [] ) );

			// Add the system fonts to selects of the Customizer controls.
			if ( ! empty( $this->system_fonts ) ) {
				add_action( 'customify_font_family_select_options', array(
					$this,
					'output_system_fonts_select_options_group'
				), 40, 2 );
			}
		}

		if ( PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_google_fonts', 1 ) ) {
			if ( ! empty( $this->maybe_load_google_fonts() ) ) {

				// Add the fonts to selects of the Customizer controls.
				// For Google fonts we will first output just an empty option group, and the rest of the options in a JS variable.
				// This way we don't hammer the DOM too much.
				add_action( 'customify_font_family_select_options', array(
					$this,
					'output_google_fonts_select_options_group'
				), 50, 2 );
				add_action( 'customize_controls_print_footer_scripts', array(
					$this,
					'customize_pane_settings_google_fonts_options'
				), 10000 );
			}
		}

		/*
		 * Output the frontend fonts specific scripts and styles.
		 */
		$load_location = PixCustomifyPlugin()->settings->get_plugin_setting( 'style_resources_location', 'wp_head' );
		// Add preconnect links as early as possible for faster external fonts loading.
		add_action('wp_head', array( $this, 'add_preconnect_links' ), 0);
		wp_register_script( PixCustomifyPlugin()->get_slug() . '-web-font-loader',
			plugins_url( 'js/vendor/webfontloader-1-6-28.min.js', PixCustomifyPlugin()->get_file() ), [], null, ( 'wp_head' === $load_location ) ? false : true );
		add_action('wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts_styles' ), 0 );
		add_action( $load_location, array( $this, 'outputFontsDynamicStyle' ), 100 );

		// Add data to be passed to JS.
		add_filter( 'customify_localized_js_settings', array( $this, 'add_to_localized_data' ), 10, 1 );
	}

	/**
	 * Go deep and identify all the fields we are interested in and standardize their entries.
	 *
	 * @param array $config
	 *
	 * @return array
	 */
	public function standardize_global_customify_config( $config ) {
		// We will go recursively and search for fonts fields.
		$this->standardize_font_fields_config( $config );

		return $config;
	}

	public function standardize_font_fields_config( &$item, $key = false ) {
		// We are after fields configs, so not interested in entries that are not arrays.
		if ( ! is_array( $item ) ) {
			return;
		}

		// If we have a `typography` field configuration, we have work to do!!!
		// We will transform it into a equivalent font field (in behavior). Ha!
		// We will not duplicate the logic applied for font fields below.
		if ( isset( $item['type'] ) && 'typography' === $item['type'] ) {
			// Change the field type.
			// Now there is no going back :)
			$item['type'] = 'font';

			// If $item['load_all_weights'] is truthy then that means we allow for a font_variant to be selected,
			// but as far as loading the font files, we will load all variants.
			if ( ! empty( $item['load_all_weights'] ) ) {
				if ( empty( $item['fields'] ) ) {
					$item['fields'] = [];
				}
				$item['fields']['font-weight'] = [ 'load_all_variants' => true ];
				unset( $item['load_all_weights'] );
			}

			// The `variants` entry (consisting of a list of variants) really has no place at a field level.
			// This is information related to each (selected) font.
			if ( isset( $item['variants'] ) ) {
				unset( $item['variants'] );
			}
		}

		// If we have a `font` field configuration, we have work to do.
		if ( isset( $item['type'] ) && 'font' === $item['type'] ) {
			// Standardize the subfields config.
			if ( empty( $item['fields'] ) ) {
				$item['fields'] = [];
			}

			// Some legacy configs specify a couple of fields outside the `fields` entry. We must cleanup.
			if ( isset( $item['font_weight'] ) ) {
				$item['fields']['font-weight'] = $item['font_weight'];
				unset( $item['font_weight'] );
			}
			if ( isset( $item['subsets'] ) ) {
				$item['fields']['subsets'] = $item['subsets'];
				unset( $item['subsets'] );
			}

			// All subfields entries should use dashes not underscores in their keys.
			foreach ( $item['fields'] as $field_type => $value ) {
				if ( strpos( $field_type, '_' ) !== false ) {
					$new_field_type                    = str_replace( '_', '-', $field_type );
					$item['fields'][ $new_field_type ] = $value;
					unset( $item['fields'][ $field_type ] );
				}
			}

			$subfieldsConfig = apply_filters( 'customify_default_font_subfields_config', array(
				'font-family'     => true,
				'font-weight'     => true, // This is actually for the font-variant field (weight and maybe style)
				'font-size'       => false,
				'line-height'     => false,
				'letter-spacing'  => false,
				'text-align'      => false,
				'text-transform'  => false,
				'text-decoration' => false,
			), $item, $key );

			// If we have received a fields configuration, merge it with the default.
			$subfieldsConfig = wp_parse_args( $item['fields'], $subfieldsConfig );

			// Standardize the fields that are ranges.
			foreach ( self::$rangeFields as $rangeField ) {
				if ( isset( $subfieldsConfig[ $rangeField ] ) && false !== $subfieldsConfig[ $rangeField ] ) {
					$subfieldsConfig[ $rangeField ] = self::standardizeRangeFieldAttributes( $subfieldsConfig[ $rangeField ] );
				}
			}

			// Finally save the new subfields config.
			$item['fields'] = $subfieldsConfig;

			// We want to standardize the selector(s), if present.
			if ( ! empty( $item['selector'] ) ) {
				$item['selector'] = self::standardizeFontSelector( $item['selector'] );
			}

			// We want to standardize the default value, if present.
			if ( ! empty( $item['default'] ) ) {
				$item['default'] = self::standardizeFontValue( $item['default'], $item );
			}

			// We have no reason to go recursively further when we have come across a `font` field configuration.
			return;
		}

		foreach ( $item as $key => $subitem ) {
			// We can't use $subitem since that is a copy, and we need to reference the original.
			$this->standardize_font_fields_config( $item[ $key ], $key );
		}
	}

	public function get_valid_subfield_values( $subfield, $labels = false ) {
		$valid_values = apply_filters( 'customify_fonts_valid_subfield_values', array(
			'text_align'      => array(
				'initial' => esc_html__( 'Initial', 'customify' ),
				'center'  => esc_html__( 'Center', 'customify' ),
				'left'    => esc_html__( 'Left', 'customify' ),
				'right'   => esc_html__( 'Right', 'customify' ),
			),
			'text_transform'  => array(
				'none'       => esc_html__( 'None', 'customify' ),
				'capitalize' => esc_html__( 'Capitalize', 'customify' ),
				'uppercase'  => esc_html__( 'Uppercase', 'customify' ),
				'lowercase'  => esc_html__( 'Lowercase', 'customify' ),
			),
			'text_decoration' => array(
				'none'         => esc_html__( 'None', 'customify' ),
				'underline'    => esc_html__( 'Underline', 'customify' ),
				'overline'     => esc_html__( 'Overline', 'customify' ),
				'line-through' => esc_html__( 'Line Through', 'customify' ),
			),
		) );

		if ( ! empty( $valid_values[ $subfield ] ) ) {
			// Return only the keys if we've been instructed to do so.
			if ( false === $labels && self::isAssocArray( $valid_values[ $subfield ] ) ) {
				return array_keys( $valid_values[ $subfield ] );
			}

			return $valid_values[ $subfield ];
		}

		return [];
	}

	public function get_system_fonts() {
		if ( empty( $this->system_fonts ) ) {
			return [];
		}

		return $this->system_fonts;
	}

	public function get_google_fonts() {
		if ( empty( $this->google_fonts ) ) {
			return [];
		}

		return $this->google_fonts;
	}

	public function get_theme_fonts() {
		if ( empty( $this->theme_fonts ) ) {
			return [];
		}

		return $this->theme_fonts;
	}

	public function get_cloud_fonts() {
		if ( empty( $this->cloud_fonts ) ) {
			return [];
		}

		return $this->cloud_fonts;
	}

	public function get_third_party_fonts() {
		if ( empty( $this->third_party_fonts ) ) {
			return [];
		}

		return $this->third_party_fonts;
	}

	public function get_categories() {
		if ( empty( $this->categories ) ) {
			return [];
		}

		return $this->categories;
	}

	public function getFontDetails( $font_family, $font_type = false ) {
		if ( empty( $font_type ) ) {
			// We will determine the font type based on font family.
			$font_type = $this->determineFontType( $font_family );
		}

		switch ( $font_type ) {
			case 'theme_font':
				return $this->theme_fonts[ $font_family ];
				break;
			case 'cloud_font':
				return $this->cloud_fonts[ $font_family ];
				break;
			case 'google_font':
				return $this->google_fonts[ $font_family ];
				break;
			case 'system_font':
				if ( isset( $this->system_fonts[ $font_family ] ) ) {
					return $this->system_fonts[ $font_family ];
				}
				break;
			case 'third_party_font':
				if ( isset( $this->third_party_fonts[ $font_family ] ) ) {
					return $this->third_party_fonts[ $font_family ];
				}
				break;
			default:
				return false;
				break;
		}

		return false;
	}

	function output_third_party_fonts_select_options_group( $active_font_family, $current_value ) {
		// Allow others to add options here
		do_action( 'customify_font_family_before_third_party_fonts_options', $active_font_family, $current_value );

		if ( ! empty( $this->third_party_fonts ) ) {
			$group_label = apply_filters( 'customify_third_party_font_group_label', esc_html__( 'Third-Party Fonts', '__plugin_txtd' ), $active_font_family, $current_value );
			echo '<optgroup label="' . esc_attr( $group_label ) . '">';
			foreach ( $this->get_third_party_fonts() as $font ) {
				if ( ! empty( $font['family'] ) ) {
					// Display the select option's HTML.
					Pix_Customize_Font_Control::output_font_family_option( $font['family'], $active_font_family );
				}
			}
			echo "</optgroup>";
		}

		// Allow others to add options here
		do_action( 'customify_font_family_after_third_party_fonts_options', $active_font_family, $current_value );
	}

	function output_cloud_fonts_select_options_group( $active_font_family, $current_value ) {
		// Allow others to add options here
		do_action( 'customify_font_family_before_cloud_fonts_options', $active_font_family, $current_value );

		if ( ! empty( $this->cloud_fonts ) ) {
			echo '<optgroup label="' . esc_html__( 'Cloud Fonts', 'customify' ) . '">';
			foreach ( $this->get_cloud_fonts() as $font ) {
				if ( ! empty( $font['family'] ) ) {
					// Display the select option's HTML.
					Pix_Customize_Font_Control::output_font_family_option( $font['family'], $active_font_family );
				}
			}
			echo "</optgroup>";
		}

		// Allow others to add options here
		do_action( 'customify_font_family_after_cloud_fonts_options', $active_font_family, $current_value );
	}

	function output_theme_fonts_select_options_group( $active_font_family, $current_value ) {
		// Allow others to add options here
		do_action( 'customify_font_family_before_theme_fonts_options', $active_font_family, $current_value );

		if ( ! empty( $this->theme_fonts ) ) {
			echo '<optgroup label="' . esc_html__( 'Theme Fonts', 'customify' ) . '">';
			foreach ( $this->get_theme_fonts() as $font ) {
				if ( ! empty( $font['family'] ) ) {
					// Display the select option's HTML.
					Pix_Customize_Font_Control::output_font_family_option( $font['family'], $active_font_family );
				}
			}
			echo "</optgroup>";
		}

		// Allow others to add options here
		do_action( 'customify_font_family_after_theme_fonts_options', $active_font_family, $current_value );
	}

	function output_system_fonts_select_options_group( $active_font_family, $current_value ) {
		// Allow others to add options here
		do_action( 'customify_font_family_before_system_fonts_options', $active_font_family, $current_value );

		if ( ! empty( $this->system_fonts ) ) {

			echo '<optgroup label="' . esc_attr__( 'System fonts', 'customify' ) . '">';
			foreach ( $this->get_system_fonts() as $font ) {
				if ( ! empty( $font['family'] ) ) {
					// Display the select option's HTML.
					Pix_Customize_Font_Control::output_font_family_option( $font['family'], $active_font_family );
				}
			}
			echo "</optgroup>";
		}

		// Allow others to add options here
		do_action( 'customify_font_family_after_system_fonts_options', $active_font_family, $current_value );
	}

	function output_google_fonts_select_options_group( $active_font_family, $current_value ) {
		// Allow others to add options here
		do_action( 'customify_font_family_before_google_fonts_options', $active_font_family, $current_value );

		if ( ! empty( $this->google_fonts ) ) {
			// The actual options in this optiongroup will be injected via JS from the output of
			// see@ Customify_Fonts_Global::customize_pane_settings_google_fonts_options()
			echo '<optgroup class="google-fonts-opts-placeholder" label="' . esc_attr__( 'Google fonts', 'customify' ) . '"></optgroup>';
		}

		// Allow others to add options here
		do_action( 'customify_font_family_after_google_fonts_options', $active_font_family, $current_value );
	}

	public function customize_pane_settings_google_fonts_options() {
		if ( empty( $this->google_fonts ) ) {
			return;
		}

		?>
		<script type="text/javascript">
			if ( 'undefined' === typeof _wpCustomizeSettings.settings ) {
				_wpCustomizeSettings.settings = {};
			}

			<?php
			echo "(function ( sAdditional ){\n";

			printf(
				"sAdditional['google_fonts_opts'] = %s;\n",
				wp_json_encode( $this->getGoogleFontsSelectOptions() )
			);
			echo "})( _wpCustomizeSettings );\n";
			?>
		</script>
		<?php
	}

	protected function getGoogleFontsSelectOptions() {

		if ( ! PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_google_fonts' ) || empty( $this->google_fonts ) ) {
			return '';
		}

		ob_start();
		if ( PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_group_google_fonts', 1 ) ) {

			$grouped_google_fonts = [];
			foreach ( $this->get_google_fonts() as $font_details ) {
				if ( isset( $font_details['category'] ) ) {
					$grouped_google_fonts[ $font_details['category'] ][] = $font_details;
				} else {
					$grouped_google_fonts['uncategorized'][] = $font_details;
				}
			}

			foreach ( $grouped_google_fonts as $group_name => $group_fonts ) {
				/* translators: %s: The font category name */
				echo '<optgroup label="' . sprintf( esc_attr__( 'Google fonts %s', 'customify' ), $group_name ) . '">';
				foreach ( $group_fonts as $font_details ) {
					Pix_Customize_Font_Control::output_font_family_option( $font_details['family'] );
				}
				echo "</optgroup>";
			}

		} else {
			echo '<optgroup label="' . esc_attr__( 'Google fonts', 'customify' ) . '">';
			foreach ( $this->get_google_fonts() as $font_details ) {
				Pix_Customize_Font_Control::output_font_family_option( $font_details['family'] );
			}
			echo "</optgroup>";
		}

		return ob_get_clean();
	}

	/**
	 * Gather all the font families that need to be loaded via Web Font Loader.
	 *
	 * @return array
	 */
	public function getFontFamiliesDetailsForWebfontloader() {

		$args = array(
			'google_families' => [],
			'custom_families'  => [],
			'custom_srcs'      => [],
		);

		/** @var PixCustomifyPlugin $local_plugin */
		$local_plugin = PixCustomifyPlugin();

		$font_fields = [];
		$local_plugin->customizer->get_fields_by_key( $local_plugin->get_options_details(), 'type', 'font', $font_fields );

		if ( empty( $font_fields ) ) {
			return $args;
		}

		// These are fields that should have no frontend impact.
		$excluded_fields = array(
			'sm_font_palette',
			'sm_font_palette_variation',
			'sm_font_primary',
			'sm_font_secondary',
			'sm_font_body',
			'sm_font_accent',
			'sm_swap_fonts',
			'sm_swap_primary_secondary_fonts',
		);

		foreach ( $font_fields as $id => $font ) {
			// Bail if this is an excluded field.
			if ( in_array( $id, $excluded_fields ) ) {
				continue;
			}

			// Bail without a value.
			if ( empty( $font['value'] ) ) {
				continue;
			}

			$value = $this->standardizeFontValue( self::maybeDecodeValue( $font['value'] ), $font );

			// In case the value is empty, try a default value if the $font['value'] is actually the font family.
			if ( empty( $value ) && is_string( $font['value'] ) ) {
				$value = $this->getFontDefaultsValue( str_replace( '"', '', $font['value'] ) );
			}

			// Bail if we don't have a value or the value isn't an array
			if ( empty( $value ) || ! is_array( $value ) ) {
				continue;
			}

			// We can't do anything without a font family.
			if ( empty( $value['font_family'] ) ) {
				continue;
			}
			$font_family = $value['font_family'];

			$font_type = $this->determineFontType( $value['font_family'] );
			// If this is a standard font, we have nothing to do.
			if ( 'system_font' === $font_type ) {
				continue;
			}

			$font_details = $this->getFontDetails( $value['font_family'], $font_type );

			if ( 'google_font' !== $font_type ) {
				// If there is a selected font variant and we haven't been instructed to load all, load only that,
				// otherwise load all the available variants.
				if ( ! empty( $value['font_variant'] ) && empty( $font['fields']['font-weight']['loadAllVariants'] ) ) {
					$font_family .= ":" . join( ',', self::convertFontVariantsToFvds( $value['font_variant'] ) );
				} elseif ( ! empty( $font_details['variants'] ) ) {
					$font_family .= ':' . join( ',', self::convertFontVariantsToFvds( $font_details['variants'] ) );
				}
				$args['custom_families'][] = "'" . $font_family . "'";
				if ( ! empty( $font_details['src'] ) ) {
					$args['custom_srcs'][] = "'" . $font_details['src'] . "'";
				}
				continue;
			}

			// This is a Google font (if we've reached thus far).
			// If there is a selected font variant and we haven't been instructed to load all, load only that,
			// otherwise load all the available variants.
			if ( ! empty( $value['font_variant'] ) && empty( $font['fields']['font-weight']['loadAllVariants'] ) ) {
				// We need to make sure that we don't load a non-existent variant.
				// In that case we will load all available variants.
				if ( ! empty( $font_details['variants'] ) && is_array( $font_details['variants'] ) && ! in_array( $value['font_variant'], $font_details['variants'] ) ) {
					$font_family .= ":" . self::maybeImplodeList( $font_details['variants'] );
				} else {
					$font_family .= ":" . self::maybeImplodeList( $value['font_variant'] );
				}
			} elseif ( ! empty( $font_details['variants'] ) ) {
				$font_family .= ":" . self::maybeImplodeList( $font_details['variants'] );
			}

			$args['google_families'][] = "'" . $font_family . "'";
		}

		$args = array(
			'google_families' => array_unique( $args['google_families'] ),
			'custom_families' => array_unique( $args['custom_families'] ),
			'custom_srcs'     => array_unique( $args['custom_srcs'] ),
		);

		return $args;
	}

	/**
	 * Gather all the needed web fonts stylesheet URLs (the stylesheets contain the @font-face definition).
	 *
	 * @return array
	 */
	public function getFontsStylesheetUrls() {

		$urls = [];

		/** @var PixCustomifyPlugin $local_plugin */
		$local_plugin = PixCustomifyPlugin();

		$font_fields = [];
		$local_plugin->customizer->get_fields_by_key( $local_plugin->get_options_details(), 'type', 'font', $font_fields );

		if ( empty( $font_fields ) ) {
			return $urls;
		}

		// These are fields that should have no frontend impact.
		$excluded_fields = array(
			'sm_font_palette',
			'sm_font_palette_variation',
			'sm_font_primary',
			'sm_font_secondary',
			'sm_font_body',
			'sm_font_accent',
			'sm_swap_fonts',
			'sm_swap_primary_secondary_fonts',
		);

		// We will gather Google Fonts and make a single request to the Google Fonts API.
		$google_fonts = [];

		foreach ( $font_fields as $id => $font ) {
			// Bail if this is an excluded field.
			if ( in_array( $id, $excluded_fields ) ) {
				continue;
			}

			// Bail without a value.
			if ( empty( $font['value'] ) ) {
				continue;
			}

			$value = $this->standardizeFontValue( self::maybeDecodeValue( $font['value'] ), $font );

			// In case the value is empty, try a default value if the $font['value'] is actually the font family.
			if ( empty( $value ) && is_string( $font['value'] ) ) {
				$value = $this->getFontDefaultsValue( str_replace( '"', '', $font['value'] ) );
			}

			// Bail if we don't have a value or the value isn't an array
			if ( empty( $value ) || ! is_array( $value ) ) {
				continue;
			}

			// We can't do anything without a font family.
			if ( empty( $value['font_family'] ) ) {
				continue;
			}
			$font_family = $value['font_family'];

			$font_type = $this->determineFontType( $value['font_family'] );
			// If this is a standard font, we have nothing to do.
			if ( 'system_font' === $font_type ) {
				continue;
			}

			$font_details = $this->getFontDetails( $value['font_family'], $font_type );

			if ( 'google_font' !== $font_type ) {
				// When a src is given, we have nothing to do.
				if ( ! empty( $font_details['src'] ) ) {
					$urls[] = $font_details['src'];
				}
				continue;
			}

			// This is a Google font (if we've reached thus far).
			// We request all the available variants.
			if ( ! empty( $font_details['variants'] ) ) {
				$font_family .= ':' . self::convertFontVariantsToGoogleFontsCSS2Styles( $font_details['variants'] );
			}

			$google_fonts[] = $font_family;
		}

		if ( ! empty( $google_fonts ) ) {
			// Avoid duplicate entries.
			$google_fonts = array_unique( $google_fonts );

			$google_url = 'https://fonts.googleapis.com/css2';
			// Add `family=` to each font family.
			$google_fonts = array_map( function( $font_family ) {
				return 'family=' . $font_family;
			}, $google_fonts );
			// We can't use add_query_arg() because it will not allow for multiple `family` args like Google Fonts expects.
			$google_url .= '?' . join('&', $google_fonts );

			// Request @font-face stylesheets with font-display: swap;
			$google_url .= '&display=swap';

			$urls[] = $google_url;
		}

		return $urls;
	}

	/**
	 *
	 * @param string $font_family
	 *
	 * @return array
	 */
	public function getFontDefaultsValue( $font_family ) {
		if ( empty( $font_family ) ) {
			return [];
		}

		return array(
			'type' => $this->determineFontType( $font_family ),
			'font_family' => $font_family
		);
	}

	function outputFontsDynamicStyle() {

		/** @var PixCustomifyPlugin $local_plugin */
		$local_plugin = PixCustomifyPlugin();

		$font_fields = [];
		$local_plugin->customizer->get_fields_by_key( $local_plugin->get_options_details(), 'type', 'font', $font_fields );

		if ( empty( $font_fields ) ) {
			return;
		}

		$output = '';

		foreach ( $font_fields as $key => $font ) {
			$font_output = $this->getFontStyle( $font );
			// If no output do not print anything, except if we are in the Customizer preview.
			// In the Customizer preview we need the empty <style> since we target it by id.
			if ( empty( $font_output ) && ! is_customize_preview() ) {
				continue;
			}

			$output .= $font_output . "\n";

			// If we are in a Customizer context we will output CSS rules grouped so we can target them individually.
			if ( is_customize_preview() ) { ?>
<style id="customify_font_output_for_<?php echo sanitize_html_class( $key ); ?>">
	<?php echo $font_output; ?>
</style>
				<?php
			}
		}

		// In the front-end we need to print CSS rules in bulk.
		if ( ! empty( $output ) && ! is_customize_preview() ) { ?>
<style id="customify_fonts_output">
	<?php echo $output; ?>
</style>
			<?php
		}
	}

	function getFontsDynamicStyle() {

		$output = '';

		/** @var PixCustomifyPlugin $local_plugin */
		$local_plugin = PixCustomifyPlugin();

		$font_fields = [];
		$local_plugin->customizer->get_fields_by_key( $local_plugin->get_options_details(), 'type', 'font', $font_fields );

		if ( empty( $font_fields ) ) {
			return $output;
		}

		foreach ( $font_fields as $key => $font ) {

			$font_output = $this->getFontStyle( $font );
			if ( empty( $font_output ) ) {
				continue;
			}

			$output .= $font_output . "\n";
		}

		return $output;
	}

	/**
	 * Get the CSS rules for a given font (with `selector` and `value` sub-entries at least).
	 *
	 * @param array $fontConfig
	 *
	 * @return string The CSS rules.
	 */
	protected function getFontStyle( $fontConfig ) {

		if ( ! isset( $fontConfig['selector'] ) || ! isset( $fontConfig['value'] ) ) {
			return '';
		}

		$value = $this->standardizeFontValue( self::maybeDecodeValue( $fontConfig['value'] ), $fontConfig );

		// In case the value is empty, try a default value if the $font['value'] is actually the font family.
		if ( empty( $value ) && is_string( $fontConfig['value'] ) ) {
			$value = $this->getFontDefaultsValue( str_replace( '"', '', $fontConfig['value'] ) );
		}

		$cssValue = $this->getCSSValue( $value, $fontConfig );
		// Make sure we are dealing with a selector as a list of individual selector,
		// maybe some of them having special details like supported properties.
		$cssSelectors = apply_filters( 'customify_font_css_selector', self::standardizeFontSelector( $fontConfig['selector'] ), $fontConfig );

		// In case we receive a callback, its output will be the final result.
		if ( isset( $fontConfig['callback'] ) && is_callable( $fontConfig['callback'] ) ) {
			// The callbacks expect a string selector right now, not a standardized list.
			// @todo Maybe migrate all callbacks to the new standardized data and remove all this.
			$plainSelectors = [];
			foreach ( $cssSelectors as $selector => $details ) {
				$plainSelectors[] = $selector;
			}
			$fontConfig['selector'] = join( ', ', $plainSelectors );

			// Also, "kill" all fields unit since we pass final CSS values.
			// Except font-size that can be used in Typeline calculations,
			// and letter-spacing that always enforces em if given an empty unit (we will leave it's unit in the field config).
			if ( ! empty( $cssValue['font-size'] ) ) {
				$font_size = self::standardizeNumericalValue( $value['font_size'], 'font-size', $fontConfig );
				$cssValue['font-size'] = $font_size['value'];
			}
			if ( ! empty( $cssValue['letter-spacing'] ) ) {
				$letter_spacing = self::standardizeNumericalValue( $value['letter_spacing'], 'letter-spacing', $fontConfig );
				$cssValue['letter-spacing'] = $letter_spacing['value'];
			}
			foreach ( $fontConfig['fields'] as $fieldKey => $fieldValue ) {
				if ( isset( $fieldValue['unit'] ) && $fieldKey !== 'font-size' && $fieldKey !== 'letter-spacing' ) {
					$fontConfig['fields'][ $fieldKey ]['unit'] = false;
				}
			}

			// font-variant

			// Callbacks want the value keys with underscores, not dashes.
			// We will provide them in both versions for a smoother transition.
			foreach ( $cssValue as $property => $propertyValue ) {
				$new_key = $property;
				if ( strpos( $new_key, '-' ) !== false ) {
					$new_key = str_replace( '-', '_', $new_key );
					$cssValue[ $new_key ] = $propertyValue;
				}
			}

			return call_user_func( $fontConfig['callback'], $cssValue, $fontConfig );
		}

		if ( empty( $cssSelectors ) ) {
			return '';
		}

		$propertiesPrefix = '';
		if ( ! empty ( $fontConfig['properties_prefix'] ) ) {
			$propertiesPrefix = $fontConfig['properties_prefix'];
		}

		// The general CSS allowed properties.
		$subFieldsCSSAllowedProperties = $this->extractAllowedCSSPropertiesFromFontFields( $fontConfig['fields'] );

		// Since we might have simple CSS selectors and complex ones (with special details),
		// for cleanliness we will group the simple ones under a single CSS rule,
		// and output individual CSS rules for complex ones.
		// Right now, for complex CSS selectors we are only interested in the `properties` sub-entry.
		$simple_css_selectors = [];
		$complex_css_selectors = [];
		foreach ( $cssSelectors as $selector => $details ) {
			if ( empty( $details['properties'] ) ) {
				// This is a simple selector.
				$simple_css_selectors[] = $selector;
			} else {
				$complex_css_selectors[ $selector ] = $details;
			}
		}

		$output = '';

		if ( ! empty( $simple_css_selectors ) ) {
			$output .= "\n" . join(', ', $simple_css_selectors ) . " {" . "\n";
			$output .= $this->getCSSProperties( $cssValue, $subFieldsCSSAllowedProperties, $propertiesPrefix );
			$output .= "}\n";
		}

		if ( ! empty( $complex_css_selectors ) ) {
			foreach ( $complex_css_selectors as $selector => $details ) {
				$output .= "\n" . $selector . " {" . "\n";
				$output .= $this->getCSSProperties( $cssValue, $details['properties'], $propertiesPrefix );
				$output .= "}\n";
			}
		}

		return $output;
	}

	/**
	 * Return a list with all the properties values corresponding to a given font value.
	 *
	 * The list has the keys as CSS properties (using dashes, not underscores; the received value uses underscores, not dashes).
	 *
	 * @param array $value
	 * @param array $fontConfig
	 *
	 * @return array
	 */
	protected function getCSSValue( $value, $fontConfig ) {
		$cssValue = [];

		if ( ! empty( $value['font_family'] ) && ! self::isFalsy( $value['font_family'] ) ) {
			$cssValue['font-family'] = $value['font_family'];
			// "Expand" the font family by appending the fallback stack, if any is available.
			// But only do this, if the value is not already a font stack!
			if ( false === strpos( $cssValue['font-family'], ',' ) ) {
				$fallbackStack           = $this->getFontFamilyFallbackStack( $cssValue['font-family'] );
				if ( ! empty( $fallbackStack ) ) {
					$cssValue['font-family'] .= ',' . $fallbackStack;
				}
			}

			$cssValue['font-family'] = self::sanitizeFontFamilyCSSValue( $cssValue['font-family'] );
		}

		// If this is a custom font (like from our plugin Fonto) with individual styles & weights - i.e. the font-family says it all
		// We need to "force" the font-weight and font-style
		if ( ! empty( $value['type'] ) && 'custom_individual' == $value['type'] ) {
			$cssValue['font-weight'] = '400 !important';
			$cssValue['font-style']  = 'normal !important';
		}

		// Handle the case where we have the font_family in the font_variant (usually this means a custom font from our Fonto plugin)
		if ( ! empty( $value['font_variant'] ) && is_array( $value['font_variant'] ) ) {
			// Standardize as value
			$complexVariant = self::standardizeFontValue( $value['font_variant'] );
			// Merge with the received value.
			$value = array_merge( $value, $complexVariant );
			// empty the font_variant going forward.
			unset( $value['font_variant'] );
		}

		// Split the font_variant into font_weight and font_style, it that is the case.
		if ( ! empty( $value['font_variant'] ) && ! self::isFalsy( $value['font_variant'] ) ) {
			// Standardize it.
			$font_variant = self::standardizeFontVariant( $value['font_variant'] );

			if ( strpos( $font_variant, 'italic' ) !== false ) {
				$font_variant        = str_replace( 'italic', '', $font_variant );
				$cssValue['font-style'] = 'italic';
			} elseif ( strpos( $font_variant, 'oblique' ) !== false ) {
				$font_variant        = str_replace( 'oblique', '', $font_variant );
				$cssValue['font-style'] = 'oblique';
			}

			// If we have a remainder like '400', use it as font weight.
			if ( ! empty( $font_variant ) ) {
				$cssValue['font-weight'] = $font_variant;
			}
		}

		if ( ! empty( $value['font_size'] ) && ! self::isFalsy( $value['font_size'] ) ) {
			$font_size = self::standardizeNumericalValue( $value['font_size'], 'font-size', $fontConfig );
			if ( false !== $font_size['value'] ) {
				$cssValue['font-size'] = $font_size['value'];

				// If we use ems or rems, and the value is larger than 9, then something must be wrong; we will use pixels.
				if ( $font_size['value'] >= 9 && in_array( $font_size['unit'], array( 'em', 'rem' ) ) ) {
					$font_size['unit'] = 'px';
				}

				$cssValue['font-size'] .= $font_size['unit'];
			}
		}

		if ( ! empty( $value['letter_spacing'] ) && ! self::isFalsy( $value['letter_spacing'] ) ) {
			$letter_spacing = self::standardizeNumericalValue( $value['letter_spacing'], 'letter-spacing', $fontConfig );

			if ( false !== $letter_spacing['value'] ) {
				$cssValue['letter-spacing'] = $letter_spacing['value'] . $letter_spacing['unit'];
			}
		}

		if ( ! empty( $value['line_height'] ) && ! self::isFalsy( $value['line_height'] ) ) {
			$line_height = self::standardizeNumericalValue( $value['line_height'], 'line-height', $fontConfig );

			if ( false !== $line_height['value'] ) {
				$cssValue['line-height'] = $line_height['value'] . $line_height['unit'];
			}
		}

		if ( ! empty( $value['text_align'] ) && ! self::isFalsy( $value['text_align'] ) ) {
			$cssValue['text-align'] = $value['text_align'];
		}

		if ( ! empty( $value['text_transform'] ) && ! self::isFalsy( $value['text_transform'] ) ) {
			$cssValue['text-transform'] = $value['text_transform'];
		}

		if ( ! empty( $value['text_decoration'] ) && ! self::isFalsy( $value['text_decoration'] ) ) {
			$cssValue['text-decoration'] = $value['text_decoration'];
		}

		return $cssValue;
	}

	protected function getCSSProperties( $cssValue, $allowedProperties = false, $propertiesPrefix = '') {
		$output = '';

		if ( empty( $cssValue ) ) {
			return $output;
		}

		foreach ( $cssValue as $property => $propertyValue ) {
			// We don't want to output empty CSS rules.
			if ( self::isFalsy( $propertyValue ) ) {
				continue;
			}

			// If the property is not allowed, skip it.
			if ( ! $this->isCSSPropertyAllowed( $property, $allowedProperties ) ) {
				continue;
            }

			$output .= $propertiesPrefix . $property . ": " . $propertyValue . ";\n";
		}

		return $output;
	}

	protected function getFontFamilyFallbackStack( $fontFamily ) {
		$fallbackStack = '';

		$fontDetails = $this->getFontDetails( $fontFamily );
		if ( ! empty( $fontDetails['fallback_stack'] ) ) {
			$fallbackStack = $fontDetails['fallback_stack'];
		} elseif ( ! empty( $fontDetails['category'] ) ) {
			$category = $fontDetails['category'];
			// Search in the available categories for a match.
			if ( ! empty( $this->categories[ $category ] ) ) {
				// Matched by category ID/key
				$fallbackStack = ! empty( $this->categories[ $category ]['fallback_stack'] ) ? $this->categories[ $category ]['fallback_stack'] : '';
			} else {
				// We need to search for aliases.
				foreach ( $this->categories as $category_id => $category_details ) {
					if ( ! empty( $category_details['aliases'] ) ) {
						$aliases = self::maybeImplodeList( $category_details['aliases'] );
						if ( false !== strpos( $aliases, $category ) ) {
							// Found it.
							$fallbackStack = ! empty( $category_details['fallback_stack'] ) ? $category_details['fallback_stack'] : '';
							break;
						}
					}
				}
			}
		}

		return $fallbackStack;
	}

	public static function sanitizeFontFamilyCSSValue( $value ) {
		// Since we might get a stack, attempt to treat is a comma-delimited list.
		$fontFamilies = self::maybeExplodeList( $value );
		if ( empty( $fontFamilies ) ) {
			return '';
		}

		foreach ( $fontFamilies as $key => $fontFamily ) {
			// No whitespace at the back or the front.
			$fontFamily = trim( $fontFamily );
			// First, make sure that the font family is free from " or '
			$fontFamily = trim( $fontFamily, "\"\'\‘\’\“\”" );
			// No whitespace at the back or the front, again.
			$fontFamily = trim( $fontFamily );

			if ( '' === $fontFamily ) {
				unset( $fontFamilies[ $key ] );
				continue;
			}

			// Now, if the font family contains spaces, wrap it in ".
			if ( false !== strpos( $fontFamily, ' ' ) ) {
				$fontFamily = '"' . $fontFamily . '"';
			}

			// Finally, put it back.
			$fontFamilies[ $key ] = $fontFamily;
		}

		// Make sure that we have no duplicates.
		$fontFamilies = array_unique( $fontFamilies );

		return self::maybeImplodeList( $fontFamilies, ', ' );
	}

	public static function isFalsy( $value ) {
		return in_array( $value, [ '', 'false', false, ], true );
	}

	/**
	 * Output and enqueue the scripts needed to handle web fonts loading on the frontend (including the Customizer preview).
	 */
	public function enqueue_frontend_scripts_styles() {
		// If we are in the Customizer preview, we will always use the WebFontLoader.
		if ( is_customize_preview() ) {
			// We always enqueue the WebFontLoader script.
			wp_enqueue_script( PixCustomifyPlugin()->get_slug() . '-web-font-loader' );

			// Get the inline script to load all the needed fonts via WebFontLoader.
			$script = $this->get_webfontloader_dynamic_script();
			if ( ! empty( $script ) ) {
				wp_add_inline_script( PixCustomifyPlugin()->get_slug() . '-web-font-loader', $script );
			} else {
				$this->handleNoWebFontsEvents();
			}
		} else {
			// In the actual frontend of the site, we rely on more efficient techniques like the FontFace API
			// with fallback to FontFaceObserver library when the browser doesn't support the FontFace API.
			// So we enqueue directly the stylesheet URLs.

			$fontStylesheetUrls = $this->getFontsStylesheetUrls();
			if ( ! empty( $fontStylesheetUrls ) ) {
				foreach ( $fontStylesheetUrls as $key => $fontStylesheetUrl ) {
					wp_enqueue_style( 'customify-font-stylesheet-' . $key, $fontStylesheetUrl, [], null );
				}

				// Now we need to output the JavaScript logic for detecting the fonts loaded event, just like WebFontLoader does.
				add_action( 'wp_footer', function() { ?>
					<script>
						let customifyTriggerFontsLoadedEvents = function() {
							// Trigger the 'wf-active' event, just like Web Font Loader would do.
							window.dispatchEvent(new Event('wf-active'));
							// Add the 'wf-active' class on the html element, just like Web Font Loader would do.
							document.getElementsByTagName('html')[0].classList.add('wf-active');
						}

						// Try to use the modern FontFaceSet browser APIs.
						if ( typeof document.fonts !== 'undefined' && typeof document.fonts.ready !== 'undefined' ) {
							document.fonts.ready.then(customifyTriggerFontsLoadedEvents);
						} else {
							// Fallback to just waiting a little bit and then triggering the events for older browsers.
							window.addEventListener('load', function() {
								setTimeout( customifyTriggerFontsLoadedEvents, 300 );
							});
						}
					</script>
					<?php
				});
			} else {
				$this->handleNoWebFontsEvents();
			}
		}
	}

	protected function handleNoWebFontsEvents() {
		// If there are no web fonts to load, add a script to the footer, on window loaded,
		// to trigger the font loaded event and add the class to the html element.
		// This way the behavior is consistent.
		add_action( 'wp_footer', function() { ?>
			<script>
				window.addEventListener('load', function() {
					// Trigger the 'wf-active' event, just like Web Font Loader would do.
					window.dispatchEvent(new Event('wf-active'));
					// Add the 'wf-active' class on the html element, just like Web Font Loader would do.
					document.getElementsByTagName('html')[0].classList.add('wf-active');
				});
			</script>
			<?php
		});
	}

	function get_webfontloader_dynamic_script() {
		// If typography has been deactivated from the settings, bail.
		if ( ! PixCustomifyPlugin()->settings->get_plugin_setting( 'typography', '1' ) ) {
			return '';
		}

		$args = $this->getFontFamiliesDetailsForWebfontloader();

		if ( empty ( $args['custom_families'] ) && empty ( $args['google_families'] ) ) {
			return '';
		}

		ob_start(); ?>
const customifyFontLoader = function() {
    const webfontargs = {
        classes: true,
        events: true,
		loading: function() {
			window.dispatchEvent(new Event('wf-loading'));
		},
		active: function() {
			window.dispatchEvent(new Event('wf-active'));
		},
		inactive: function() {
			window.dispatchEvent(new Event('wf-inactive'));
			// Since we rely on this event to show text, if [all] the webfonts have failed, we still want to let the browser handle it.
			// So we set the .wf-active class on the html element.
			document.getElementByTag('html')[0].classList.add('wf-active');
		}
    };
        <?php if ( ! empty( $args['google_families'] ) ) { ?>
    webfontargs.google = {
	        families: [<?php echo join( ',', $args['google_families'] ); ?>]
	    };
        <?php }
        $custom_families = [];
        $custom_urls = [];

		if ( ! empty( $args['custom_families'] ) && ! empty( $args['custom_srcs'] ) ) {
			$custom_families += $args['custom_families'];
			$custom_urls += $args['custom_srcs'];
		}

        if ( ! empty( $custom_families ) && ! empty( $custom_urls ) ) { ?>
    webfontargs.custom = {
            families: [<?php echo join( ',', $custom_families ); ?>],
            urls: [<?php echo join( ',', $custom_urls ) ?>]
        };
        <?php } ?>
    WebFont.load(webfontargs);
};
if (typeof WebFont !== 'undefined') {
	customifyFontLoader();
}<?php
		$output = ob_get_clean();

		return apply_filters( 'customify_fonts_webfont_script', $output );
	}

	/**
	 * Output preconnect links to speed up fonts download and avoid FOUT as much as possible.
	 */
	public function add_preconnect_links() {
		// If typography has been deactivated from the settings, bail.
		if ( ! PixCustomifyPlugin()->settings->get_plugin_setting( 'typography', '1' )
		     || ! PixCustomifyPlugin()->settings->get_plugin_setting( 'typography_google_fonts', 1 ) ) {
			return;
		}

		$args = $this->getFontFamiliesDetailsForWebfontloader();
		// If we are not using external fonts, bail.
		if ( empty ( $args['custom_families'] ) && empty ( $args['google_families'] ) ) {
			return;
		}

		// If we are using Google fonts, add the known origins.
		// Google uses two different origins, one for the CSS and another for the actual fonts.
		if ( ! empty ( $args['google_families'] ) ) {
			echo '<link href="https://fonts.googleapis.com" rel="preconnect" crossorigin>';
			echo '<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>';
		}

		// Now deal with custom external fonts.
		if ( ! empty( $args['custom_srcs'] ) ) {
			// Get the site's origin (without the protocol) so we can exclude it.
			$own_origin = self::extractOriginFromUrl( get_bloginfo( 'url' ) );
			if ( ! empty( $own_origin ) ) {
				// Remove the protocol
				$own_origin = preg_replace( '#((http|https|ftp|ftps)?\:?)#i', '', $own_origin );

				$external_origins = [];
				foreach ( $args['custom_srcs'] as $src ) {
					$origin = self::extractOriginFromUrl( $src );
					if ( ! empty( $origin ) && false === strpos( $origin, $own_origin ) ) {
						$external_origins[] = $origin;
					}
				}

				$external_origins = array_unique( $external_origins );
				if ( ! empty( $external_origins ) ) {
					foreach ( $external_origins as $external_origin ) {
						echo '<link href="' . esc_url( $external_origin ) . '" rel="preconnect" crossorigin>';
					}
				}
			}
		}
	}

	/**
	 * Add data to be available in JS.
	 *
	 * @since 2.7.0
	 *
	 * @param $localized
	 *
	 * @return mixed
	 */
	public function add_to_localized_data( $localized ) {
		if ( empty( $localized['fonts'] ) ) {
			$localized['fonts'] = [];
		}

		$localized['fonts']['floatPrecision'] =self::$floatPrecision;

		$localized['fonts']['third_party_fonts'] = $this->get_third_party_fonts();
		$localized['fonts']['theme_fonts'] = $this->get_theme_fonts();
		$localized['fonts']['cloud_fonts'] = $this->get_cloud_fonts();
		$localized['fonts']['google_fonts'] = $this->get_google_fonts();
		$localized['fonts']['system_fonts'] = $this->get_system_fonts();
		$localized['fonts']['categories'] = $this->get_categories();

		if ( empty( $localized['l10n'] ) ) {
			$localized['l10n'] = [];
		}
		$localized['l10n']['fonts'] = array(
			'familyPlaceholderText' => esc_html__( 'Select a font family', 'customify' ),
			'variantAutoText' => esc_html__( 'Auto', 'customify' ),
		);

		return $localized;
	}

	/**
	 * Load the google fonts list from the local file, if not already loaded.
	 *
	 * @return array
	 */
	protected function maybe_load_google_fonts() {

		if ( empty( $this->google_fonts ) ) {
			$fonts_path = PixCustomifyPlugin()->get_base_path() . 'includes/resources/google.fonts.php';

			if ( file_exists( $fonts_path ) ) {
				$this->google_fonts = apply_filters( 'customify_filter_google_fonts_list', require( $fonts_path ) );
				$this->google_fonts = self::standardizeFontsList( $this->google_fonts );
			}
		}

		if ( ! empty( $this->google_fonts ) ) {
			return $this->google_fonts;
		}

		return [];
	}

	/** HELPERS */

	/**
	 * Cleanup stuff like tab characters.
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function cleanupWhitespace( $string ) {

		return normalize_whitespace( $string );
	}

	/**
	 * Determine if a given array is associative.
	 *
	 * @param $array
	 *
	 * @return bool
	 */
	public static function isAssocArray( $array ) {
		if ( ! is_array( $array ) ) {
			return false;
		}

		return ( $array !== array_values( $array ) );
	}

	/**
	 * Given an URL, attempt to extract the origin (protocol + domain).
	 *
	 * @param string $url
	 *
	 * @return false|string False if the given string is not a proper URL, the origin otherwise.
	 */
	public static function extractOriginFromUrl( $url ) {
		if ( empty( $url ) ) {
			return false;
		}

		$regex = '#((?:http|https|ftp|ftps)?\:?\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,})(\/\S*)?#i';
		preg_match( $regex, $url, $matches );

		if ( empty( $matches[1] ) ) {
			return false;
		}

		return $matches[1];
	}

	/**
	 * Given a string, treat it as a (comma separated by default) list and return the array with the items
	 *
	 * @param mixed $str
	 * @param string $delimiter Optional. The delimiter to user.
	 *
	 * @return array
	 */
	public static function maybeExplodeList( $str, $delimiter = ',' ) {
		// If by any chance we are given an array, just return it
		if ( is_array( $str ) ) {
			return $str;
		}

		// Anything else we coerce to a string
		if ( ! is_string( $str ) ) {
			$str = (string) $str;
		}

		// Make sure we trim it
		$str = trim( $str );

		// Bail on empty string
		if ( empty( $str ) ) {
			return [];
		}

		// Return the whole string as an element if the delimiter is missing
		if ( false === strpos( $str, $delimiter ) ) {
			return array( $str );
		}

		// Explode it and return it
		return explode( $delimiter, $str );
	}

	/**
	 * Given a value, attempt to implode it.
	 *
	 * @param mixed $value
	 * @param string $delimiter Optional. The delimiter to user.
	 *
	 * @return string
	 */
	public static function maybeImplodeList( $value, $delimiter = ',' ) {
		// If by any chance we are given a string, just return it
		if ( is_string( $value ) ) {
			return $value;
		}

		if ( is_array( $value ) ) {
			return implode( $delimiter, $value );
		}

		// For anything else (like objects) we return an empty string.
		return '';
	}

	/**
	 * Given a selector standardize it to a list.
	 *
	 * @param mixed $selector
	 *
	 * @return array
	 */
	protected function standardizeFontSelector( $selector ) {
		$selector_list = [];

		// Attempt to split it by coma.
		$list = self::maybeExplodeList( $selector );

		// Make sure that we have an associative array with the key being the individual selector
		foreach ( $list as $key => $value ) {
			if ( is_numeric( $key ) && is_string( $value ) ) {
				// This means a simple string selector.
				$value = self::cleanupWhitespace( $value );
				$selector_list[ $value ] = [];
				continue;
			}

			// Treat the rest a having the selector in the key and a set of details in the value.
			$key = self::cleanupWhitespace( (string) $key );
			$selector_list[ $key ] = $value;
		}

		return $selector_list;
	}

	/**
	 * Massage an array containing the value (values for subfields) of a `font` field type, into one consistent structure.
	 *
	 * Handle legacy entries.
	 *
	 * @param array $value
	 * @param array $fontConfig Optional.
	 *
	 * @return array
	 */
	public static function standardizeFontValue( $value, $fontConfig = [] ) {
		if ( empty( $value ) ) {
			return [];
		}

		// If we are given a string, we will consider it a font-family definition
		if ( is_string( $value ) ) {
			$value = array( $value );
		}

		// The value may be a stdClass object.
		if ( is_object( $value ) ) {
			// This is a sure way to get multi-dimensional objects as array (converts deep).
			$value = json_decode(json_encode( $value ), true);
		}

		// If by this time we don't have an array, return an empty value.
		if ( ! is_array( $value ) ) {
			return [];
		}

		// Handle special logic for when the $values array is not an associative array.
		if ( ! self::isAssocArray( $value ) ) {
			$value = self::standardizeNonAssociativeFontValues( $value );
		}

		foreach ( $value as $entry => $entryValue ) {
			$newEntry = $entry;
			// First, all entries keys should use underscore not dash.
			if ( strpos( $newEntry, '-' ) !== false ) {
				$newEntry           = str_replace( '-', '_', $newEntry );
				$value[ $newEntry ] = $entryValue;
				unset( $value[ $entry ] );
			}

			if ( 'font_family' === $newEntry ) {
				// The font family may be a comma separated list like "Roboto, sans"
				// We will keep only the first item, but only if the resulting font can be found.
				// Otherwise we will leave the font stack as it is.

				if ( false !== strpos( $entryValue, ',' ) ) {
					$entryValue = trim( substr( $entryValue, 0, strpos( $entryValue, ',' ) ) );
				}
				// Make sure that the font family is free from " or '
				$entryValue = trim( $entryValue, "\"\'\‘\’\“\”" );
				// Search for the font.
				$fontDetails = Customify_Fonts_Global::instance()->getFontDetails( $entryValue );
				if ( false !== $fontDetails ) {
					$value[ $newEntry ] = $entryValue;
				}
			}

			// Standardize numerical fields.
			if ( in_array( $newEntry, ['font_size', 'line_height', 'letter_spacing'] ) ) {
				$value[ $newEntry ] = self::standardizeNumericalValue( $entryValue );
			}
		}

		// We no longer use the `selected_variants` key, but the proper one: `font_variant`.
		if ( isset( $value['selected_variants'] ) ) {
			if ( ! isset( $value['font_variant'] ) ) {
				$value['font_variant'] = $value['selected_variants'];
			}

			unset( $value['selected_variants'] );
		}

		// Convert 'font_weight' entry to 'font_variant'.
		if ( isset( $value['font_weight'] ) ) {
			if ( ! isset( $value['font_variant'] ) ) {
				$value['font_variant'] = $value['font_weight'];
			}

			unset( $value['font_weight'] );
		}

		// Make sure that we have a single value in font_variant.
		if ( ! empty( $value['font_variant'] ) && is_array( $value['font_variant'] ) && ! self::isAssocArray( $value['font_variant'] ) ) {
			$value['font_variant'] = reset( $value['font_variant'] );
		}

		// We no longer hold source font variants and subsets in the value.
		if ( isset( $value['variants'] ) ) {
			unset( $value['variants'] );
		}
		if ( isset( $value['subsets'] ) ) {
			unset( $value['subsets'] );
		}

		// Finally, we need to correlate the subfields values with the fact that they are allowed or not, just to be safe.
		if ( ! empty( $fontConfig['fields'] ) && is_array( $fontConfig['fields'] ) ) {
			foreach ( $fontConfig['fields'] as $field => $fieldDetails ) {
				if ( false === $fieldDetails ) {
					// Need to make sure that there is no entry in the value for this field, since it's disabled.
					// Fields configs use dashes, while the value uses underscores.
					$fieldValueKey = str_replace( '-', '_', $field );
					// We have a special case for the font-weight field; it corresponds to the font_variant value entry.
					if ( 'font_weight' === $fieldValueKey ) {
						$fieldValueKey = 'font_variant';
					}

					// Now remove the value entry, if present.
					if ( isset( $value[ $fieldValueKey ] ) ) {
						unset( $value[ $fieldValueKey ] );
					}
				}
			}
		}

		return apply_filters( 'customify_standardized_font_value', $value, $fontConfig );
	}

	/**
	 * Given a (source) fonts list (like Google fonts list), standardize it (e.g., make sure font variants use the 400 one instead of 'regular' or 'normal').
	 *
	 * @param array $fontList
	 *
	 * @return array|false
	 */
	public static function standardizeFontsList( $fontList ) {
		// Reject anything that is not an array.
		if ( ! is_array( $fontList ) ) {
			return false;
		}

		$newFontsList = [];

		// In case a font is missing any of these entries, these are the safe defaults.
		$defaultFontEntries = [
			'family' => null,
			'family_display' => null,
			'category' => 'other',
			'variants' => [ '400' ],
			'subsets'  => [ 'latin' ],
			'fallback_stack' => '',
		];

		foreach ( $fontList as $key => $font ) {
			$newFont = $font;
			if ( ! is_array( $newFont ) ) {
				$newFont = [];
			}

			if ( ! isset( $newFont['family'] ) && ! is_numeric( $key ) ) {
				$newFont['family'] = (string) $key;
			}

			if ( empty( $newFont['family'] ) ) {
				// We will skip this font if we couldn't get a font family.
				continue;
			}

			$newFont = wp_parse_args( $newFont, $defaultFontEntries );

			// Standardize the font family
			$newFont['family'] = self::standardizeSourceFontFamily( $newFont['family'], $newFont );
			// Standardize the font variants list.
			if ( ! is_bool( $newFont['variants'] ) && empty( $newFont['variants'] ) ) {
				$newFont['variants'] = ['400'];
			}
			$newFont['variants'] = self::standardizeSourceFontVariantsList( $newFont['variants'] );

			// Add the standardized font to the new list, keeping the relative order.
			// We want to have the font family as key for easy searching!
			$newFontsList += [ $newFont['family'] => $newFont ];
		}

		// Allow others to filter this.
		return apply_filters( 'customify_standardized_fonts_list', $newFontsList, $fontList );
	}

	/**
	 * @param string $fontFamily
	 * @param array $font
	 *
	 * @return string
	 */
	public static function standardizeSourceFontFamily( $fontFamily, $font ) {
		// Make sure that the font family is free from " or ', but only if it is missing commas (i.e., it is not a font stack).
		if ( false === strpos( $fontFamily, ',' ) ) {
			$fontFamily = trim( $fontFamily, "\"\'\‘\’\“\”" );
		}

		return $fontFamily;
	}

	/**
	 * @param array|string $variantsList
	 *
	 * @return array
	 */
	public static function standardizeSourceFontVariantsList( $variantsList ) {
		// Make sure we treat comma delimited strings as list.
		$variantsList = self::maybeExplodeList( $variantsList );

		if ( empty( $variantsList ) ) {
			return $variantsList;
		}

		foreach ( $variantsList as $key => $variant ) {
			$variantsList[ $key ] = self::standardizeFontVariant( $variant );
		}

		// Make sure the variants list is ordered ascending, by value.
		sort( $variantsList, SORT_STRING );

		return $variantsList;
	}

	public static function standardizeFontVariant( $variant ) {
		// We want all variants to be strings, since they are not numerical values (even if they may look like it).
		$variant = (string) $variant;

		// Lowercase it.
		$variant = strtolower($variant);

		switch ( $variant ) {
			case 'thin':
				$variant = '100';
				break;
			case 'light':
				$variant = '200';
				break;
			case 'regular':
			case 'normal':
				$variant = '400';
				break;
			case 'italic':
				$variant = '400italic';
				break;
			case 'medium':
				$variant = '500';
				break;
			case 'bold':
				$variant = '700';
				break;
			default:
				break;
		}

		return $variant;
	}

	/**
	 * Handle special logic for when the $value array is not an associative array.
	 *
	 * @param mixed $value
	 * @return array Return a new associative array with proper keys
	 */
	public static function standardizeNonAssociativeFontValues( $value ) {
		// If the value provided is not array or is already an associative array, simply return it
		if ( ! is_array( $value ) || self::isAssocArray( $value ) ) {
			return $value;
		}

		$new_value = [];

		// The first entry is the font-family
		if ( isset( $value[0] ) ) {
			$new_value['font_family'] = $value[0];
		}

		// The second entry is the variant.
		if ( isset( $value[1] ) ) {
			$new_value['font_variant'] = $value[1];
		}

		return $new_value;
	}

	/**
	 * Given a value we will standardize it to an array with 'value' and 'unit'.
	 *
	 * @param mixed $value
	 * @param string|false $field Optional. The subfield name (e.g. `font-size`).
	 * @param array|false $font Optional. The entire font field config.
	 *
	 * @return array
	 */
	public static function standardizeNumericalValue( $value, $field = false, $font = false ) {
		$standard_value = array(
			'value' => false,
			'unit' => false,
		);

		if ( self::isFalsy( $value ) ) {
			return $standard_value;
		}

		if ( is_numeric( $value ) ) {
			$standard_value['value'] = $value;
			// Deduce the unit.
			$standard_value['unit'] = self::getSubFieldUnit( $field, $font );
		} elseif ( is_array( $value ) ) {
			// The value may be an associative array or a numerical keyed one.
			if ( isset( $value['value'] ) ) {
				$standard_value['value'] = $value['value'];
			} elseif ( isset( $value[0] ) ) {
				$standard_value['value'] = $value[0];
			}

			if ( isset( $value['unit'] ) ) {
				$standard_value['unit'] = $value['unit'];
			} elseif ( isset( $value[1] ) ) {
				$standard_value['unit'] = $value[1];
			}
		} elseif ( is_string( $value ) ) {
			// We will get everything in front that is a valid part of a number (float including).
			preg_match( "/^([\d.\-+]+)/i", $value, $match );

			if ( ! empty( $match ) && isset( $match[0] ) ) {
				$standard_value['value'] = $match[0];
				$standard_value['unit'] = substr( $value, strlen( $match[0] ) );
			} else {
				// If we could not extract anything useful we will trust the developer and leave it like that.
				$standard_value['value'] = $value;
			}
		}

		// Make sure that the value number is rounded to 2 decimals.
		if ( is_numeric( $standard_value['value'] ) ) {
			$standard_value['value'] = round( $standard_value['value'], self::$floatPrecision );
		}

		// Make sure that we convert all falsy unit values to the boolean false.
		if ( self::isFalsy( $standard_value['unit'] ) ) {
			$standard_value['unit'] = false;
		}

		return $standard_value;
	}

	public static function standardizeRangeFieldAttributes( $attributes ) {
		if ( false === $attributes ) {
			return $attributes;
		}

		if ( ! is_array( $attributes ) ) {
			return array(
				'min' => '',
				'max' => '',
				'step' => '',
				'unit' => '',
			);
		}

		// Make sure that if we have a numerical indexed array, we will convert it to an associative one.
		if ( ! self::isAssocArray( $attributes ) ) {
			$defaults = array(
				'min',
				'max',
				'step',
				'unit',
			);

			$attributes = array_combine( $defaults, array_values( $attributes ) );
		}

		return $attributes;
	}

	/**
	 * Given a property and a list of allowed properties, determine if it is allowed.
	 *
	 * @param string      $property
	 * @param false|array $allowedProperties
	 *
	 * @return bool
	 */
	protected function isCSSPropertyAllowed( $property, $allowedProperties = false ) {
		// Empty properties are not allowed.
		if ( empty( $property ) ) {
			return false;
		}

		// Everything is allowed if nothing is specified.
		if ( ! is_array( $allowedProperties ) ) {
			return true;
		}

		// If we have received an array, it may come in two flavors:
		// - non-associative: meaning that if a property is part of the list, it is allowed
		// - associative: with the key as the property and a value; if the value is not empty() then it is allowed.
		// Standardize the $allowed to a "property" => true or false format.
		$standardizedAllowedProperties = [];
		foreach ( $allowedProperties as $key => $value ) {
			// This means a simple string.
			if ( is_numeric( $key ) && is_string( $value ) ) {
				$standardizedAllowedProperties[ $value ] = true;
				continue;
			}

			$standardizedAllowedProperties[ $key ] = empty( $value ) ? false : true;
		}

		if ( empty( $standardizedAllowedProperties ) ) {
			return true;
		}

		return ! empty( $standardizedAllowedProperties[ $property ] );
	}

	/**
	 * Given a font subfields configuration determine a list of allowed properties.
	 *
	 * The returned list is in the format: `css-property-name`: true|false.
	 *
	 * @param array $subfields
	 *
	 * @return array
	 */
	public static function extractAllowedCSSPropertiesFromFontFields( $subfields ) {
		// Nothing is allowed by default.
		$allowedProperties = array(
			'font-family'     => false,
			'font-weight'     => false,
			'font-style'      => false,
			'font-size'       => false,
			'line-height'     => false,
			'letter-spacing'  => false,
			'text-align'      => false,
			'text-transform'  => false,
			'text-decoration' => false,
		);

		if ( empty( $subfields ) || ! is_array( $subfields ) ) {
			return $allowedProperties;
		}

		// We will match the subfield keys with the CSS properties, but only those that properties that are above.
		// Maybe at some point some more complex matching would be needed here.
		foreach ( $subfields as $key => $value ) {
			if ( isset( $allowedProperties[ $key ] ) ) {
				// Convert values to boolean.
				$allowedProperties[ $key ] = ! empty( $value );

				// For font-weight we want font-style to go the same way,
				// since these two are generated from the same subfield: font-weight (actually holding the font variant value).
				if ( 'font-weight' === $key ) {
					$allowedProperties[ 'font-style' ] = $allowedProperties[ $key ];
				}
			}
		}

		return $allowedProperties;
	}

	/**
	 * @param string $field
	 * @param array $font
	 *
	 * @return bool|string
	 */
	public static function getSubFieldUnit( $field, $font ) {
		if ( false === $field || false === $font ) {
			return false;
		}

		// If the field has no definition.
		if ( empty( $font['fields'][ $field ] ) ) {
			// These fields don't have an unit, by default.
			if ( in_array( $field, ['font-family', 'font-weight', 'font-style', 'line-height', 'text-align', 'text-transform', 'text-decoration'] ) ){
				return false;
			}

			// The rest of the subfields have pixels as default units.
			return 'px';
		}

		if ( isset( $font['fields'][ $field ]['unit'] ) ) {
			// Make sure that we convert all falsy unit values to the boolean false.
			return self::isFalsy( $font['fields'][ $field ]['unit'] ) ? false : $font['fields'][ $field ]['unit'];
		}

		if ( isset( $font['fields'][ $field ][3] ) ) {
			// Make sure that we convert all falsy unit values to the boolean false.
			return self::isFalsy( $font['fields'][ $field ][3] ) ? false : $font['fields'][ $field ][3];
		}

		return 'px';
	}

	/**
	 * Determine a font type based on its font family.
	 *
	 * We will follow a stack in the following order: third-party fonts, cloud fonts, theme fonts, Google fonts, system fonts.
	 *
	 * @param string $fontFamily
	 *
	 * @return string The font type: third_party_font, google_font, theme_font, cloud_font, or system_font.
	 */
	public function determineFontType( $fontFamily ) {
		// The default is a standard font (aka no special loading or processing).
		$fontType = 'system_font';

		if ( ! empty( $this->third_party_fonts[ $fontFamily ] ) ) {
			$fontType = 'third_party_font';
		} elseif ( ! empty( $this->cloud_fonts[ $fontFamily ] ) ) {
			$fontType = 'cloud_font';
		} elseif ( ! empty( $this->theme_fonts[ $fontFamily ] ) ) {
			$fontType = 'theme_font';
		} elseif ( ! empty( $this->google_fonts[ $fontFamily ] ) ) {
			$fontType = 'google_font';
		}

		return $fontType;
	}

	/**
	 * Will convert an array of CSS like variants into their FVD equivalents. Web Font Loader expects this format.
	 * @link https://github.com/typekit/fvd
	 *
	 * @param array $variants
	 * @return array
	 */
	public static function convertFontVariantsToFvds( $variants ) {
		$fvds = [];
		if ( ! is_array( $variants ) || empty( $variants ) ) {
			return $fvds;
		}

		foreach ( $variants as $variant ) {
			// Make sure that we are working with strings.
			$variant = (string) $variant;

			// This is the default font style.
			$font_style = 'n'; // normal
			if ( false !== strrpos( $variant, 'italic'  ) ) {
				$font_style = 'i';
				$variant    = str_replace( 'italic', '', $variant );
			} elseif ( false !== strrpos( $variant, 'oblique' ) ) {
				$font_style = 'o';
				$variant    = str_replace( 'oblique', '', $variant );
			}

//          The equivalence:
//
//			1: 100
//			2: 200
//			3: 300
//			4: 400 (default, also recognized as 'normal')
//			5: 500
//			6: 600
//			7: 700 (also recognized as 'bold')
//			8: 800
//			9: 900

			switch ( $variant ) {
				case '100':
					$font_weight = 1;
					break;
				case '200':
					$font_weight = 2;
					break;
				case '300':
					$font_weight = 3;
					break;
				case '500':
					$font_weight = 5;
					break;
				case '600':
					$font_weight = 6;
					break;
				case '700':
				case 'bold':
					$font_weight = 7;
					break;
				case '800':
					$font_weight = 8;
					break;
				case '900':
					$font_weight = 9;
					break;
				default:
					$font_weight = 4;
					break;
			}

			$fvds[] = $font_style . '' .  $font_weight;
		}

		return $fvds;
	}

	/**
	 * Will convert an array of CSS like variants into the appropriate Google Fonts CSS 2 API format.
	 * @link https://developers.google.com/fonts/docs/css2
	 *
	 * @param array $variants
	 * @return string
	 */
	public static function convertFontVariantsToGoogleFontsCSS2Styles( $variants ) {
		$stylesString = '';
		if ( ! is_array( $variants ) || empty( $variants ) ) {
			return $stylesString;
		}

		$styleWeights = [
			'italic' => [],
			'normal' => [],
		];

		foreach ( $variants as $variant ) {
			// Make sure that we are working with strings.
			$variant = (string) $variant;

			// This is the default font style.
			$font_style = 'normal'; // normal
			if ( false !== strrpos( $variant, 'italic'  ) ) {
				$font_style = 'italic';
				$variant    = str_replace( 'italic', '', $variant );
			}

//          The equivalence:
//
//			1: 100
//			2: 200
//			3: 300
//			4: 400 (default, also recognized as 'normal')
//			5: 500
//			6: 600
//			7: 700 (also recognized as 'bold')
//			8: 800
//			9: 900

			switch ( $variant ) {
				case '100':
					$font_weight = 100;
					break;
				case '200':
					$font_weight = 200;
					break;
				case '300':
					$font_weight = 300;
					break;
				case '500':
					$font_weight = 500;
					break;
				case '600':
					$font_weight = 600;
					break;
				case '700':
				case 'bold':
					$font_weight = 700;
					break;
				case '800':
					$font_weight = 800;
					break;
				case '900':
					$font_weight = 900;
					break;
				default:
					$font_weight = 400;
					break;
			}

			$styleWeights[ $font_style ][] = $font_weight;
		}

		// Now construct the string.

		// All supported weights, ordered numerically.
		$allWeights = [ 100, 200, 300, 400, 500, 600, 700, 800, 900 ];

		$axisTagsList = [];
		// We always have both `ital` and `wght` axis, for a clearer logic.
		$axisTagsList[] = 'ital';
		$axisTagsList[] = 'wght';

		$axisTuplesList = [];
		foreach ( $allWeights as $weight ) {
			// Go through all axes determine the tuple (e.g. italic 400 becomes 1,400; or 700 becomes 0,700)
			// The ital axis can only have the value 0 or 1.
			if ( false !== array_search( $weight, $styleWeights['normal'] ) ) {
				$axisTuplesList[] = '0,' . $weight;
			}
			if ( false !== array_search( $weight, $styleWeights['italic'] ) ) {
				$axisTuplesList[] = '1,' . $weight;
			}
		}

		if ( ! empty( $axisTuplesList ) ) {
			// We must make sure that the axis tags are ordered alphabetically.
			sort( $axisTagsList, SORT_STRING );
			// We also need to sort the tuples, numerically.
			sort( $axisTuplesList, SORT_STRING );

			$stylesString = join( ',', $axisTagsList ) . '@' . join( ';', $axisTuplesList );
		}

		return $stylesString;
	}

	/**
	 * Attempt to JSON decode the provided value.
	 *
	 * @param mixed $value
	 *
	 * @return mixed|string
	 */
	public static function maybeDecodeValue( $value ) {
		// If the value is already an array, nothing to do.
		if ( is_array( $value ) ) {
			return $value;
		}

		if ( is_string( $value ) ) {
			$value = PixCustomifyPlugin::decodeURIComponent( $value );
			$value = wp_unslash( $value );
			$value = json_decode( $value, true );
		}

		return $value;
	}

	/**
	 * Attempt to JSON encode the provided value.
	 *
	 * @param mixed $value
	 *
	 * @return mixed|string
	 */
	public static function maybeEncodeValue( $value ) {
		// If the value is already a string, nothing to do.
		if ( is_string( $value ) ) {
			return $value;
		}

		if ( is_array( $value ) || is_object( $value ) ) {
			$value = PixCustomifyPlugin::encodeURIComponent( json_encode( $value ) );
		}

		return $value;
	}

	/**
	 * Main Customify_Fonts_Global Instance
	 *
	 * Ensures only one instance of Customify_Fonts_Global is loaded or can be loaded.
	 *
	 * @since  2.7.0
	 * @static
	 *
	 * @return Customify_Fonts_Global Main Customify_Fonts_Global instance
	 */
	public static function instance() {

		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self();
		}
		return self::$_instance;
	} // End instance ()

	/**
	 * Cloning is forbidden.
	 */
	public function __clone() {

		_doing_it_wrong( __FUNCTION__,esc_html__( 'You should not do that!', 'customify' ), '' );
	}

	/**
	 * Unserializing instances of this class is forbidden.
	 */
	public function __wakeup() {

		_doing_it_wrong( __FUNCTION__, esc_html__( 'You should not do that!', 'customify' ), '' );
	}
}