Files
wordpress-theme-aksrt/inc/modules/widget-common.php
T
Claude d975ff4ce6 feat(phase2): shared common-form layer + CommonWidgetFields trait for all widgets
- Extract Zibll CSF_Widget::params() common-field parity into
  inc/modules/widget-common.php (required by widgets-register.php to keep
  both files <1000 lines): subtitle, title_style(simple/big), title_highlight
  +color, title_link x3, title_link_style colors, sidebar_affix, show_type
  (all/only_pc/only_sm), show_id_type(show/hide), show_ids, animation_in,
  animation_repeat, layout_pin_pc/m, layout_bg(white/dark). Adds
  normalize_link/normalize_bg helpers, affix_attrs, common_wrapper_attr,
  render_title, animation_class. Back-compat normalizes legacy tokens
  (in_affix->sidebar_affix, pc/only_pc, include/exclude->show/hide).
- Add src/AKSRT/Widgets/Traits/CommonWidgetFields.php trait
  (render_common_form_section + update_common_fields).
- Wire all 57 widgets to use the trait; migrate the 16 hand-rolled in_affix
  implementations to shared sidebar_affix (fixes 6 latent bugs where
  in_affix was never persisted). SearchWidget intentionally skips the shared
  form section (its business show_type collides) but adopts the affix helper.
- widget_display_callback parity verified via tmp assertion script.

Refs .specs/plans/zibll-widgets-1to1.md Phase 2 (US1/US5)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 20:42:46 +08:00

