<?php
/**
 * Blogsy Customizer sanitization callback functions.
 *
 * @package     Blogsy
 * @author      Peregrine Themes
 * @since       1.0.0
 */

/**
 * Do not allow direct script access.
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Number sanitization callback
 *
 * @since 1.0.0
 * @param mixed $val Number.
 */
function blogsy_sanitize_number( $val ) {
	return is_numeric( $val ) ? $val : 0;
}

/**
 * Toggle field sanitization.
 *
 * @since 1.0.0
 * @param mixed                $input    Value of the toggle field.
 * @param WP_Customize_Setting $setting  Setting object.
 */
function blogsy_sanitize_toggle( $input, WP_Customize_Setting $setting ): bool {

	if ( 'blogsy_enable_front_page' === $setting ) {
		sanitize_key( $input );
	}

	// Ensure input is a slug.
	$input = sanitize_key( $input );

	return (bool) $input;
}

/**
 * Sanitize checkbox
 *
 * @param mixed $input Value of checkbox.
 */
function blogsy_sanitize_checkbox( $input ): bool {

	// returns true if checkbox is checked.
	return ( isset( $input ) );
}

/**
 * Select field sanitization callback.
 *
 * @since 1.0.0
 * @param mixed                $input    Value of the select field.
 * @param WP_Customize_Setting $setting  Setting object.
 */
function blogsy_sanitize_select( $input, WP_Customize_Setting $setting ) {

	$control  = $setting->manager->get_control( $setting->id );
	$multiple = isset( $control->multiple ) ? $control->multiple : false;
	$choices  = isset( $control->choices ) ? $control->choices : [];

	// For AJAX-backed Select2 controls we cannot validate only against the current
	// preloaded choices, because a new selection may not be part of those choices.
	if ( $control && ! empty( $control->is_select2 ) && ! empty( $control->data_source ) ) {
		$dynamic = blogsy_sanitize_select_dynamic_source( $input, $setting, $control );
		if ( false !== $dynamic ) {
			return $dynamic;
		}
	}

	if ( $multiple ) {

		// Check if input is array.
		if ( is_array( $input ) && ! empty( $input ) ) {

			$return = [];

			// Get only valid values.
			foreach ( $input as $selected ) {

				$selected = sanitize_key( $selected );

				if ( array_key_exists( $selected, $choices ) ) {
					$return[] = $selected;
				}
			}

			// Return valid only.
			return $return;

		} else {

			// Return default if input valid.
			return [];
		}
	} else {

		// Ensure input is a slug.
		$input = sanitize_key( $input );

		// If the input is a valid key, return it; otherwise, return the default.
		return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
	}
}

/**
 * Validate dynamic AJAX select values against the configured data source.
 *
 * @param mixed  $input   Submitted value or array of values.
 * @param object $setting Setting object.
 * @param object $control Customizer control.
 *
 * @return mixed False when not handled, otherwise sanitized input.
 * @since 1.0.16
 */
function blogsy_sanitize_select_dynamic_source( $input, $setting, $control ) {
	$data_source      = $control->data_source;
	$data_source_name = $control->data_source_name;
	$multiple         = isset( $control->multiple ) ? $control->multiple : false;

	if ( $multiple ) {
		if ( ! is_array( $input ) || empty( $input ) ) {
			return [];
		}

		$input = array_map( 'sanitize_key', $input );
		$input = array_filter( array_map( 'trim', $input ), 'strlen' );
		if ( empty( $input ) ) {
			return [];
		}

		$valid_ids = blogsy_sanitize_select2_valid_ids( $input, $data_source, $data_source_name );
		if ( empty( $valid_ids ) ) {
			return [];
		}

		$valid_ids = array_map( 'strval', $valid_ids );
		return array_values( array_intersect( $input, $valid_ids ) );
	}

	$input = sanitize_key( $input );
	if ( '' === $input ) {
		return $setting->default;
	}

	$valid_ids = blogsy_sanitize_select2_valid_ids( [ $input ], $data_source, $data_source_name );
	$valid_ids = array_map( 'strval', $valid_ids );

	return in_array( $input, $valid_ids, true ) ? $input : $setting->default;
}

