preg_match('/^-?\d+$/', (string) $token) ? (string) (int) $token : '', $tokens ?: []); return implode(',', array_values(array_unique(array_filter($tokens, static fn($token): bool => $token !== '')))); } return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_text_field((string) $value); } } private static function empty_value(string $type) { if ($type === 'switcher') { return false; } if (in_array($type, ['group', 'checkbox', 'terms'], true)) { return []; } if (in_array($type, ['link', 'spacing', 'background', 'fieldset', 'accordion'], true)) { return []; } return ''; } private static function number(array $field, $value) { $number = is_numeric($value) ? (float) $value : (float) ($field['default'] ?? 0); if (isset($field['min'])) { $number = max((float) $field['min'], $number); } if (isset($field['max'])) { $number = min((float) $field['max'], $number); } $step = (float) ($field['step'] ?? 1); // Whole values stay int so JSON round trips keep normalize identity // even for fractional-step fields (e.g. partners speed 0.1–10s). return $step >= 1 || fmod($number, 1.0) === 0.0 ? (int) round($number) : $number; } private static function choice(array $field, $value) { $options = (array) ($field['options'] ?? []); if (!empty($field['multiple'])) { $values = is_array($value) ? $value : []; return array_values(array_filter(array_map('strval', $values), static fn($item) => array_key_exists($item, $options))); } $key = (string) $value; return array_key_exists($key, $options) ? $key : (string) ($field['default'] ?? array_key_first($options) ?? ''); } private static function checkbox(array $field, $value) { $options = (array) ($field['options'] ?? []); if (!$options) { return filter_var($value, FILTER_VALIDATE_BOOLEAN); } $values = is_array($value) ? $value : []; return array_values(array_filter(array_map('strval', $values), static fn($item) => array_key_exists($item, $options))); } private static function terms($value): array { $values = is_array($value) ? $value : (preg_split('/[,,\s]+/', (string) $value) ?: []); return array_values(array_unique(array_filter(array_map('absint', $values)))); } private static function link($value): array { $value = is_array($value) ? $value : []; return [ 'url' => esc_url_raw((string) ($value['url'] ?? '')), 'text' => sanitize_text_field((string) ($value['text'] ?? '')), 'target' => ($value['target'] ?? '') === '_blank' ? '_blank' : '', ]; } private static function spacing($value): array { $value = is_array($value) ? $value : []; $result = []; foreach (['top', 'right', 'bottom', 'left'] as $edge) { $result[$edge] = isset($value[$edge]) && is_numeric($value[$edge]) ? (int) $value[$edge] : ''; } return $result; } private static function background($value): array { $value = is_array($value) ? $value : []; return [ 'color' => self::color((string) ($value['color'] ?? '')), 'image' => esc_url_raw((string) ($value['image'] ?? '')), 'position' => self::allow((string) ($value['position'] ?? 'center center'), ['center center', 'top center', 'bottom center', 'center left', 'center right'], 'center center'), 'repeat' => self::allow((string) ($value['repeat'] ?? 'no-repeat'), ['no-repeat', 'repeat', 'repeat-x', 'repeat-y'], 'no-repeat'), 'attachment' => self::allow((string) ($value['attachment'] ?? 'scroll'), ['scroll', 'fixed'], 'scroll'), 'size' => self::allow((string) ($value['size'] ?? 'cover'), ['cover', 'contain', 'auto'], 'cover'), ]; } private static function group(array $field, $value): array { $rows = is_array($value) ? array_values($value) : []; $max = max(1, (int) ($field['max_items'] ?? 50)); $children = (array) ($field['fields'] ?? []); return array_map(static fn($row) => self::normalize($children, is_array($row) ? $row : []), array_slice($rows, 0, $max)); } private static function nested(array $field, $value): array { $children = (array) ($field['fields'] ?? []); return self::normalize($children, is_array($value) ? $value : []); } private static function color(string $value): string { $value = trim($value); if ($value === '') { return ''; } $hex = sanitize_hex_color($value); if ($hex) { return $hex; } return preg_match('/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/', $value) ? $value : ''; } private static function allow(string $value, array $allowed, string $fallback): string { return in_array($value, $allowed, true) ? $value : $fallback; } }