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>
This commit is contained in:
@@ -0,0 +1,638 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user