/**
 * Return valid IDs for AJAX select2 choices from the configured source.
 *
 * @param array       $input_ids Submitted IDs.
 * @param string      $data_source Data source identifier.
 * @param string|null $data_source_name Optional taxonomy name.
 *
 * @return array Valid IDs.
 * @since 1.0.16
 */
function blogsy_sanitize_select2_valid_ids( $input_ids, $data_source, $data_source_name = null ) {

	if ( 'post_types' === $data_source || 'post_type' === $data_source ) {
		return blogsy_sanitize_select2_valid_post_types( $input_ids );
	}

	$ids = array_map( 'absint', $input_ids );
	$ids = array_filter( $ids );
	if ( empty( $ids ) ) {
		return [];
	}

	switch ( $data_source ) {
		case 'category':
			$args = [
				'taxonomy'   => $data_source_name ?? 'category',
				'hide_empty' => false,
				'include'    => $ids,
				'fields'     => 'ids',
			];
			return get_terms( $args );

		case 'tags':
			$args = [
				'taxonomy'   => 'post_tag',
				'hide_empty' => false,
				'include'    => $ids,
				'fields'     => 'ids',
			];
			return get_terms( $args );

		case 'page':
			$args = [
				'post_type'      => 'page',
				'post_status'    => 'publish',
				'post__in'       => $ids,
				'posts_per_page' => count( $ids ),
				'orderby'        => 'post__in',
				'fields'         => 'ids',
			];
			return get_posts( $args );

		case 'post':
			$args = [
				'post_type'      => 'post',
				'post_status'    => 'publish',
				'post__in'       => $ids,
				'posts_per_page' => count( $ids ),
				'orderby'        => 'post__in',
				'fields'         => 'ids',
			];
			return get_posts( $args );

		default:
			if ( ! post_type_exists( $data_source ) ) {
				return [];
			}
			$args = [
				'post_type'      => $data_source,
				'post_status'    => 'publish',
				'post__in'       => $ids,
				'posts_per_page' => count( $ids ),
				'orderby'        => 'post__in',
				'fields'         => 'ids',
			];
			return get_posts( $args );

	}
}

/**
 * Return valid post types for AJAX select2 choices.
 *
 * @param array $input Submitted post type slugs.
 * @return array Valid post type slugs.
 * @since 1.0.16
 */
function blogsy_sanitize_select2_valid_post_types( $input ) {
	$input = array_map( 'sanitize_key', $input );
	$input = array_filter( array_map( 'trim', $input ), 'strlen' );
	if ( empty( $input ) ) {
		return [];
	}

	$valid_post_types = get_post_types( [ 'public' => true ], 'names' );
	return array_values( array_intersect( $input, $valid_post_types ) );
}
/**
 * Tags input field sanitization callback.
 *
 * @since 1.0.0
 * @param mixed $input    Value of the tags input field.
 */
function blogsy_sanitize_tags_input( $input ) {
	if ( empty( $input ) ) {
		return '';
	}

	// Convert to array.
	$tags = array_map( 'trim', explode( ',', $input ) );

	// Remove empty values.
	$tags = array_filter( $tags );

	// Sanitize each tag.
	$tags = array_map( 'sanitize_text_field', $tags );

	// Remove duplicates.
	$tags = array_unique( $tags );

	// Convert back to comma-separated string.
	return implode( ',', $tags );
}

/**
 * Radio field sanitization callback.
 *
 * @since 1.0.0
 * @param mixed                $input    Value of the radio field.
 * @param WP_Customize_Setting $setting  Setting object.
 */
function blogsy_sanitize_radio( $input, WP_Customize_Setting $setting ): string {
	// Ensure input is a slug.
	$input = sanitize_key( $input );

	// Get a list of choices from the control associated with the setting.
	$choices = $setting->manager->get_control( $setting->id )->choices;

	// If the input is a valid key, return it; otherwise, return the default.
	return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}

/**
 * Textarea field sanitization callback.
 *
 * @since 1.0.0
 * @param mixed $input    Value of the select field.
 */