639 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Shared widget common-form layer — Zibll CSF_Widget::params() parity.
*
* Extracted from widgets-register.php to keep each file under the 1000-line
* hard limit. Provides:
* - common field defaults / normalization (subtitle, title_style, highlight,
* 3 title links, sidebar_affix, show_type, show_id_type, animation, layout)
* - shared admin form renderer + update processor
* - wrapper class/attr builders (affix, animation, show-type, layout bg)
* - Zibll-aligned title block renderer (simple / big styles)
* - display-by-id gate consumed by widget_display_callback
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
// ============================================================
// Shared Widget Form Helper — 公共表单字段与显示规则
// ============================================================
if (!function_exists('aksrt_widget_common_defaults')) {
/**
* Default values for shared widget instance fields.
*
* Zibll CSF_Widget::params() common-field parity:
* subtitle, title_style, title_highlight, title_highlight_color,
* title_link (×3), title_link_style colors, sidebar_affix,
* show_type (all/only_pc/only_sm), show_id_type (show/hide), show_ids,
* animation_in, animation_repeat, layout_pin_pc/m, layout_bg (white/dark).
*
* @return array<string, mixed>
*/
function aksrt_widget_common_defaults(): array {
return [
'subtitle' => '',
'title_style' => '',
'title_highlight' => '',
'title_highlight_color'=> 'focus-color',
'title_link' => ['url' => '', 'text' => '', 'target' => ''],
'title_link2' => ['url' => '', 'text' => '', 'target' => ''],
'title_link3' => ['url' => '', 'text' => '', 'target' => ''],
'title_link_style' => ['color_1' => 'jb-blue', 'color_2' => 'jb-yellow', 'color_3' => 'jb-green'],
'sidebar_affix' => false,
'show_type' => 'all',
'show_id_type' => '',
'show_ids' => '',
'animation_in' => '',
'animation_repeat' => false,
'layout_pin_pc' => ['top' => '', 'bottom' => ''],
'layout_pin_m' => ['top' => '', 'bottom' => ''],
'layout_bg' => ['img_white' => [], 'img_dark' => []],
];
}
}
if (!function_exists('aksrt_widget_normalize_link')) {
/**
* Normalize a title_linkN array {url,text,target}.
*
* @param mixed $raw
* @return array{url:string,text:string,target:string}
*/
function aksrt_widget_normalize_link($raw): array {
$raw = is_array($raw) ? $raw : [];
return [
'url' => esc_url_raw(trim((string) ($raw['url'] ?? ''))),
'text' => sanitize_text_field((string) ($raw['text'] ?? '')),
'target' => ($raw['target'] ?? '') === '_blank' ? '_blank' : '',
];
}
}
if (!function_exists('aksrt_widget_normalize_common')) {
/**
* Normalize common widget instance fields, filling defaults for legacy instances.
*
* Accepts both the new structured shape (title_link as array) and legacy
* flat shapes (title_link as URL string, in_affix bool) emitted by older
* widget versions, normalizing them onto the canonical shared schema.
*
* @param array<string, mixed> $instance
* @return array<string, mixed>
*/
function aksrt_widget_normalize_common(array $instance): array {
$defaults = aksrt_widget_common_defaults();
$result = array_merge($defaults, $instance);
$result['subtitle'] = sanitize_text_field($result['subtitle'] ?? '');
$result['title_style'] = in_array($result['title_style'] ?? '', ['', 'big'], true) ? $result['title_style'] : '';
$result['title_highlight'] = sanitize_text_field($result['title_highlight'] ?? '');
$result['title_highlight_color'] = sanitize_html_class((string) ($result['title_highlight_color'] ?? 'focus-color'));
if ($result['title_highlight_color'] === '') {
$result['title_highlight_color'] = 'focus-color';
}
// Legacy flat title_link (URL string) → structured first link.
if (is_string($result['title_link'] ?? null)) {
$result['title_link'] = ['url' => esc_url_raw(trim($result['title_link'])), 'text' => '', 'target' => ''];
}
$result['title_link'] = aksrt_widget_normalize_link($result['title_link'] ?? []);
$result['title_link2'] = aksrt_widget_normalize_link($result['title_link2'] ?? []);
$result['title_link3'] = aksrt_widget_normalize_link($result['title_link3'] ?? []);
$tl_style = is_array($result['title_link_style'] ?? null) ? $result['title_link_style'] : [];
$result['title_link_style'] = [
'color_1' => sanitize_html_class((string) ($tl_style['color_1'] ?? 'jb-blue')) ?: 'jb-blue',
'color_2' => sanitize_html_class((string) ($tl_style['color_2'] ?? 'jb-yellow')) ?: 'jb-yellow',
'color_3' => sanitize_html_class((string) ($tl_style['color_3'] ?? 'jb-green')) ?: 'jb-green',
];
// show_type: accept both AKSRT legacy (pc/mobile) and Zibll (only_pc/only_sm) tokens.
$show_type = (string) ($result['show_type'] ?? 'all');
if ($show_type === 'only_pc' || $show_type === 'pc') {
$show_type = 'only_pc';
} elseif ($show_type === 'only_sm' || $show_type === 'mobile') {
$show_type = 'only_sm';
} else {
$show_type = 'all';
}
$result['show_type'] = $show_type;
// show_id_type: Zibll (show/hide) ↔ AKSRT legacy (include/exclude).
$id_type = (string) ($result['show_id_type'] ?? '');
if ($id_type === 'include') {
$id_type = 'show';
} elseif ($id_type === 'exclude') {
$id_type = 'hide';
} elseif (!in_array($id_type, ['', 'show', 'hide'], true)) {
$id_type = '';
}
$result['show_id_type'] = $id_type;
$result['show_ids'] = sanitize_text_field($result['show_ids'] ?? '');
$result['sidebar_affix'] = !empty($result['sidebar_affix']) || !empty($result['in_affix'] ?? false);
$result['animation_in'] = sanitize_key($result['animation_in'] ?? '');
$result['animation_repeat'] = !empty($result['animation_repeat']);
foreach (['layout_pin_pc', 'layout_pin_m'] as $pin_key) {
$pin = is_array($result[$pin_key] ?? null) ? $result[$pin_key] : [];
$result[$pin_key] = [
'top' => preg_match('/^-?\d+$/', (string) ($pin['top'] ?? '')) ? (string) (int) $pin['top'] : '',
'bottom' => preg_match('/^-?\d+$/', (string) ($pin['bottom'] ?? '')) ? (string) (int) $pin['bottom'] : '',
];
}
$bg = is_array($result['layout_bg'] ?? null) ? $result['layout_bg'] : [];
$result['layout_bg'] = [
'img_white' => aksrt_widget_normalize_bg($bg['img_white'] ?? []),
'img_dark' => aksrt_widget_normalize_bg($bg['img_dark'] ?? []),
];
return $result;
}
}
if (!function_exists('aksrt_widget_normalize_bg')) {
/**
* Normalize a layout_bg sub-block (background CSS props).
*
* @param mixed $raw
* @return array<string,string>
*/
function aksrt_widget_normalize_bg($raw): array {
$raw = is_array($raw) ? $raw : [];
$out = [];
foreach (['background-color', 'background-image', 'background-position', 'background-repeat', 'background-attachment', 'background-size', 'background-origin'] as $k) {
$v = trim((string) ($raw[$k] ?? ''));
if ($k === 'background-image' && $v !== '') {
$v = esc_url_raw($v);
} elseif ($k === 'background-color' && $v !== '') {
// Allow hex / rgb / rgba / named; strip tags defensively.
$v = sanitize_text_field($v);
} else {
$v = sanitize_text_field($v);
}
if ($v !== '') {
$out[$k] = $v;
}
}
return $out;
}
}
if (!function_exists('aksrt_widget_animation_options')) {
/**
* Zibll-aligned module entrance animation options.
*
* @return array<string,string>
*/
function aksrt_widget_animation_options(): array {
return [
'' => __('无动画', 'aksrt'),
'fade' => __('淡入', 'aksrt'),
'slideup' => __('从下往上滑出', 'aksrt'),
'slidedown' => __('从上往下滑出', 'aksrt'),
'slideright' => __('从左往右滑出', 'aksrt'),
'slideleft' => __('从右往左滑出', 'aksrt'),
'zoomin' => __('由小变大', 'aksrt'),
'zoomout' => __('由大变小', 'aksrt'),
];
}
}
if (!function_exists('aksrt_widget_highlight_color_options')) {
/**
* Highlight color palette (Zibll zib_palette parity, theme classes).
*
* @return array<string,string>
*/
function aksrt_widget_highlight_color_options(): array {
return [
'focus-color' => __('主题色', 'aksrt'),
'jb-blue' => __('渐变蓝', 'aksrt'),
'jb-green' => __('渐变绿', 'aksrt'),
'jb-yellow' => __('渐变黄', 'aksrt'),
'jb-orange' => __('渐变橙', 'aksrt'),
'jb-red' => __('渐变红', 'aksrt'),
'jb-purple' => __('渐变紫', 'aksrt'),
'jb-pink' => __('渐变粉', 'aksrt'),
'jb-cyan' => __('渐变青', 'aksrt'),
'c-blue' => __('蓝色', 'aksrt'),
'c-green' => __('绿色', 'aksrt'),
'c-red' => __('红色', 'aksrt'),
'c-yellow' => __('黄色', 'aksrt'),
'c-purple' => __('紫色', 'aksrt'),
];
}
}
if (!function_exists('aksrt_widget_render_link_field')) {
/**
* Render a single title-link sub-field (url/text/target) for the admin form.
*
* @param string $base Field base name, e.g. 'title_link'.
* @param array $link Normalized link {url,text,target}.
* @param string $label Field label.
*/
function aksrt_widget_render_link_field(string $base, array $link, string $label): void {
?>
<p>
<label><?php echo esc_html($label); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($base); ?>[text]" value="<?php echo esc_attr($link['text']); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
</label>
<input class="widefat" type="url" name="<?php echo esc_attr($base); ?>[url]" value="<?php echo esc_attr($link['url']); ?>" placeholder="<?php esc_attr_e('链接地址', 'aksrt'); ?>">
<label><input type="checkbox" name="<?php echo esc_attr($base); ?>[target]" value="_blank" <?php checked($link['target'] === '_blank'); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
}
if (!function_exists('aksrt_widget_render_common_form')) {
/**
* Render shared widget admin form fields.
*
* Call this from your widget's form() method. Renders the full Zibll
* common-field set: title block (subtitle / style / highlight / 3 links),
* sidebar affix, display rules, entrance animation, layout padding + bg.
*
* @param array<string, mixed> $instance Current widget instance.
*/
function aksrt_widget_render_common_form(array $instance): void {
$instance = aksrt_widget_normalize_common($instance);
$pin_pc = $instance['layout_pin_pc'];
$pin_m = $instance['layout_pin_m'];
$bg_w = $instance['layout_bg']['img_white'];
$bg_d = $instance['layout_bg']['img_dark'];
?>
<hr style="margin:16px 0 12px;border-color:#e0e0e0;">
<p style="font-size:12px;color:#666;margin-bottom:8px;font-weight:600;"><?php esc_html_e('标题与链接', 'aksrt'); ?></p>
<p>
<label><?php esc_html_e('副标题', 'aksrt'); ?>
<input class="widefat" type="text" name="subtitle" value="<?php echo esc_attr($instance['subtitle']); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('标题样式', 'aksrt'); ?></label>
<label><input type="radio" name="title_style" value="" <?php checked($instance['title_style'], ''); ?>> <?php esc_html_e('简约', 'aksrt'); ?></label>
<label><input type="radio" name="title_style" value="big" <?php checked($instance['title_style'], 'big'); ?>> <?php esc_html_e('居中大标题', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('标题选字高亮(标题中要被高亮的部分)', 'aksrt'); ?>
<input class="widefat" type="text" name="title_highlight" value="<?php echo esc_attr($instance['title_highlight']); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('高亮颜色', 'aksrt'); ?>
<select class="widefat" name="title_highlight_color">
<?php foreach (aksrt_widget_highlight_color_options() as $val => $lbl) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($instance['title_highlight_color'], $val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<?php aksrt_widget_render_link_field('title_link', $instance['title_link'], __('标题链接', 'aksrt')); ?>
<?php if ($instance['title_style'] !== 'big') : ?>
<?php aksrt_widget_render_link_field('title_link2', $instance['title_link2'], __('标题链接 2', 'aksrt')); ?>
<?php aksrt_widget_render_link_field('title_link3', $instance['title_link3'], __('标题链接 3', 'aksrt')); ?>
<?php endif; ?>
<?php if ($instance['title_style'] === 'big') : ?>
<p>
<label><?php esc_html_e('链接样式(颜色1/2/3)', 'aksrt'); ?></label>
<select name="title_link_style[color_1]">
<?php foreach (aksrt_widget_highlight_color_options() as $val => $lbl) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($instance['title_link_style']['color_1'], $val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
<select name="title_link_style[color_2]">
<?php foreach (aksrt_widget_highlight_color_options() as $val => $lbl) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($instance['title_link_style']['color_2'], $val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
<select name="title_link_style[color_3]">
<?php foreach (aksrt_widget_highlight_color_options() as $val => $lbl) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($instance['title_link_style']['color_3'], $val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
</p>
<?php endif; ?>
<p style="font-size:12px;color:#666;margin:10px 0 8px;font-weight:600;"><?php esc_html_e('显示规则', 'aksrt'); ?></p>
<p>
<label><?php esc_html_e('显示设备', 'aksrt'); ?></label>
<label><input type="radio" name="show_type" value="all" <?php checked($instance['show_type'], 'all'); ?>> <?php esc_html_e('全部', 'aksrt'); ?></label>
<label><input type="radio" name="show_type" value="only_pc" <?php checked($instance['show_type'], 'only_pc'); ?>> <?php esc_html_e('仅 PC', 'aksrt'); ?></label>
<label><input type="radio" name="show_type" value="only_sm" <?php checked($instance['show_type'], 'only_sm'); ?>> <?php esc_html_e('仅移动端', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('按 ID 显示或隐藏', 'aksrt'); ?></label>
<label><input type="radio" name="show_id_type" value="" <?php checked($instance['show_id_type'], ''); ?>> <?php esc_html_e('不限制', 'aksrt'); ?></label>
<label><input type="radio" name="show_id_type" value="show" <?php checked($instance['show_id_type'], 'show'); ?>> <?php esc_html_e('指定 ID 显示', 'aksrt'); ?></label>
<label><input type="radio" name="show_id_type" value="hide" <?php checked($instance['show_id_type'], 'hide'); ?>> <?php esc_html_e('指定 ID 隐藏', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('文章/页面 ID(逗号分隔)', 'aksrt'); ?>
<input class="widefat" type="text" name="show_ids" value="<?php echo esc_attr($instance['show_ids']); ?>" placeholder="1,2,3">
</label>
</p>
<p>
<label><input type="checkbox" name="sidebar_affix" value="1" <?php checked($instance['sidebar_affix']); ?>> <?php esc_html_e('侧栏吸顶(仅侧栏有效)', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
<select class="widefat" name="animation_in">
<?php foreach (aksrt_widget_animation_options() as $val => $lbl) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($instance['animation_in'], $val); ?>><?php echo esc_html($lbl); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="animation_repeat" value="1" <?php checked($instance['animation_repeat']); ?>> <?php esc_html_e('动画重复播放', 'aksrt'); ?></label>
</p>
<p style="font-size:12px;color:#666;margin:10px 0 8px;font-weight:600;"><?php esc_html_e('布局:间距与背景', 'aksrt'); ?></p>
<p>
<label><?php esc_html_e('PC 端额外上/下间距 (px)', 'aksrt'); ?>
<input class="tiny-text" type="number" name="layout_pin_pc[top]" value="<?php echo esc_attr($pin_pc['top']); ?>" placeholder="0">
<input class="tiny-text" type="number" name="layout_pin_pc[bottom]" value="<?php echo esc_attr($pin_pc['bottom']); ?>" placeholder="0">
</label>
</p>
<p>
<label><?php esc_html_e('移动端额外上/下间距 (px)', 'aksrt'); ?>
<input class="tiny-text" type="number" name="layout_pin_m[top]" value="<?php echo esc_attr($pin_m['top']); ?>" placeholder="0">
<input class="tiny-text" type="number" name="layout_pin_m[bottom]" value="<?php echo esc_attr($pin_m['bottom']); ?>" placeholder="0">
</label>
</p>
<p>
<label><?php esc_html_e('日间背景色', 'aksrt'); ?>
<input class="widefat" type="text" name="layout_bg[img_white][background-color]" value="<?php echo esc_attr($bg_w['background-color'] ?? ''); ?>">
</label>
<label><?php esc_html_e('日间背景图', 'aksrt'); ?>
<input class="widefat" type="url" name="layout_bg[img_white][background-image]" value="<?php echo esc_attr($bg_w['background-image'] ?? ''); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('夜间背景色', 'aksrt'); ?>
<input class="widefat" type="text" name="layout_bg[img_dark][background-color]" value="<?php echo esc_attr($bg_d['background-color'] ?? ''); ?>">
</label>
<label><?php esc_html_e('夜间背景图', 'aksrt'); ?>
<input class="widefat" type="url" name="layout_bg[img_dark][background-image]" value="<?php echo esc_attr($bg_d['background-image'] ?? ''); ?>">
</label>
</p>
<?php
}
}
if (!function_exists('aksrt_widget_update_common')) {
/**
* Process shared widget form fields in update().
*
* @param array<string, mixed> $new_instance
* @param array<string, mixed> $old_instance
* @return array<string, mixed>
*/
function aksrt_widget_update_common(array $new_instance, array $old_instance): array {
$merged = array_merge($old_instance, $new_instance);
return aksrt_widget_normalize_common($merged);
}
}
if (!function_exists('aksrt_widget_render_common_before')) {
/**
* Build common wrapper classes/attrs for a widget instance.
*
* Zibll CSF_Widget::wrap_attributes() parity: show-type class, sidebar
* affix data-affix, entrance animation class, layout padding/bg CSS vars.
* Call this at the start of widget() before rendering content.
*
* @param array $args Sidebar args.
* @param array $instance Widget instance.
* @return array<int,string> CSS class names (Zibll show_class style).
*/
function aksrt_widget_get_common_wrapper_classes(array $args, array $instance): array {
$instance = aksrt_widget_normalize_common($instance);
$classes = [];
// Show type: PC or mobile (Zibll hidden-xs / visible-xs-block).
if ($instance['show_type'] === 'only_pc') {
$classes[] = 'hidden-xs';
} elseif ($instance['show_type'] === 'only_sm') {
$classes[] = 'visible-xs-block';
}
// Sidebar affix
if ($instance['sidebar_affix']) {
$classes[] = 'is-affix';
}
// Animation (Zibll obs-animate ani-*).
if ($instance['animation_in'] !== '') {
$classes[] = 'obs-animate';
$classes[] = 'ani-' . $instance['animation_in'];
if ($instance['animation_repeat']) {
$classes[] = 'obs-animate-repeat';
}
}
return $classes;
}
}
if (!function_exists('aksrt_widget_common_wrapper_attr')) {
/**
* Build the full wrapper attribute string (class + style + data-affix) for
* the Zibll .zib-widget-wrap parity layer.
*
* Combine with a widget-specific class via a leading space, or use as-is.
*
* @param array<string,mixed> $instance Widget instance.
* @param string $extra_class Additional class (widget-specific).
* @return string Escaped attribute string beginning with a leading space, e.g. ' class="…" style="…" data-affix="true"'.
*/
function aksrt_widget_common_wrapper_attr(array $instance, string $extra_class = ''): string {
$instance = aksrt_widget_normalize_common($instance);
$classes = ['zib-widget-wrap'];
foreach (aksrt_widget_get_common_wrapper_classes([], $instance) as $c) {
$classes[] = $c;
}
$extra_class = trim($extra_class);
if ($extra_class !== '') {
$classes[] = $extra_class;
}
$styles = [];
if ($instance['layout_pin_pc']['top'] !== '') {
$styles[] = '--pt-pc:' . (int) $instance['layout_pin_pc']['top'] . 'px;';
}
if ($instance['layout_pin_pc']['bottom'] !== '') {
$styles[] = '--pb-pc:' . (int) $instance['layout_pin_pc']['bottom'] . 'px;';
}
if ($instance['layout_pin_m']['top'] !== '') {
$styles[] = '--pt-m:' . (int) $instance['layout_pin_m']['top'] . 'px;';
}
if ($instance['layout_pin_m']['bottom'] !== '') {
$styles[] = '--pb-m:' . (int) $instance['layout_pin_m']['bottom'] . 'px;';
}
foreach (['white' => 'img_white', 'dark' => 'img_dark'] as $mode => $bg_key) {
$bg = $instance['layout_bg'][$bg_key] ?? [];
if (!empty($bg['background-color'])) {
$styles[] = '--bg-color-' . $mode . ':' . $bg['background-color'] . ';';
}
if (!empty($bg['background-image'])) {
$styles[] = '--bg-img-' . $mode . ':url("' . $bg['background-image'] . '");';
}
}
$attr = ' class="' . esc_attr(implode(' ', $classes)) . '"';
if ($styles) {
$attr .= ' style="' . esc_attr(implode('', $styles)) . '"';
}
if ($instance['sidebar_affix']) {
$attr .= ' data-affix="true"';
}
return $attr;
}
}
if (!function_exists('aksrt_widget_affix_attrs')) {
/**
* Compact affix class+attribute helper for widgets that apply affix onto an
* inner wrapper (the two-family pattern from the legacy in_affix widgets).
*
* @param array<string,mixed> $instance Widget instance.
* @return array{class:string,attr:string} class includes leading space when active; attr includes leading space.
*/
function aksrt_widget_affix_attrs(array $instance): array {
$instance = aksrt_widget_normalize_common($instance);
if (empty($instance['sidebar_affix'])) {
return ['class' => '', 'attr' => ''];
}
return ['class' => ' is-affix', 'attr' => ' data-affix="true"'];
}
}
if (!function_exists('aksrt_widget_render_title')) {
/**
* Render the Zibll-aligned widget title block (CSF_Widget::show_title parity).
*
* Two styles:
* - '' (简约): .box-body notop > .title-theme with optional subtitle + more link
* - 'big' (居中大标题): .big-title-wrap with up to 3 button links
*
* Highlight wraps the matched substring in a colored span.
*
* @param array<string,mixed> $instance Widget instance.
* @return string Title HTML (empty when no title).
*/
function aksrt_widget_render_title(array $instance): string {
$instance = aksrt_widget_normalize_common($instance);
$title = trim((string) ($instance['title'] ?? ''));
if ($title === '') {
return '';
}
$highlight = trim((string) $instance['title_highlight']);
$subtitle = (string) $instance['subtitle'];
$anim = function_exists('aksrt_widget_animation_class')
? aksrt_widget_animation_class($instance)
: '';
$title = esc_html($title);
if ($highlight !== '' && $highlight !== $title) {
$color = $instance['title_highlight_color'];
$hl_esc = esc_html($highlight);
$title = str_replace($hl_esc, '<span class="title-highlight ' . esc_attr($color) . '">' . $hl_esc . '</span>', $title);
}
if ($instance['title_style'] === 'big') {
$style = $instance['title_link_style'];
$links = '';
foreach (['title_link' => 'color_1', 'title_link2' => 'color_2', 'title_link3' => 'color_3'] as $key => $color_key) {
$link = $instance[$key];
if ($link['url'] !== '' && $link['text'] !== '') {
$blank = $link['target'] === '_blank' ? ' target="_blank"' : '';
$links .= '<a' . $blank . ' href="' . esc_url($link['url']) . '" class="but ' . esc_attr($style[$color_key]) . '">' . esc_html($link['text']) . '</a>';
}
}
$links = $links !== '' ? '<div class="flex jc big-title-buts' . $anim . '">' . $links . '</div>' : '';
$sub = $subtitle !== '' ? '<p class="big-title-sub muted-2-color' . $anim . '">' . esc_html($subtitle) . '</p>' : '';
return '<div class="big-title-wrap"><h2 class="big-title' . $anim . '">' . $title . '</h2>' . $sub . $links . '</div>';
}
// Simple style.
$link = $instance['title_link'];
$sub = $subtitle !== '' ? '<small class="ml10">' . esc_html($subtitle) . '</small>' : '';
$more = '';
if ($link['url'] !== '') {
$text = $link['text'] !== '' ? $link['text'] : '<i class="fa fa-angle-right fa-fw"></i>' . __('更多', 'aksrt');
$blank = $link['target'] === '_blank' ? ' target="_blank"' : '';
$more = '<div class="pull-right em09 mt3"><a' . $blank . ' href="' . esc_url($link['url']) . '" class="muted-2-color">' . wp_kses_post($text) . '</a></div>';
}
return '<div class="box-body notop' . $anim . '"><div class="title-theme">' . $title . $sub . $more . '</div></div>';
}
}
if (!function_exists('aksrt_widget_animation_class')) {
/**
* Zibll CSF_Widget::animation_class parity: obs-animate ani-* [+repeat].
*
* @param array<string,mixed> $instance Normalized widget instance.
* @param bool $is_title Reserved for title-specific animation.
* @return string
*/
function aksrt_widget_animation_class(array $instance, bool $is_title = false): string {
$animation = (string) ($instance['animation_in'] ?? '');
if ($animation === '') {
return '';
}
$class = ' obs-animate ani-' . $animation;
if (!empty($instance['animation_repeat'])) {
$class .= ' obs-animate-repeat';
}
return $class;
}
}
if (!function_exists('aksrt_widget_should_display_by_ids')) {
/**
* Check if the current post/page should display the widget based on show_id_type and show_ids.
*
* show_id_type tokens: '' (no limit) | 'show' (whitelist) | 'hide' (blacklist).
* Accepts legacy 'include'/'exclude' via normalize_common.
*
* @param array $instance Widget instance.
* @return bool
*/
function aksrt_widget_should_display_by_ids(array $instance): bool {
$instance = aksrt_widget_normalize_common($instance);
if ($instance['show_id_type'] === '' || $instance['show_ids'] === '') {
return true;
}
$current_id = get_the_ID();
if (!$current_id) {
return true; // Not on a singular page, allow display
}
// Zibll splits on comma/Chinese-comma/whitespace/newline.
$ids = array_filter(array_map('absint', preg_split('/[,,\s]+/', $instance['show_ids']) ?: []));
if ($instance['show_id_type'] === 'show') {
return in_array($current_id, $ids, true);
}
if ($instance['show_id_type'] === 'hide') {
return !in_array($current_id, $ids, true);
}
return true;
}
}