refactor(color): unify color sanitizing and picking behind shared components

- Add AKSRT\Core\Color::sanitize($value, $allow_alpha) as the single PHP
  color sanitizer; replace sanitize_hex_color calls and per-class regexes
  (FloatButtons, FieldSchema, ThemeSearch, icon cards, TicketStatusService)
- SchemaProvider / ControllerBase sanitize `color` fields as colors;
  alpha kept only when the schema sets showAlpha
- Split SchemaProvider OAuth validation and option matching into sibling
  traits to stay under the 1000-line limit
- Add AksColorPicker.vue + utils/color.js mirroring the PHP rules; AksField,
  FloatBtnCommonFields and TicketStatusPanel use it
- Widget background color input now uses wpColorPicker
- Remove dead AksCompositeField dimensions/spacing/background branches and
  the unused Gutenberg ColorPicker var
- Add color-unification contract test; update harness requires and tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-28 20:47:41 +08:00
1 parent 74021d26bb
commit 57ff405691
32 files changed
+522 -456

No files matched your search

+1 -1
View File
@@ -77,7 +77,7 @@ trait DisplayPageGroups {
'slides' => is_array($s['header_slider'] ?? null) ? $s['header_slider'] : self::header_slider_default_slides(),
'show_on_pages' => $show_on,
'show_type' => $show_type,
'nav_color' => sanitize_hex_color((string) ($s['header_slider_nav_color'] ?? '')) ?: '',
'nav_color' => \AKSRT\Core\Color::sanitize((string) ($s['header_slider_nav_color'] ?? '')) ?: '',
'slider_config' => $slider_option,
'search_show' => self::sanitize_device_visibility($s['header_slider_search_s'] ?? ['pc_s', 'm_s']),
'search_config' => $search_option,
+2 -2
View File
@@ -18,9 +18,9 @@ trait FacadeBulk {
return [
'custom' => !empty($s['header_theme_custom']),
'bg_custom' => sanitize_hex_color((string) ($s['header_theme_bg_custom'] ?? '')) ?: '',
'bg_custom' => \AKSRT\Core\Color::sanitize((string) ($s['header_theme_bg_custom'] ?? '')) ?: '',
'bg' => self::normalize_palette_hex((string) ($s['header_theme_bg'] ?? '')),
'color_custom' => sanitize_hex_color((string) ($s['header_theme_color_custom'] ?? '')) ?: '',
'color_custom' => \AKSRT\Core\Color::sanitize((string) ($s['header_theme_color_custom'] ?? '')) ?: '',
];
}
+2 -19
View File
@@ -218,25 +218,8 @@ class FloatButtons {
private static function sanitize_color_group($group): array {
$group = is_array($group) ? $group : [];
return [
'color' => self::sanitize_color((string) ($group['color'] ?? '')),
'bg' => self::sanitize_color((string) ($group['bg'] ?? '')),
'color' => \AKSRT\Core\Color::sanitize($group['color'] ?? '', true),
'bg' => \AKSRT\Core\Color::sanitize($group['bg'] ?? '', true),
];
}
/**
* 允许 #hex(3/4/6/8 位)与 rgb()/rgba()。
*/
public static function sanitize_color(string $value): string {
$value = trim($value);
if ($value === '') {
return '';
}
if (preg_match('/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $value)) {
return $value;
}
if (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)) {
return $value;
}
return '';
}
}
+2 -2
View File
@@ -61,9 +61,9 @@ class HomeCms {
$s = self::get_footer_settings();
return [
'custom' => !empty($s['footer_theme_custom']),
'bg_custom' => sanitize_hex_color((string) ($s['footer_theme_bg_custom'] ?? '')) ?: '',
'bg_custom' => \AKSRT\Core\Color::sanitize((string) ($s['footer_theme_bg_custom'] ?? '')) ?: '',
'bg' => self::normalize_palette_hex((string) ($s['footer_theme_bg'] ?? '')),
'color_custom' => sanitize_hex_color((string) ($s['footer_theme_color_custom'] ?? '')) ?: '',
'color_custom' => \AKSRT\Core\Color::sanitize((string) ($s['footer_theme_color_custom'] ?? '')) ?: '',
];
}
+4 -8
View File
@@ -388,12 +388,10 @@ class ThemeSearch {
return '';
}
private const HEX_PATTERN = '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/';
/** 全局主题色:theme_skin_custom 优先,清空时使用 theme_skin 预置色(对齐参考主题 zib_head_css)。 */
public static function theme_color(): string {
$custom = trim((string) Settings::get_value('theme_skin_custom', ''));
if (preg_match(self::HEX_PATTERN, $custom)) {
$custom = \AKSRT\Core\Color::sanitize(Settings::get_value('theme_skin_custom', ''));
if ($custom !== '') {
return $custom;
}
$skin = ltrim(trim((string) Settings::get_value('theme_skin', 'f04494')), '#');
@@ -402,8 +400,7 @@ class ThemeSearch {
/** 夜间模式主题色;未设置返回空串,夜间沿用全局主题色。 */
public static function dark_theme_color(): string {
$color = trim((string) Settings::get_value('dark_theme_skin_custom', ''));
return preg_match(self::HEX_PATTERN, $color) ? $color : '';
return \AKSRT\Core\Color::sanitize(Settings::get_value('dark_theme_skin_custom', ''));
}
public static function get_theme_mode_button_positions(): array {
@@ -429,8 +426,7 @@ class ThemeSearch {
if (!is_array($bg)) {
return '';
}
$color = trim((string) ($bg['color'] ?? ''));
$color = preg_match(self::HEX_PATTERN, $color) ? $color : '';
$color = \AKSRT\Core\Color::sanitize($bg['color'] ?? '');
$image = esc_url_raw(trim((string) ($bg['image'] ?? '')));
if ($color === '' && $image === '') {
return '';