function blogsy_sanitize_textarea( $input ): string {
	return wp_kses_post( $input );
}

/**
 * Color field sanitization callback
 *
 * @param string $color Color code.
 * @since 1.0.0
 */
function blogsy_sanitize_color( string $color ): string {

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

	if ( false === strpos( $color, 'rgba' ) ) {
		return blogsy_sanitize_hex_color( $color );
	}

	return blogsy_sanitize_alpha_color( $color );
}

/**
 * Sanitize HEX color.
 *
 * @param string $color Color code in HEX.
 * @since 1.0.0
 */
function blogsy_sanitize_hex_color( string $color ): string {

	if ( '' === $color ) {
		return '';
	}

	// Remove multiple '#' characters and keep only one.
	$color = preg_replace( '/#+/', '#', $color );

	// 3 or 6 hex digits, or the empty string.
	if ( preg_match( '|^#([A-Fa-f0-9]{3}){1,2}$|', $color ) ) {
		return $color;
	}

	return '';
}

/**
 * Sanitize Alpha color.
 *
 * @param string $color Color code in RGBA.
 * @since 1.0.0
 */
function blogsy_sanitize_alpha_color( string $color ): string {

	if ( '' === $color ) {
		return '';
	}

	if ( false === strpos( $color, 'rgba' ) ) {
		/* Hex sanitize */
		return blogsy_sanitize_hex_color( $color );
	}

	/* rgba sanitize */
	$color = str_replace( ' ', '', $color );
	sscanf( $color, 'rgba(%d,%d,%d,%f)', $red, $green, $blue, $alpha );
	return 'rgba(' . $red . ',' . $green . ',' . $blue . ',' . $alpha . ')';
}

/**
 * Sanitize image.
 *
 * @since 1.0.0
 * @param string               $image    Selected image.
 * @param WP_Customize_Setting $setting  Setting object.
 */
function blogsy_sanitize_image( string $image, WP_Customize_Setting $setting ): string {
	/*
		 * Array of valid image file types.
		 *
		 * The array includes image mime types that are included in wp_get_mime_types()
	*/
	$mimes = [
		'jpg|jpeg|jpe' => 'image/jpeg',
		'gif'          => 'image/gif',
		'png'          => 'image/png',
		'bmp'          => 'image/bmp',
		'tif|tiff'     => 'image/tiff',
		'ico'          => 'image/x-icon',
		'svg'          => 'image/svg+xml',
		'webp'         => 'image/webp',
		'avif'         => 'image/avif',
	];

	// Return an array with file extension and mime_type.
	$file = wp_check_filetype( $image, $mimes );

	// If $image has a valid mime_type, return it; otherwise, return the default.
	return ( $file['ext'] ? $image : $setting->default );
}

/**
 * Sanitize widget control value.
 *
 * @param array|bool           $widgets Array of saved widgets or false.
 * @param WP_Customize_Setting $setting WP Customize Setting object.
 * @return mixed[]
 * @since 1.0.0
 */
