fix(widgets): preserve badge HTML in icon card title/desc like reference theme

wp_kses_post() strips the custom <badge> element, so icon card title/desc
lost admin-authored markup (e.g. <badge class="ml6 jb-blue">GO</badge>)
both at save time and render time, diverging from the reference theme
which stores and echoes these fields raw.

- add shared inline whitelist (aksrt_icon_card_kses_tags/aksrt_icon_card_kses)
  and route both card renderers plus the media-brand fallback through it
- drop the dead unused $allow array in aksrt_icon_cover_card
- add 'rich_html' sanitize type (FieldSchema + SchemaProvider) and mark
  icon card title/desc fields in widget schemas, page display schema and
  the page config meta box save path
- align header slider card module with reference show_widget_bg branches
  (aksrt-panel/flex1 vs padding-10 wrapper/relative-h)
- update widget contract tests to pin the whitelist behavior
This commit is contained in:
2026-09-19 16:39:59 +08:00
parent 2f571c2a98
commit cd7082454a
9 changed files with 108 additions and 36 deletions
+4 -2
View File
@@ -250,8 +250,10 @@ final class PageConfigMetaBox {
}
$link = is_array($card['link'] ?? null) ? $card['link'] : [];
$cards[] = [
'title' => wp_kses_post((string) ($card['title'] ?? '')),
'desc' => wp_kses_post((string) ($card['desc'] ?? '')),
// legacy echoes card title/desc raw (admin authors <badge> etc.);
// wp_kses_post() would strip the custom badge element.
'title' => aksrt_icon_card_kses((string) ($card['title'] ?? '')),
'desc' => aksrt_icon_card_kses((string) ($card['desc'] ?? '')),
// Icon picker values may carry an inline "customsvg-<svg>" payload;
// sanitize_text_field would strip it down to a bare prefix.
'icon' => function_exists('aksrt_sanitize_icon_value')
+1 -1
View File
@@ -181,7 +181,7 @@ class SchemaPageDisplay {
}
private static function card_fields(): array {
return [self::field('icon_radius4', '方形图标', 'switcher', false), self::field('cards', '添加卡片', 'group', [], ['button_title' => '添加内容', 'fields' => [self::field('title', '文案标题', 'textarea', '', ['sanitize_type' => 'html']), self::field('desc', '文案简介', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon', '选择图标', 'icon', 'fa fas fa-magic'), self::field('customize_icon', '自定义图标', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon_class', '图标颜色', 'palette', 'c-yellow', ['options' => self::aksrt_palette(true)]), self::field('link', '跳转链接', 'link', [])]])];
return [self::field('icon_radius4', '方形图标', 'switcher', false), self::field('cards', '添加卡片', 'group', [], ['button_title' => '添加内容', 'fields' => [self::field('title', '文案标题', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('desc', '文案简介', 'textarea', '', ['sanitize_type' => 'rich_html']), self::field('icon', '选择图标', 'icon', 'fa fas fa-magic'), self::field('customize_icon', '自定义图标', 'textarea', '', ['sanitize_type' => 'html']), self::field('icon_class', '图标颜色', 'palette', 'c-yellow', ['options' => self::aksrt_palette(true)]), self::field('link', '跳转链接', 'link', [])]])];
}
private static function orderby_fields(): array { return [self::field('lists', '显示排序方式', 'select', ['modified', 'views', 'like', 'comment_count'], ['multiple' => true, 'sortable' => true, 'options' => self::post_order_options(false)]), self::field('dropdown', '更多排序方式', 'switcher', false)]; }
+7
View File
@@ -821,6 +821,13 @@ class SchemaProvider {
return array_values(array_unique($ids));
case 'html':
return wp_kses_post((string) $value);
case 'rich_html':
// Inline whitelist for card title/desc: `badge` is a custom
// element that wp_kses_post() would strip, but legacy (Zibll)
// renders such markup verbatim.
return function_exists('aksrt_icon_card_kses')
? aksrt_icon_card_kses((string) $value)
: wp_kses_post((string) $value);
case 'textarea':
return sanitize_textarea_field((string) $value);
case 'code':
+17
View File
@@ -85,12 +85,18 @@ final class FieldSchema {
? aksrt_sanitize_icon_value((string) $value)
: sanitize_text_field((string) $value);
case 'textarea':
if (($field['sanitize'] ?? '') === 'rich_html') {
return self::rich_inline_html((string) $value);
}
return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_textarea_field((string) $value);
case 'text':
default:
if (($field['sanitize'] ?? '') === 'url') {
return esc_url_raw((string) $value);
}
if (($field['sanitize'] ?? '') === 'rich_html') {
return self::rich_inline_html((string) $value);
}
if (($field['sanitize'] ?? '') === 'ids') {
return implode(',', array_values(array_unique(array_filter(array_map('absint', preg_split('/[,,\s]+/', (string) $value))))));
}
@@ -103,6 +109,17 @@ final class FieldSchema {
}
}
/**
* Inline rich-HTML sanitizer for admin-authored card title/desc fields.
* `badge` and friends are not in WP's $allowedposttags, so wp_kses_post()
* would strip the exact markup legacy/Zibll renders verbatim.
*/
private static function rich_inline_html(string $value): string {
return function_exists('aksrt_icon_card_kses')
? aksrt_icon_card_kses($value)
: wp_kses_post($value);
}
private static function empty_value(string $type) {
if ($type === 'switcher') {
return false;
+4 -4
View File
@@ -537,8 +537,8 @@ final class WidgetSchemas {
['id' => 'icon_class', 'type' => 'palette', 'label' => '图标样式', 'default' => 'c-yellow', 'options' => self::icon_background_palette(), 'dependency' => ['icon_radius', '!=', '']],
['id' => 'icon_color', 'type' => 'palette', 'label' => '图标颜色', 'default' => 'key-color', 'options' => self::icon_text_palette(), 'dependency' => ['icon_radius|icon_custom_color', '==|==', '|']],
['id' => 'icon_custom_color', 'type' => 'color', 'label' => '自定义图标颜色', 'default' => '', 'dependency' => ['icon_radius', '==', '']],
['id' => 'title', 'type' => 'textarea', 'label' => '文案标题', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]],
['id' => 'desc', 'type' => 'textarea', 'label' => '文案简介', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 2]],
['id' => 'title', 'type' => 'textarea', 'label' => '文案标题', 'default' => '', 'allow_html' => true, 'sanitize' => 'rich_html', 'attributes' => ['rows' => 1]],
['id' => 'desc', 'type' => 'textarea', 'label' => '文案简介', 'default' => '', 'allow_html' => true, 'sanitize' => 'rich_html', 'attributes' => ['rows' => 2]],
]],
];
}
@@ -550,8 +550,8 @@ final class WidgetSchemas {
['id' => 'show_widget_bg', 'type' => 'switcher', 'label' => '显示模块背景', 'default' => false],
['id' => 'icon_radius4', 'type' => 'switcher', 'label' => '方形图标', 'default' => false],
['id' => 'cards', 'type' => 'group', 'label' => '添加卡片', 'max_items' => 40, 'default' => [], 'fields' => [
['id' => 'title', 'type' => 'textarea', 'label' => '文案标题', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]],
['id' => 'desc', 'type' => 'textarea', 'label' => '文案简介', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 2]],
['id' => 'title', 'type' => 'textarea', 'label' => '文案标题', 'default' => '', 'allow_html' => true, 'sanitize' => 'rich_html', 'attributes' => ['rows' => 1]],
['id' => 'desc', 'type' => 'textarea', 'label' => '文案简介', 'default' => '', 'allow_html' => true, 'sanitize' => 'rich_html', 'attributes' => ['rows' => 2]],
['id' => 'icon', 'type' => 'icon', 'label' => '选择图标', 'default' => 'fa fas fa-magic'],
['id' => 'customize_icon', 'type' => 'textarea', 'label' => '自定义图标', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1]],
['id' => 'icon_class', 'type' => 'palette', 'label' => '图标颜色', 'default' => 'c-yellow', 'options' => self::icon_background_palette()],