function blogsy_sanitize_widget( $widgets, WP_Customize_Setting $setting ): array {

	// Widgets has to be an array.
	if ( ! is_array( $widgets ) || [] === $widgets ) {
		return [];
	}

	$control = $setting->manager->get_control( $setting->id );

	// Control widgets.
	$control_widgets = $control->widgets;

	// Control locations.
	$control_locations = $control->locations;

	// Control visibility options.
	$control_visibility = $control->visibility;

	// Used to control max uses for widgets.
	$widget_uses = [];

	foreach ( $widgets as $index => $widget ) {

		// Check if this widget is allowed.
		if ( ! array_key_exists( $widget['type'], $control_widgets ) ) {
			unset( $widgets[ $index ] );
			continue;
		}

		// Set max usage for widget.
		if ( ! isset( $widget_uses[ $widget['type'] ] ) ) {
			$widget_uses[ $widget['type'] ] = isset( $control_widgets[ $widget['type'] ]['max_uses'] ) ? intval( $control_widgets[ $widget['type'] ]['max_uses'] ) : -1;
		}

		// Remove if max widgets exceeded count.
		if ( -1 !== $widget_uses[ $widget['type'] ] ) {

			if ( $widget_uses[ $widget['type'] ] > 0 ) {

				// Widget count still good.
				--$widget_uses[ $widget['type'] ];
			} else {

				// Exceeded widget max usage.
				unset( $widgets[ $index ] );
				continue;
			}
		}

		// Ensure widget has values.
		if ( ! isset( $widget['values'] ) || ! is_array( $widget['values'] ) ) {
			$widget['values'] = [];
		}

		// Validate the location parameter.
		if ( isset( $widget['values']['location'] ) ) {

			if ( empty( $control_locations ) ) {

				// No locations available.
				unset( $widget['values']['location'] );
			} elseif ( ! array_key_exists( $widget['values']['location'], $control_locations ) ) {

				// This locations is not available, take one from array of available locations.
				$widget['values']['location'] = key( $control_locations );
			}
		} elseif ( ! empty( $control_locations ) ) {

			// Widget has to have a location option.
			$widget['values']['location'] = key( $control_locations );
		}

		// Validate the visibility parameter.
		if ( isset( $widget['values']['visibility'] ) ) {

			if ( empty( $control_visibility ) ) {

				// No visibility available.
				$widget['values']['visibility'] = '';
			} elseif ( ! array_key_exists( $widget['values']['visibility'], $control_visibility ) ) {

				// This locations is not available, take one from array of available locations.
				$widget['values']['visibility'] = key( $control_visibility );
			}
		} elseif ( ! empty( $control_visibility ) ) {

			// Widget has to have a location option.
			$widget['values']['visibility'] = key( $control_visibility );
		}

		// Validated Image field.
		if ( isset( $widget['values']['image_id'] ) && '' !== $widget['values']['image_id'] ) {
			$widget['values']['image_id'] = absint( $widget['values']['image_id'] );
		}

		// Sanitize display_area checkbox array.
		if ( isset( $widget['values']['display_area'] ) && is_array( $widget['values']['display_area'] ) ) {
			$widget['values']['display_area'] = array_map( 'sanitize_text_field', $widget['values']['display_area'] );
		}

		// Validate widget values.
		if ( ! empty( $widget['values'] ) ) {
			$classname        = $widget['classname'];
			$instance         = new $classname( $widget['values'] );
			$widget['values'] = $instance->values;
		}
	}

	return $widgets;
}

/**
 * No sanitization. Used for controls that only output HTML.
 *
 * @since 1.0.0
 * @param mixed $val Value.
 */
function blogsy_no_sanitize( $val ) {
	return $val;
}

/**
 * Function to sanitize sections order control
 *
 * @param string $input Sections order in json format.
 *
 * @return string
 */
function blogsy_sanitize_order( string $input ): string {

	$json = json_decode( $input, true );
	foreach ( $json as $section => $priority ) {
		if ( ! is_string( $section ) || ! is_int( $priority ) ) {
			return false;
		}
	}

	$filter_empty = array_filter( $json, 'blogsy_not_empty' );

	return wp_json_encode( $filter_empty );
}

/**
 * Function to filter json empty elements.
 *
 * @param int $val Element of json decoded.
 */
function blogsy_not_empty( $val ): bool {
	return ! empty( $val );
}

/**
 * Sanitize Background control value.
 *
 * @since 1.0.0
 * @param mixed $background Value.
 * @return array
 */
function blogsy_sanitize_background( $background ): array {

	$bg_params = [
		'background-image'      => '',
		'background-image-id'   => '',
		'background-repeat'     => 'repeat',
		'background-position-x' => '50',
		'background-position-y' => '50',
		'background-size'       => 'auto',
		'background-attachment' => 'scroll',
	];

	foreach ( array_keys( $bg_params ) as $key ) {

		if ( isset( $background[ $key ] ) ) {
			$bg_params[ $key ] = 'background-image' === $key ? esc_url_raw( $background[ $key ] ) : esc_attr( $background[ $key ] );
		}
	}

	if ( empty( $bg_params['background-image'] ) ) {
		$bg_params['background-image-id'] = '';
	}

	return $bg_params;
}

/**
 * Sanitize Spacing control value.
 *
 * @since 1.0.0
 * @param array                $values  Values.
 * @param WP_Customize_Setting $setting WP Customize Setting instance.
 * @param array                $default_value Default Value.
 */
function blogsy_sanitize_spacing( array $values, WP_Customize_Setting $setting, array $default_value = [] ): array {

	$control         = $setting->manager->get_control( $setting->id );
	$control_choices = $control->choices;
	$control_units   = $control->unit;

	foreach ( $control_choices as $key => $value ) {
		if ( ! isset( $values[ $key ] ) ) {
			$values[ $key ] = $default_value[ $key ] ?? 0;
		}
	}

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

		if ( 'unit' === $key ) {
			continue;
		}

		if ( ! isset( $control_choices[ $key ] ) ) {
			unset( $values[ $key ] );
			continue;
		}

		$values[ $key ] = is_numeric( $value ) ? $value : '';
	}

	if ( isset( $values['unit'] ) && ! in_array( $values['unit'], $control_units, true ) ) {
		if ( isset( $default_value['unit'] ) ) {
			$values['unit'] = $default_value['unit'];
		} elseif ( ! empty( $control_units ) ) {
			$values['unit'] = $control_units[0];
		} else {
			$values['unit'] = '';
		}
	}

	return $values;
}

/**
 * Sanitize Range control value.
 *
 * @since 1.0.0
 * @param mixed                $value   Values.
 * @param WP_Customize_Setting $setting WP Customize Setting instance.
 * @param mixed                $default_value Default Value.
 */
function blogsy_sanitize_range( $value, WP_Customize_Setting $setting, $default_value = '' ) {

	if ( is_array( $value ) ) {
		if ( isset( $value['value'], $value['unit'] ) ) {
			return [
				'value' => floatval( $value['value'] ),
				'unit'  => sanitize_text_field( $value['unit'] ),
			];
		} elseif ( isset( $value['value'] ) ) {
			return floatval( $value['value'] );
		}
	}

	return is_numeric( $value ) ? floatval( $value ) : $default_value;
}

/**
 * Sanitize Responsive control value.
 * Iterate through all responsive breakpoints and sanitize each value.
 *
 * @since 1.0.0
 * @param array                $values  Values.
 * @param WP_Customize_Setting $setting WP Customize Setting instance.
 */
function blogsy_sanitize_responsive( array $values, WP_Customize_Setting $setting ): array {

	$control      = $setting->manager->get_control( $setting->id );
	$control_type = str_replace( 'blogsy-', '', $control->type );

	if ( is_array( $control->responsive ) && [] !== $control->responsive ) {

		// Ensure all responsive devices are in value.
		foreach ( array_keys( $control->responsive ) as $device ) {

			if ( ! isset( $values[ $device ] ) ) {
				$values[ $device ] = $setting->default[ $device ] ?? '';
			}
		}

		// Ensure all devices in value are allowed and sanitize value.
		foreach ( $values as $device => $value ) {

			if ( 'unit' === $device ) {
				continue;
			}

			if ( ! isset( $control->responsive[ $device ] ) ) {
				unset( $values[ $device ] );
				continue;
			}

			// Sanitize value.
			$values[ $device ] = call_user_func_array(
				'blogsy_sanitize_' . $control_type,
				[
					$value,
					$setting,
					$setting->default[ $device ] ?? '',
				]
			);
		}
	}

	return $values;
}

/**
 * Typography field sanitization.
 *
 * @since 1.0.0
 * @param array                $value    Value of the toggle field.
 * @param WP_Customize_Setting $setting  Setting object.
 */
function blogsy_sanitize_typography( array $value, WP_Customize_Setting $setting ): array {

	$defaults = [
		'font-family'         => '',
		'font-subsets'        => [],
		'font-weight'         => '',
		'font-style'          => '',
		'text-transform'      => '',
		'font-size-desktop'   => '',
		'font-size-tablet'    => '',
		'font-size-mobile'    => '',
		'font-size-unit'      => '',
		'color'               => '',
		'letter-spacing'      => '',
		'letter-spacing-unit' => '',
		'line-height-desktop' => '',
		'line-height-tablet'  => '',
		'line-height-mobile'  => '',
		'line-height-unit'    => '',
	];

	$defaults = wp_parse_args( $setting->default, $defaults );

	return wp_parse_args( $value, $defaults );
}

/**
 * Design Options field sanitization.
 *
 * @since 1.0.0
 * @param array                $value    Value of the toggle field.
 * @param WP_Customize_Setting $setting  Setting object.
 * @return array
 */
function blogsy_sanitize_design_options( array $value, WP_Customize_Setting $setting ): array {

	$control = $setting->manager->get_control( $setting->id );

	$sanitized = (array) $setting->default;

	if ( isset( $control->display ) ) {

		if ( isset( $control->display['background'] ) ) {

			if ( ! array_key_exists( $value['background-type'], $control->display['background'] ) ) {
				$sanitized['background-type'] = 'color';
			} else {
				$sanitized['background-type'] = $value['background-type'];
			}

			// Color.
			if ( array_key_exists( 'color', $control->display['background'] ) && isset( $value['background-color'] ) ) {

				$sanitized['background-color'] = blogsy_sanitize_color( $value['background-color'] );
			}

			// Gradient.
			if ( array_key_exists( 'gradient', $control->display['background'] ) ) {

				if ( isset( $value['gradient-color-1'] ) ) {
					$sanitized['gradient-color-1'] = blogsy_sanitize_color( $value['gradient-color-1'] );
				}

				if ( isset( $value['gradient-color-2'] ) ) {
					$sanitized['gradient-color-2'] = blogsy_sanitize_color( $value['gradient-color-2'] );
				}

				if ( isset( $value['gradient-color-1-location'] ) ) {
					$sanitized['gradient-color-1-location'] = blogsy_sanitize_range( $value['gradient-color-1-location'], $setting );
				}

				if ( isset( $value['gradient-color-2-location'] ) ) {
					$sanitized['gradient-color-2-location'] = blogsy_sanitize_range( $value['gradient-color-2-location'], $setting );
				}

				if ( isset( $value['gradient-type'] ) && in_array( $value['gradient-type'], [ 'linear', 'radial' ], true ) ) {
					$sanitized['gradient-type'] = $value['gradient-type'];
				}

				if ( isset( $value['gradient-linear-angle'] ) ) {
					$sanitized['gradient-linear-angle'] = blogsy_sanitize_range( $value['gradient-linear-angle'], $setting );
				}

				if ( isset( $value['gradient-position'] ) ) {
					$sanitized['gradient-position'] = sanitize_text_field( $value['gradient-position'] );
				}
			}

			// Image.
			if ( array_key_exists( 'image', $control->display['background'] ) ) {

				if ( isset( $value['background-image'] ) ) {
					$sanitized['background-image'] = sanitize_text_field( $value['background-image'] );
				}

				if ( isset( $value['background-image-id'] ) ) {
					$sanitized['background-image-id'] = sanitize_text_field( $value['background-image-id'] );
				}

				if ( isset( $value['background-repeat'] ) ) {
					$sanitized['background-repeat'] = sanitize_text_field( $value['background-repeat'] );
				}

				if ( isset( $value['background-position-x'] ) ) {
					$sanitized['background-position-x'] = intval( $value['background-position-x'] );
				}

				if ( isset( $value['background-position-y'] ) ) {
					$sanitized['background-position-y'] = intval( $value['background-position-y'] );
				}

				if ( isset( $value['background-size'] ) ) {
					$sanitized['background-size'] = sanitize_text_field( $value['background-size'] );
				}

				if ( isset( $value['background-attachment'] ) ) {
					$sanitized['background-attachment'] = sanitize_text_field( $value['background-attachment'] );
				}

				if ( isset( $value['background-color-overlay'] ) ) {
					$sanitized['background-color-overlay'] = sanitize_text_field( $value['background-color-overlay'] );
				}
			}
		}

		if ( isset( $control->display['color'] ) ) {
			foreach ( $control->display['color'] as $id => $title ) {
				if ( isset( $value[ $id ] ) ) {
					$sanitized[ $id ] = blogsy_sanitize_color( $value[ $id ] );
				}
			}
		}

		if ( isset( $control->display['border'] ) ) {

			// Border Color.
			if ( array_key_exists( 'color', $control->display['border'] ) && isset( $value['border-color'] ) ) {
				$sanitized['border-color'] = blogsy_sanitize_color( $value['border-color'] );
			}

			// Border Style.
			if ( isset( $value['border-style'] ) ) {
				$sanitized['border-style'] = sanitize_key( $value['border-style'] );
			}

			// Separator Border Style.
			if ( isset( $value['separator-color'] ) ) {
				$sanitized['separator-color'] = blogsy_sanitize_color( $value['separator-color'] );
			}

			// Border Width.
			$border_width = [ 'left', 'top', 'right', 'bottom' ];

			foreach ( $border_width as $position ) {
				if ( isset( $value[ 'border-' . $position . '-width' ] ) ) {
					$sanitized[ 'border-' . $position . '-width' ] = intval( $value[ 'border-' . $position . '-width' ] );
				}
			}
		}

		if ( isset( $control->display['box-shadow'] ) ) {

			// Box Shadow X Offset.
			if ( isset( $value['x'] ) ) {
				$sanitized['x'] = intval( $value['x'] );
			}

			// Box Shadow Y Offset.
			if ( isset( $value['y'] ) ) {
				$sanitized['y'] = intval( $value['y'] );
			}

			// Box Shadow Blur.
			if ( isset( $value['blur'] ) ) {
				$sanitized['blur'] = max( 0, intval( $value['blur'] ) );
			}

			// Box Shadow Spread.
			if ( isset( $value['spread'] ) ) {
				$sanitized['spread'] = intval( $value['spread'] );
			}

			// Box Shadow Color.
			if ( isset( $value['color'] ) ) {
				$sanitized['color'] = blogsy_sanitize_color( $value['color'] );
			}

			// Box Shadow Type.
			if ( isset( $value['type'] ) && in_array( $value['type'], [ 'inset', 'outset' ], true ) ) {
				$sanitized['type'] = $value['type'];
			}
		}
	}

	return $sanitized;
}

/**
 * Sortable field sanitization.
 *
 * @param array|null           $value Value of the sortable field.
 * @param WP_Customize_Setting $setting WP Customize Setting object.
 * @return array|string
 * @since 1.0.0
 */
function blogsy_sanitize_sortable( ?array $value, WP_Customize_Setting $setting ) {

	$control = $setting->manager->get_control( $setting->id );
	$choices = $control->choices;
	$default = $setting->default;

	if ( empty( $value ) ) {
		return $default;
	}

	foreach ( $value as $item => $config ) {
		if ( ! isset( $choices[ $item ] ) ) {
			unset( $value[ $item ] );
		}
	}

	foreach ( $choices as $item => $config ) {
		if ( ! isset( $value[ $item ] ) ) {
			$value[ $item ] = $default[ $item ] ?? false;
		}
	}

	return $value;
}

/**
 * Sanitize repeatable data
 *
 * @param string               $input     Repeater input.
 * @param WP_Customize_Setting $setting WP Customizer Setting object.
 * @return bool|mixed|string|void
 */
function blogsy_repeater_sanitize( string $input, WP_Customize_Setting $setting ) {
	$control = $setting->manager->get_control( $setting->id );
	$fields  = $control->fields;
	$input   = json_decode( wp_unslash( $input ), true );

	$data = wp_parse_args( $input, [] );
	if ( ! is_array( $data ) ) {
		return false;
	}

	if ( ! isset( $data['_items'] ) ) {
		return false;
	}

	$data = $data['_items'];
	foreach ( $data as $i => $item_data ) {
		foreach ( $item_data as $id => $value ) {
			if ( isset( $fields[ $id ] ) ) {
				switch ( strtolower( $fields[ $id ]['type'] ) ) {
					case 'text':
					case 'radio':
						$data[ $i ][ $id ] = sanitize_text_field( $value );
						break;
					case 'url':
						$data[ $i ][ $id ] = esc_url_raw( $value );
						break;
					case 'textarea':
					case 'editor':
						$data[ $i ][ $id ] = wp_kses_post( $value );
						break;
					case 'color':
						$data[ $i ][ $id ] = blogsy_sanitize_hex_color( $value );
						break;
					case 'coloralpha':
						$data[ $i ][ $id ] = blogsy_sanitize_alpha_color( $value );
						break;
					case 'checkbox':
						$data[ $i ][ $id ] = blogsy_sanitize_checkbox( $value );
						break;
					case 'select':
						$data[ $i ][ $id ] = '';
						if ( is_array( $fields[ $id ]['options'] ) && ( isset( $fields[ $id ]['options'] ) && [] !== $fields[ $id ]['options'] ) ) {
							// if is multiple choices.
							if ( is_array( $value ) ) {
								foreach ( $value as $k => $v ) {
									if ( isset( $fields[ $id ]['options'][ $v ] ) ) {
										$value[ $k ] = $v;
									}
								}

								$data[ $i ][ $id ] = $value;
							} elseif ( isset( $fields[ $id ]['options'][ $value ] ) ) {
								$data[ $i ][ $id ] = $value;
							}
						}

						break;
					case 'media':
						$value                    = wp_parse_args(
							$value,
							[
								'url' => '',
								'id'  => false,
							]
						);
						$value['id']              = absint( $value['id'] );
						$data[ $i ][ $id ]['url'] = sanitize_text_field( $value['url'] );
						$url                      = wp_get_attachment_url( $value['id'] );
						if ( $url ) {
							$data[ $i ][ $id ]['id']  = $value['id'];
							$data[ $i ][ $id ]['url'] = $url;
						} else {
							$data[ $i ][ $id ]['id'] = '';
						}

						break;
					case 'link':
						$value                       = wp_parse_args(
							$value,
							[
								'url'    => '',
								'title'  => '',
								'target' => '',
							]
						);
						$data[ $i ][ $id ]['url']    = esc_url_raw( $value['url'] );
						$data[ $i ][ $id ]['title']  = sanitize_text_field( $value['title'] );
						$data[ $i ][ $id ]['target'] = sanitize_text_field( $value['target'] );
						break;
					case 'gradient':
						if ( isset( $value['gradient-color-1'] ) ) {
							$sanitized['gradient-color-1'] = blogsy_sanitize_color( $value['gradient-color-1'] );
						}

						if ( isset( $value['gradient-color-2'] ) ) {
							$sanitized['gradient-color-2'] = blogsy_sanitize_color( $value['gradient-color-2'] );
						}

						if ( isset( $value['gradient-color-1-location'] ) ) {
							$sanitized['gradient-color-1-location'] = blogsy_sanitize_range( $value['gradient-color-1-location'], $setting );
						}

						if ( isset( $value['gradient-color-2-location'] ) ) {
							$sanitized['gradient-color-2-location'] = blogsy_sanitize_range( $value['gradient-color-2-location'], $setting );
						}

						if ( isset( $value['gradient-type'] ) && in_array( $value['gradient-type'], [ 'linear', 'radial' ], true ) ) {
							$sanitized['gradient-type'] = $value['gradient-type'];
						}

						if ( isset( $value['gradient-linear-angle'] ) ) {
							$sanitized['gradient-linear-angle'] = blogsy_sanitize_range( $value['gradient-linear-angle'], $setting );
						}

						if ( isset( $value['gradient-position'] ) ) {
							$sanitized['gradient-position'] = sanitize_text_field( $value['gradient-position'] );
						}

						break;
					case 'design-options':
						blogsy_sanitize_design_options( $value, $setting );
						break;
					default:
						$data[ $i ][ $id ] = wp_kses_post( $value );
				}
			} else {
				$data[ $i ][ $id ] = wp_kses_post( $value );
			}

			if ( count( $data[ $i ] ) !== count( $fields ) ) {
				foreach ( $fields as $k => $f ) {
					if ( ! isset( $data[ $i ][ $k ] ) ) {
						$data[ $i ][ $k ] = '';
					}
				}
			}
		}
	}

	return $data;
}
