refactor(classes): organize inc/classes into domain folders

Move ~94 root class shells and root *-traits into domain directories
(core, auth, user, credits, content, payment/gateways, forum, shop,
sms, ai, messages, extensions, widgets, settings, admin). Update
bootstrap require paths, co-locate traits with owners, fix forum/user
relative requires, drop duplicate shop controller requires, and wire
home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
kate522 committed 2026-07-17 11:11:57 +08:00
1 parent 2a8fe68048
commit ce1c13b947
115 files changed
+171 -224

No files matched your search

@@ -0,0 +1,564 @@
<?php
/**
* AKSRT 第一批通用小工具 — v4.0 Zibll-aligned
*
* New widgets:
* 1. AKSRT_Search_Widget — 搜索框
* 2. AKSRT_Notice_Widget — 公告通知
* 3. AKSRT_Recent_Comments_Widget — 最新评论
* 4. AKSRT_Friend_Links_Widget — 友情链接
* 5. AKSRT_Icon_Cards_Widget — 图标卡片
* 6. AKSRT_Buttons_Widget — 按钮组
* 7. AKSRT_Custom_HTML_Gap_Widget — 自定义 HTML / 间距
*
* @package AKSRT
* @version 4.0.0
*/
defined('ABSPATH') || exit;
// ============================================================
// 1. 搜索框小工具
// ============================================================
class AKSRT_Search_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
'description' => esc_html__('带样式的搜索框', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
$placeholder = sanitize_text_field($instance['placeholder'] ?? __('搜索文章...', 'aksrt'));
?>
<div class="widget-search-box">
<form role="search" method="get" action="<?php echo esc_url(home_url('/')); ?>">
<div class="widget-search-box__inner">
<input type="search" name="s" class="widget-search-box__input" placeholder="<?php echo esc_attr($placeholder); ?>" value="<?php echo esc_attr(get_search_query()); ?>" autocomplete="off">
<button type="submit" class="widget-search-box__btn" aria-label="<?php esc_attr_e('搜索', 'aksrt'); ?>">
<i class="fas fa-search"></i>
</button>
</div>
</form>
</div>
<?php
echo $args['after_widget'];
}
public function form($instance): void {
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('placeholder'); ?>"><?php esc_html_e('占位文本:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('placeholder'); ?>" name="<?php echo $this->get_field_name('placeholder'); ?>" value="<?php echo esc_attr($instance['placeholder'] ?? __('搜索文章...', 'aksrt')); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'placeholder' => sanitize_text_field($new_instance['placeholder'] ?? ''),
];
}
}
// ============================================================
// 2. 公告通知小工具
// ============================================================
class AKSRT_Notice_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_notice', esc_html__('AKSRT 公告', 'aksrt'), [
'description' => esc_html__('显示公告/通知信息', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$content = wp_kses_post($instance['content'] ?? '');
$style = sanitize_key($instance['style'] ?? 'info');
$icon = sanitize_text_field($instance['icon'] ?? 'fas fa-bullhorn');
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
?>
<div class="widget-notice widget-notice--<?php echo esc_attr($style); ?>">
<?php if ($icon) : ?>
<div class="widget-notice__icon"><i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i></div>
<?php endif; ?>
<div class="widget-notice__content"><?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</div>
<?php
echo $args['after_widget'];
}
public function form($instance): void {
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('content'); ?>"><?php esc_html_e('公告内容:', 'aksrt'); ?></label>
<textarea class="widefat" rows="4" id="<?php echo $this->get_field_id('content'); ?>" name="<?php echo $this->get_field_name('content'); ?>"><?php echo esc_textarea($instance['content'] ?? ''); ?></textarea>
</p>
<p>
<label for="<?php echo $this->get_field_id('style'); ?>"><?php esc_html_e('样式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo $this->get_field_id('style'); ?>" name="<?php echo $this->get_field_name('style'); ?>">
<option value="info" <?php selected($instance['style'] ?? '', 'info'); ?>><?php esc_html_e('信息(蓝色)', 'aksrt'); ?></option>
<option value="success" <?php selected($instance['style'] ?? '', 'success'); ?>><?php esc_html_e('成功(绿色)', 'aksrt'); ?></option>
<option value="warning" <?php selected($instance['style'] ?? '', 'warning'); ?>><?php esc_html_e('警告(黄色)', 'aksrt'); ?></option>
<option value="danger" <?php selected($instance['style'] ?? '', 'danger'); ?>><?php esc_html_e('危险(红色)', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo $this->get_field_id('icon'); ?>"><?php esc_html_e('图标类名:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('icon'); ?>" name="<?php echo $this->get_field_name('icon'); ?>" value="<?php echo esc_attr($instance['icon'] ?? 'fas fa-bullhorn'); ?>">
<small style="color:#666;"><?php esc_html_e('Font Awesome 类名,如: fas fa-bullhorn', 'aksrt'); ?></small>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'content' => wp_kses_post($new_instance['content'] ?? ''),
'style' => in_array($new_instance['style'] ?? '', ['info', 'success', 'warning', 'danger'], true) ? $new_instance['style'] : 'info',
'icon' => sanitize_text_field($new_instance['icon'] ?? 'fas fa-bullhorn'),
];
}
}
// ============================================================
// 3. 最新评论小工具
// ============================================================
class AKSRT_Recent_Comments_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [
'description' => esc_html__('显示最新评论列表', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? __('最新评论', 'aksrt'), $instance, $this->id_base);
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$show_avatar = !empty($instance['show_avatar']);
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
$comments = get_comments([
'number' => $count,
'status' => 'approve',
'post_status' => 'publish',
]);
if ($comments) : ?>
<div class="widget-recent-comments">
<?php foreach ($comments as $comment) :
$post = get_post($comment->comment_post_ID);
if (!$post) continue;
?>
<div class="widget-recent-comments__item">
<?php if ($show_avatar) : ?>
<div class="widget-recent-comments__avatar">
<?php echo get_avatar($comment, 32); ?>
</div>
<?php endif; ?>
<div class="widget-recent-comments__body">
<div class="widget-recent-comments__meta">
<strong class="widget-recent-comments__author"><?php echo esc_html($comment->comment_author); ?></strong>
<span class="widget-recent-comments__time"><?php echo esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))); ?></span>
</div>
<p class="widget-recent-comments__text">
<a href="<?php echo esc_url(get_comment_link($comment)); ?>"><?php echo esc_html(wp_trim_words($comment->comment_content, 15, '...')); ?></a>
</p>
<span class="widget-recent-comments__post">
<?php esc_html_e('于', 'aksrt'); ?> <a href="<?php echo esc_url(get_permalink($post->ID)); ?>"><?php echo esc_html(get_the_title($post->ID)); ?></a>
</span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else : ?>
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无评论', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无评论', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif;
echo $args['after_widget'];
}
public function form($instance): void {
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? __('最新评论', 'aksrt')); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="1" max="20" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" value="<?php echo (int) ($instance['count'] ?? 5); ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo $this->get_field_name('show_avatar'); ?>" value="1" <?php checked(!empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'show_avatar' => !empty($new_instance['show_avatar']),
];
}
}
// ============================================================
// 4. 友情链接小工具
// ============================================================
class AKSRT_Friend_Links_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_friend_links', esc_html__('AKSRT 友情链接', 'aksrt'), [
'description' => esc_html__('显示友情链接列表', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? __('友情链接', 'aksrt'), $instance, $this->id_base);
$show_icon = !empty($instance['show_icon']);
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
$links = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_friend_links() : [];
if (!empty($links)) : ?>
<div class="widget-friend-links" style="--fl-columns:<?php echo (int) $columns; ?>;">
<?php foreach ($links as $link) :
$name = sanitize_text_field($link['name'] ?? '');
$url = esc_url($link['url'] ?? '');
$desc = sanitize_text_field($link['description'] ?? '');
$icon = esc_url($link['icon'] ?? '');
if ($name === '' || $url === '') continue;
?>
<a href="<?php echo $url; ?>" class="widget-friend-links__item" target="_blank" rel="noopener noreferrer" title="<?php echo esc_attr($desc); ?>">
<?php if ($show_icon && $icon) : ?>
<img src="<?php echo $icon; ?>" alt="" class="widget-friend-links__icon" width="16" height="16"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
<?php endif; ?>
<span class="widget-friend-links__name"><?php echo esc_html($name); ?></span>
</a>
<?php endforeach; ?>
</div>
<?php else : ?>
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无友情链接', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无友情链接', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif;
echo $args['after_widget'];
}
public function form($instance): void {
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? __('友情链接', 'aksrt')); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('columns'); ?>"><?php esc_html_e('列数:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="1" max="4" id="<?php echo $this->get_field_id('columns'); ?>" name="<?php echo $this->get_field_name('columns'); ?>" value="<?php echo (int) ($instance['columns'] ?? 2); ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo $this->get_field_name('show_icon'); ?>" value="1" <?php checked(!empty($instance['show_icon'])); ?>> <?php esc_html_e('显示站点图标', 'aksrt'); ?></label>
</p>
<p style="color:#666;font-size:12px;"><?php esc_html_e('链接数据在后台"友链管理"中配置。', 'aksrt'); ?></p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
'show_icon'=> !empty($new_instance['show_icon']),
];
}
}
// ============================================================
// 5. 图标卡片小工具
// ============================================================
class AKSRT_Icon_Cards_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
'description' => esc_html__('显示图标+文字卡片网格', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
if (!empty($cards)) : ?>
<div class="widget-icon-cards" style="--ic-columns:<?php echo (int) $columns; ?>;">
<?php foreach ($cards as $card) :
$card_title = sanitize_text_field($card['title'] ?? '');
$card_icon = sanitize_text_field($card['icon'] ?? '');
$card_link = esc_url($card['link'] ?? '');
$card_desc = sanitize_text_field($card['description'] ?? '');
if ($card_title === '') continue;
$tag = $card_link !== '' ? 'a' : 'div';
$extra = $card_link !== '' ? ' href="' . $card_link . '" target="_blank" rel="noopener"' : '';
?>
<<?php echo $tag; ?> class="widget-icon-cards__item"<?php echo $extra; ?>>
<?php if ($card_icon) : ?>
<div class="widget-icon-cards__icon"><i class="<?php echo esc_attr($card_icon); ?>" aria-hidden="true"></i></div>
<?php endif; ?>
<div class="widget-icon-cards__text">
<div class="widget-icon-cards__title"><?php echo esc_html($card_title); ?></div>
<?php if ($card_desc) : ?>
<div class="widget-icon-cards__desc"><?php echo esc_html($card_desc); ?></div>
<?php endif; ?>
</div>
</<?php echo $tag; ?>>
<?php endforeach; ?>
</div>
<?php endif;
echo $args['after_widget'];
}
public function form($instance): void {
$cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
while (count($cards) < 4) {
$cards[] = ['title' => '', 'icon' => '', 'link' => '', 'description' => ''];
}
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('columns'); ?>"><?php esc_html_e('列数:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="1" max="4" id="<?php echo $this->get_field_id('columns'); ?>" name="<?php echo $this->get_field_name('columns'); ?>" value="<?php echo (int) ($instance['columns'] ?? 2); ?>">
</p>
<hr>
<?php foreach ($cards as $i => $card) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][title]"); ?>" value="<?php echo esc_attr($card['title']); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>"></p>
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][icon]"); ?>" value="<?php echo esc_attr($card['icon']); ?>" placeholder="<?php esc_attr_e('图标类名 (如 fas fa-star)', 'aksrt'); ?>"></p>
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][description]"); ?>" value="<?php echo esc_attr($card['description']); ?>" placeholder="<?php esc_attr_e('描述(可选)', 'aksrt'); ?>"></p>
<p style="margin:0;"><input class="widefat" type="url" name="<?php echo $this->get_field_name("cards[{$i}][link]"); ?>" value="<?php echo esc_attr($card['link']); ?>" placeholder="<?php esc_attr_e('链接(可选)', 'aksrt'); ?>"></p>
</div>
<?php endforeach;
}
public function update($new_instance, $old_instance): array {
$cards = [];
if (isset($new_instance['cards']) && is_array($new_instance['cards'])) {
foreach ($new_instance['cards'] as $card) {
if (!is_array($card)) continue;
$cards[] = [
'title' => sanitize_text_field($card['title'] ?? ''),
'icon' => sanitize_text_field($card['icon'] ?? ''),
'link' => esc_url_raw($card['link'] ?? ''),
'description' => sanitize_text_field($card['description'] ?? ''),
];
}
}
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
'cards' => $cards,
];
}
}
// ============================================================
// 6. 按钮组小工具
// ============================================================
class AKSRT_Buttons_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [
'description' => esc_html__('显示一组自定义按钮', 'aksrt'),
]);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
$layout = sanitize_key($instance['layout'] ?? 'vertical');
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
if (!empty($buttons)) : ?>
<div class="widget-buttons widget-buttons--<?php echo esc_attr($layout); ?>">
<?php foreach ($buttons as $btn) :
$btn_text = sanitize_text_field($btn['text'] ?? '');
$btn_url = esc_url($btn['url'] ?? '');
$btn_style = sanitize_key($btn['style'] ?? 'primary');
$btn_icon = sanitize_text_field($btn['icon'] ?? '');
$btn_target = !empty($btn['new_window']) ? '_blank' : '_self';
if ($btn_text === '' || $btn_url === '') continue;
?>
<a href="<?php echo $btn_url; ?>" class="widget-buttons__btn widget-buttons__btn--<?php echo esc_attr($btn_style); ?>" target="<?php echo esc_attr($btn_target); ?>" rel="noopener">
<?php if ($btn_icon) : ?><i class="<?php echo esc_attr($btn_icon); ?>" aria-hidden="true"></i> <?php endif; ?>
<?php echo esc_html($btn_text); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif;
echo $args['after_widget'];
}
public function form($instance): void {
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
while (count($buttons) < 3) {
$buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'new_window' => false];
}
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('layout'); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo $this->get_field_id('layout'); ?>" name="<?php echo $this->get_field_name('layout'); ?>">
<option value="vertical" <?php selected($instance['layout'] ?? '', 'vertical'); ?>><?php esc_html_e('垂直', 'aksrt'); ?></option>
<option value="horizontal" <?php selected($instance['layout'] ?? '', 'horizontal'); ?>><?php esc_html_e('水平', 'aksrt'); ?></option>
</select>
</p>
<hr>
<?php foreach ($buttons as $i => $btn) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<p style="margin:0 0 4px;">
<input class="widefat" type="text" name="<?php echo $this->get_field_name("buttons[{$i}][text]"); ?>" value="<?php echo esc_attr($btn['text']); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
</p>
<p style="margin:0 0 4px;">
<input class="widefat" type="url" name="<?php echo $this->get_field_name("buttons[{$i}][url]"); ?>" value="<?php echo esc_attr($btn['url']); ?>" placeholder="<?php esc_attr_e('链接地址', 'aksrt'); ?>">
</p>
<p style="margin:0 0 4px;">
<select class="widefat" name="<?php echo $this->get_field_name("buttons[{$i}][style]"); ?>">
<option value="primary" <?php selected($btn['style'] ?? '', 'primary'); ?>><?php esc_html_e('主要', 'aksrt'); ?></option>
<option value="secondary" <?php selected($btn['style'] ?? '', 'secondary'); ?>><?php esc_html_e('次要', 'aksrt'); ?></option>
<option value="outline" <?php selected($btn['style'] ?? '', 'outline'); ?>><?php esc_html_e('线框', 'aksrt'); ?></option>
<option value="text" <?php selected($btn['style'] ?? '', 'text'); ?>><?php esc_html_e('文字', 'aksrt'); ?></option>
</select>
</p>
<p style="margin:0 0 4px;">
<input class="widefat" type="text" name="<?php echo $this->get_field_name("buttons[{$i}][icon]"); ?>" value="<?php echo esc_attr($btn['icon'] ?? ''); ?>" placeholder="<?php esc_attr_e('图标(可选)', 'aksrt'); ?>">
</p>
<p style="margin:0;">
<label><input type="checkbox" name="<?php echo $this->get_field_name("buttons[{$i}][new_window]"); ?>" value="1" <?php checked(!empty($btn['new_window'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
</div>
<?php endforeach;
}
public function update($new_instance, $old_instance): array {
$buttons = [];
if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) {
$allowed_styles = ['primary', 'secondary', 'outline', 'text'];
foreach ($new_instance['buttons'] as $btn) {
if (!is_array($btn)) continue;
$style = sanitize_key($btn['style'] ?? 'primary');
if (!in_array($style, $allowed_styles, true)) $style = 'primary';
$buttons[] = [
'text' => sanitize_text_field($btn['text'] ?? ''),
'url' => esc_url_raw($btn['url'] ?? ''),
'style' => $style,
'icon' => sanitize_text_field($btn['icon'] ?? ''),
'new_window' => !empty($btn['new_window']),
];
}
}
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'layout' => in_array($new_instance['layout'] ?? '', ['vertical', 'horizontal'], true) ? $new_instance['layout'] : 'vertical',
'buttons' => $buttons,
];
}
}
// ============================================================
// 7. 自定义 HTML / 间距小工具
// ============================================================
class AKSRT_Custom_HTML_Gap_Widget extends WP_Widget {
public function __construct() {
parent::__construct('aksrt_custom_html_gap', esc_html__('AKSRT 自定义HTML/间距', 'aksrt'), [
'description' => esc_html__('插入自定义 HTML 或间距', 'aksrt'),
]);
}
public function widget($args, $instance): void {
$mode = sanitize_key($instance['mode'] ?? 'html');
if ($mode === 'gap') {
$height = max(0, min(200, (int) ($instance['gap_height'] ?? 20)));
echo '<div class="widget-custom-gap" style="height:' . (int) $height . 'px;" aria-hidden="true"></div>';
return;
}
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
echo '<div class="widget-custom-html">' . wp_kses_post($instance['content'] ?? '') . '</div>';
echo $args['after_widget'];
}
public function form($instance): void {
$mode = sanitize_key($instance['mode'] ?? 'html');
?>
<p>
<label for="<?php echo $this->get_field_id('mode'); ?>"><?php esc_html_e('模式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo $this->get_field_id('mode'); ?>" name="<?php echo $this->get_field_name('mode'); ?>">
<option value="html" <?php selected($mode, 'html'); ?>><?php esc_html_e('自定义 HTML', 'aksrt'); ?></option>
<option value="gap" <?php selected($mode, 'gap'); ?>><?php esc_html_e('间距', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(仅 HTML 模式,可选):', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('content'); ?>"><?php esc_html_e('HTML 内容:', 'aksrt'); ?></label>
<textarea class="widefat" rows="6" id="<?php echo $this->get_field_id('content'); ?>" name="<?php echo $this->get_field_name('content'); ?>"><?php echo esc_textarea($instance['content'] ?? ''); ?></textarea>
</p>
<p>
<label for="<?php echo $this->get_field_id('gap_height'); ?>"><?php esc_html_e('间距高度(px,仅间距模式):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" max="200" id="<?php echo $this->get_field_id('gap_height'); ?>" name="<?php echo $this->get_field_name('gap_height'); ?>" value="<?php echo (int) ($instance['gap_height'] ?? 20); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'mode' => in_array($new_instance['mode'] ?? '', ['html', 'gap'], true) ? $new_instance['mode'] : 'html',
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'content' => wp_kses_post($new_instance['content'] ?? ''),
'gap_height' => max(0, min(200, (int) ($new_instance['gap_height'] ?? 20))),
];
}
}
@@ -0,0 +1,467 @@
<?php
/**
* AKSRT 新增小工具 — v4.0 Zibll-Inspired
*
* 新增:
* 1. AKSRT_Hot_Posts_Widget — 热榜文章(按浏览/评论/点赞排序)
* 2. AKSRT_Tab_Posts_Widget — 多栏目 Tab 切换文章列表
*
* @package AKSRT
* @version 4.0.0
*/
defined('ABSPATH') || exit;
// ============================================================
// 1. 热榜文章小工具
// ============================================================
class AKSRT_Hot_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_hot_posts',
esc_html__('AKSRT 热榜文章', 'aksrt'),
[
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
$count = absint($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = absint($instance['days'] ?? 30);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
}
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
if ($days > 0 && $days < 365) {
$query_args['date_query'] = [['after' => $days . ' days ago']];
}
switch ($order_by) {
case 'comments':
$query_args['orderby'] = 'comment_count';
$query_args['order'] = 'DESC';
break;
case 'likes':
$query_args['meta_key'] = '_aksrt_like_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'favorites':
$query_args['meta_key'] = '_aksrt_favorite_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'downloads':
$query_args['meta_key'] = '_aksrt_res_download_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
default:
$query_args['meta_key'] = 'aksrt_post_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
}
$query = new WP_Query($query_args);
if ($query->have_posts()) {
echo '<div class="widget-hot-posts widget-hot-posts--mini">';
$index = 0;
while ($query->have_posts()) {
$query->the_post();
$index++;
$rank_class = $index <= 3 ? ' rank-top' : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
?>
<div class="hot-post-item <?php echo esc_attr($rank_class); ?>">
<span class="hot-post-item__rank"><?php echo esc_html($index); ?></span>
<div class="hot-post-item__info">
<a href="<?php the_permalink(); ?>" class="hot-post-item__title"><?php the_title(); ?></a>
<div class="hot-post-item__meta">
<span><i class="fas fa-eye" aria-hidden="true"></i> <?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($views) : (string) $views); ?></span>
<span class="hot-post-item__time"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
</div>
</div>
</div>
<?php
}
echo '</div>';
wp_reset_postdata();
} else {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无热门文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
}
echo $args['after_widget'];
}
public function form($instance): void {
$title = $instance['title'] ?? __('热门文章', 'aksrt');
$count = (int) ($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = (int) ($instance['days'] ?? 30);
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" type="number" min="1" max="20" value="<?php echo $count; ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('order_by'); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo $this->get_field_id('order_by'); ?>" name="<?php echo $this->get_field_name('order_by'); ?>">
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo $this->get_field_id('days'); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('days'); ?>" name="<?php echo $this->get_field_name('days'); ?>" type="number" min="0" max="365" value="<?php echo $days; ?>">
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => absint($new_instance['count'] ?? 5),
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
'days' => absint($new_instance['days'] ?? 30),
];
}
}
// ============================================================
// 2. 多栏目 Tab 切换文章列表小工具
// ============================================================
class AKSRT_Tab_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tab_posts',
esc_html__('AKSRT 多栏目文章', 'aksrt'),
[
'description' => esc_html__('Tab 切换显示不同分类的文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* Normalize tabs from legacy comma-separated string or structured array.
*
* @param mixed $tabs Raw tabs data.
* @return array Normalized structured tabs.
*/
private function normalize_tabs($tabs): array {
// Legacy comma-separated format
if (is_string($tabs) && $tabs !== '') {
$legacy_keys = array_map('trim', explode(',', $tabs));
$legacy_defs = [
'latest' => ['title' => '最新', 'orderby' => 'date'],
'comments' => ['title' => '热评', 'orderby' => 'comments'],
'random' => ['title' => '随机', 'orderby' => 'rand'],
'views' => ['title' => '热门', 'orderby' => 'views'],
];
$result = [];
foreach ($legacy_keys as $key) {
if (isset($legacy_defs[$key])) {
$result[] = [
'title' => __($legacy_defs[$key]['title'], 'aksrt'),
'category_id' => 0,
'orderby' => $legacy_defs[$key]['orderby'],
'count' => 0,
'enabled' => true,
];
} elseif (is_numeric($key) && absint($key) > 0) {
$term = get_term(absint($key), 'category');
$result[] = [
'title' => ($term && !is_wp_error($term)) ? $term->name : '',
'category_id' => absint($key),
'orderby' => 'date',
'count' => 0,
'enabled' => true,
];
}
}
return $result;
}
// Structured array format
if (is_array($tabs)) {
$allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
$result = [];
foreach ($tabs as $tab) {
if (!is_array($tab)) continue;
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date';
$result[] = [
'title' => sanitize_text_field($tab['title'] ?? ''),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'enabled' => !empty($tab['enabled']),
];
}
return $result;
}
return [];
}
/**
* Build WP_Query args for a tab's orderby.
*/
private function build_tab_query_args(array $tab, int $default_count): array {
$count = $tab['count'] > 0 ? $tab['count'] : $default_count;
$args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
if ($tab['category_id'] > 0) {
$args['cat'] = $tab['category_id'];
}
switch ($tab['orderby']) {
case 'views':
$args['meta_key'] = 'aksrt_post_views';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'likes':
$args['meta_key'] = '_aksrt_like_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'favorites':
$args['meta_key'] = '_aksrt_favorite_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'downloads':
$args['meta_key'] = '_aksrt_res_download_count';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'comments':
$args['orderby'] = 'comment_count';
$args['order'] = 'DESC';
break;
case 'rand':
$args['orderby'] = 'rand';
break;
case 'modified':
$args['orderby'] = 'modified';
$args['order'] = 'DESC';
break;
case 'date':
default:
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
}
return $args;
}
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$count = absint($instance['count'] ?? 5);
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
// Filter enabled tabs
$tabs = array_filter($tabs, fn($t) => !empty($t['enabled']));
if (empty($tabs)) {
echo $args['after_widget'];
return;
}
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
}
$widget_id = $this->id;
$use_shared = function_exists('aksrt_render_mini_list');
?>
<div class="widget-tab-posts" id="<?php echo esc_attr($widget_id); ?>-tabs">
<!-- Tab 导航 -->
<ul class="widget-tab-posts__nav" role="tablist">
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
$tab_title = $tab['title'] !== '' ? $tab['title'] : __('Tab ' . ($i + 1), 'aksrt');
?>
<li role="presentation">
<button type="button" role="tab"
class="widget-tab-posts__tab<?php echo $first ? ' active' : ''; ?>"
data-tab="<?php echo esc_attr($tab_key); ?>"
aria-selected="<?php echo $first ? 'true' : 'false'; ?>"
><?php echo esc_html($tab_title); ?></button>
</li>
<?php $first = false; endforeach; ?>
</ul>
<!-- Tab 内容 -->
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
$q = new WP_Query($this->build_tab_query_args($tab, $count));
?>
<div class="widget-tab-posts__panel<?php echo $first ? ' active' : ''; ?>" data-tab-panel="<?php echo esc_attr($tab_key); ?>" role="tabpanel"<?php echo $first ? '' : ' hidden'; ?>>
<?php if ($q->have_posts()) : ?>
<?php if ($use_shared) : ?>
<div class="widget-tab-posts__list widget-tab-posts__list--mini">
<?php aksrt_render_mini_list($q, ['context' => 'sidebar', 'show_thumb' => true, 'show_meta' => true], true); ?>
</div>
<?php else : ?>
<ul class="widget-tab-posts__list">
<?php while ($q->have_posts()) : $q->the_post(); ?>
<li class="widget-tab-posts__item">
<?php if (has_post_thumbnail()) : ?>
<a href="<?php the_permalink(); ?>" class="widget-tab-posts__thumb">
<?php the_post_thumbnail('thumbnail', ['loading' => function_exists('aksrt_is_lazyload_enabled') && aksrt_is_lazyload_enabled('widget') ? 'lazy' : false, 'class' => 'fit-cover']); ?>
</a>
<?php endif; ?>
<div class="widget-tab-posts__info">
<a href="<?php the_permalink(); ?>" class="widget-tab-posts__title"><?php the_title(); ?></a>
<span class="widget-tab-posts__meta"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
</div>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
<?php else : ?>
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无文章', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php endif; ?>
</div>
<?php wp_reset_postdata(); $first = false; endforeach; ?>
</div>
<?php
echo $args['after_widget'];
}
public function form($instance): void {
$title = $instance['title'] ?? '';
$count = (int) ($instance['count'] ?? 5);
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
$orderby_options = [
'date' => __('最新', 'aksrt'),
'modified' => __('更新', 'aksrt'),
'views' => __('浏览', 'aksrt'),
'likes' => __('点赞', 'aksrt'),
'comments' => __('评论', 'aksrt'),
'favorites' => __('收藏', 'aksrt'),
'downloads' => __('下载', 'aksrt'),
'rand' => __('随机', 'aksrt'),
];
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('默认每个 Tab 显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" type="number" min="1" max="20" value="<?php echo $count; ?>">
</p>
<hr style="margin:12px 0;">
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('Tab 配置', 'aksrt'); ?></p>
<?php
// Render each tab as a mini-form
$max_tabs = 8;
$tabs = array_slice($tabs, 0, $max_tabs);
// Ensure at least 3 slots
while (count($tabs) < 3) {
$tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false];
}
foreach ($tabs as $i => $tab) :
?>
<div style="border:1px solid #ddd;padding:8px;margin-bottom:8px;border-radius:4px;background:#fafafa;">
<p style="margin:0 0 6px;">
<label><input type="checkbox" name="<?php echo $this->get_field_name("tabs[{$i}][enabled]"); ?>" value="1" <?php checked(!empty($tab['enabled'])); ?>> <?php printf(esc_html__('Tab %d 启用', 'aksrt'), $i + 1); ?></label>
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" name="<?php echo $this->get_field_name("tabs[{$i}][title]"); ?>" value="<?php echo esc_attr($tab['title']); ?>" placeholder="<?php esc_attr_e('留空则使用分类名', 'aksrt'); ?>">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('分类 ID:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" name="<?php echo $this->get_field_name("tabs[{$i}][category_id]"); ?>" value="<?php echo (int) $tab['category_id']; ?>" placeholder="0">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('排序:', 'aksrt'); ?></label>
<select class="widefat" name="<?php echo $this->get_field_name("tabs[{$i}][orderby]"); ?>">
<?php foreach ($orderby_options as $val => $label) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($tab['orderby'], $val); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</p>
<p style="margin:0;">
<label><?php esc_html_e('数量(0=使用默认):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo $this->get_field_name("tabs[{$i}][count]"); ?>" value="<?php echo (int) $tab['count']; ?>">
</p>
</div>
<?php endforeach;
// Render hidden fields for any remaining tabs to preserve data
for ($i = count($tabs); $i < $max_tabs; $i++) :
?>
<input type="hidden" name="<?php echo $this->get_field_name("tabs[{$i}][enabled]"); ?>" value="">
<?php endfor;
}
public function update($new_instance, $old_instance): array {
$result = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
];
$tabs = [];
if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) {
$allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
foreach ($new_instance['tabs'] as $tab) {
if (!is_array($tab)) continue;
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date';
$tabs[] = [
'title' => sanitize_text_field($tab['title'] ?? ''),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'enabled' => !empty($tab['enabled']),
];
}
}
$result['tabs'] = !empty($tabs) ? $tabs : sanitize_text_field($new_instance['tabs'] ?? 'latest,comments,random');
return $result;
}
}
@@ -0,0 +1,518 @@
<?php
/**
* AKSRT 自定义小工具
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
// ============================================================
// 1. 用户中心小工具 — 替换旧作者信息小工具
// ============================================================
class AKSRT_About_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_about',
esc_html__('AKSRT 用户中心', 'aksrt'),
[
'description' => esc_html__('登录状态面板:未登录显示登录/注册按钮,已登录显示用户信息与快捷入口', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* 前端渲染
*/
public function widget($args, $instance): void {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
}
// 判断登录状态
if (is_user_logged_in()) {
$this->render_logged_in();
} else {
$this->render_logged_out($instance);
}
// 输出内联样式 & JS(仅在本 widget 实例加载时注入一次)
$this->inline_assets();
echo $args['after_widget'];
}
// ==========================================
// 已登录状态渲染(Zibll 风格用户卡片)
// ==========================================
private function render_logged_in(): void {
$u = wp_get_current_user();
$avatar = get_avatar_url($u->ID, ['size' => 160]);
$profile = aksrt_get_user_center_url();
$logout = aksrt_get_logout_url(home_url('/'));
// 封面图:用户在用户中心上传的背景图 > 默认背景图
$cover = get_user_meta($u->ID, 'aksrt_background_image', true);
if (! $cover) {
$cover = get_theme_file_uri('assets/img/person_background.png');
}
?>
<div class="aksrt-user-card">
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
<img class="aksrt-user-card__cover-img" src="<?php echo esc_url($cover); ?>" alt=""<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
</div>
<div class="aksrt-user-card__content">
<div class="aksrt-user-card__avatar">
<img src="<?php echo esc_url($avatar); ?>" alt="" width="80" height="80"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"'; ?>>
</div>
<div class="aksrt-user-card__info">
<div class="aksrt-user-card__name"><?php echo esc_html($u->display_name); ?></div>
<div class="aksrt-user-card__desc"><?php echo esc_html($u->user_email); ?></div>
</div>
<div class="aksrt-user-card__btns">
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="<?php echo esc_url($profile); ?>">
<i class="fas fa-user" aria-hidden="true"></i> <?php esc_html_e('个人主页', 'aksrt'); ?>
</a>
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline" href="<?php echo esc_url($logout); ?>">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i> <?php esc_html_e('退出', 'aksrt'); ?>
</a>
</div>
</div>
</div>
<?php
}
// ==========================================
// 未登录状态渲染(Zibll 风格用户卡片)
// ==========================================
private function render_logged_out(array $instance = []): void {
$login_url = aksrt_get_login_url();
$register_url = aksrt_get_register_url();
// 封面图:后台配置 > 默认
$cover = ! empty($instance['cover']) ? $instance['cover'] : get_theme_file_uri('assets/img/widgets-original.png');
$avatar = get_avatar_url(0, ['size' => 160, 'default' => 'mystery']);
$oauth_html = '';
if (function_exists('AKSRT_OAuth') && method_exists(AKSRT_OAuth::get_instance(), 'get_login_buttons')) {
$oauth_html = AKSRT_OAuth::get_instance()->get_login_buttons();
}
?>
<div class="aksrt-user-card">
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
<?php if ($cover) : ?>
<img class="aksrt-user-card__cover-img" src="<?php echo esc_url($cover); ?>" alt=""<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
<?php endif; ?>
</div>
<div class="aksrt-user-card__content">
<div class="aksrt-user-card__avatar">
<img src="<?php echo esc_url($avatar); ?>" alt="" width="80" height="80"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"'; ?>>
</div>
<div class="aksrt-user-card__info">
<div class="aksrt-user-card__desc"><?php esc_html_e('Hi! 请登录', 'aksrt'); ?></div>
</div>
<div class="aksrt-user-card__btns">
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="<?php echo esc_url($login_url); ?>"><?php esc_html_e('登 录', 'aksrt'); ?></a>
<?php if (get_option('users_can_register')) : ?>
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline" href="<?php echo esc_url($register_url); ?>"><?php esc_html_e('注 册', 'aksrt'); ?></a>
<?php endif; ?>
</div>
<?php if ($oauth_html) : ?>
<div class="aksrt-user-card__oauth">
<div class="aksrt-user-card__divider"><?php esc_html_e('社交账号登录', 'aksrt'); ?></div>
<div class="aksrt-user-card__social-icons">
<?php echo $oauth_html; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
// ==========================================
// 内联样式(Zibll 风格用户卡片,只输出一次)
// ==========================================
private function inline_assets(): void {
if (did_action('aksrt_user_card_assets')) {
return;
}
do_action('aksrt_user_card_assets');
?>
<style>
/* ========== AKSRT 用户卡片(Zibll 风格) ========== */
/*
* 消除"卡片套卡片":
* 外层 .widget-card 提供 background/shadow/border-radius/padding:15px
* 内层 .aksrt-user-card 用负 margin 把封面图扩展到外层边缘,
* 使封面图与外层卡片融为一体,不再有 15px 间距。
*/
.widget-card > .aksrt-user-card {
margin: -15px -15px 0;
overflow: hidden;
border-radius: var(--radius-card, 8px) var(--radius-card, 8px) 0 0;
}
/* 有标题时:取消标题下方的 margin-bottom,因为卡片已无额外间距 */
.widget.aksrt_about.widget-card > .widget-title + .aksrt-user-card {
margin-top: -5px;
}
/* ---- 封面图 ---- */
.aksrt-user-card__cover {
position: relative;
overflow: hidden;
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark, #4f46e5));
}
.aksrt-user-card__cover-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* ---- 内容区 — 悬浮在封面上方 ---- */
.aksrt-user-card__content {
padding: 15px;
margin-top: -55px;
position: relative;
text-align: center;
}
/* ---- 头像(绝对居中) ---- */
.aksrt-user-card__avatar {
text-align: center;
line-height: 0;
}
.aksrt-user-card__avatar img {
display: block;
width: 80px;
height: 80px;
margin: 0 auto;
border-radius: 50%;
border: 3px solid var(--color-primary);
object-fit: cover;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* ---- 用户信息 ---- */
.aksrt-user-card__info {
margin-top: 12px;
text-align: center;
}
.aksrt-user-card__name {
font-size: 1.1em;
font-weight: bold;
color: var(--color-ink, var(--color-text));
}
.aksrt-user-card__desc {
font-size: 13px;
color: var(--color-text-muted, #9ca3af);
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- 按钮组 ---- */
.aksrt-user-card__btns {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 16px;
}
.aksrt-user-card__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 20px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: opacity 0.2s ease, transform 0.15s ease;
flex: 1;
max-width: 140px;
}
.aksrt-user-card__btn:hover {
opacity: 0.88;
transform: translateY(-1px);
}
.aksrt-user-card__btn:active {
transform: translateY(0);
}
.aksrt-user-card__btn--primary {
background: var(--color-primary);
color: #fff;
}
.aksrt-user-card__btn--outline {
background: var(--color-primary-softer, rgba(136,171,218,0.05));
color: var(--color-primary);
border: 1px solid var(--color-primary-50, rgba(136,171,218,0.12));
}
/* ---- OAuth 社交登录 ---- */
.aksrt-user-card__oauth {
margin-top: 12px;
}
.aksrt-user-card__divider {
font-size: 12px;
color: var(--color-text-muted, #9ca3af);
margin-bottom: 10px;
position: relative;
}
.aksrt-user-card__divider::before,
.aksrt-user-card__divider::after {
content: '';
position: absolute;
top: 50%;
width: 28%;
height: 1px;
background: var(--color-border, #e5e7eb);
}
.aksrt-user-card__divider::before { left: 0; }
.aksrt-user-card__divider::after { right: 0; }
.aksrt-user-card__social-icons {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.aksrt-user-card__social-icons a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
font-size: 15px;
color: var(--color-text-muted, #6b7280);
background: var(--color-bg-tertiary, #f3f4f6);
text-decoration: none;
transition: background 0.18s ease, color 0.18s ease;
}
.aksrt-user-card__social-icons a:hover {
background: var(--color-primary);
color: #fff;
}
/* ---- 暗色适配 ---- */
html.dark .aksrt-user-card__avatar img,
body.theme-dark .aksrt-user-card__avatar img {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
html.dark .aksrt-user-card__social-icons a,
body.theme-dark .aksrt-user-card__social-icons a {
background: rgba(255, 255, 255, 0.08);
}
html.dark .aksrt-user-card__social-icons a:hover,
body.theme-dark .aksrt-user-card__social-icons a:hover {
background: var(--color-primary);
}
/* ---- 移动端 ---- */
@media screen and (max-width: 480px) {
.aksrt-user-card__content {
margin-top: -45px;
}
.aksrt-user-card__avatar img {
width: 68px;
height: 68px;
}
}
</style>
<?php
}
// ==========================================
// 后台表单
// ==========================================
public function form($instance): void {
$title = $instance['title'] ?? '';
$cover = $instance['cover'] ?? '';
$default_cover = get_theme_file_uri('assets/img/widgets-original.png');
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('cover'); ?>"><?php esc_html_e('未登录背景图 URL:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('cover'); ?>"
name="<?php echo $this->get_field_name('cover'); ?>"
type="url" value="<?php echo esc_attr($cover); ?>"
placeholder="<?php echo esc_attr($default_cover); ?>">
</p>
<p style="font-size:12px;color:#666;">
<?php esc_html_e('留空则使用默认背景图。已登录用户可使用个人封面图。', 'aksrt'); ?>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'cover' => esc_url_raw($new_instance['cover'] ?? ''),
];
}
}
// ============================================================
// 2. 分类目录小工具
// ============================================================
class AKSRT_Categories_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_categories',
esc_html__('AKSRT 分类目录', 'aksrt'),
array(
'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
'customize_selective_refresh' => true,
)
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
if ($title): ?>
<h3 class="widget-title"><?php echo $title; ?></h3>
<?php endif;
$count = absint($instance['count'] ?: 8);
$cats = get_categories(array('number' => $count, 'orderby' => 'count', 'order' => 'DESC'));
?>
<ul class="widget-list">
<?php foreach ($cats as $cat): ?>
<li>
<a href="<?php echo esc_url(get_category_link($cat->term_id)); ?>"><?php echo esc_html($cat->name); ?></a>
<span><?php echo (int)$cat->count; ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php
echo $args['after_widget'];
}
public function form($instance) {
$title = $instance['title'] ?? __('分类目录', 'aksrt');
$count = isset($instance['count']) ? (int)$instance['count'] : 8;
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>"
name="<?php echo $this->get_field_name('count'); ?>"
type="number" min="1" max="50" value="<?php echo $count; ?>">
</p>
<?php
}
public function update($new, $old) {
return [
'title' => sanitize_text_field($new['title']),
'count' => absint($new['count']),
];
}
}
// ============================================================
// 3. 标签云小工具
// ============================================================
class AKSRT_Tag_Cloud_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tag_cloud',
esc_html__('AKSRT 标签云', 'aksrt'),
array(
'description' => esc_html__('展示文章标签云', 'aksrt'),
'customize_selective_refresh' => true,
)
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
$title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
$count = absint($instance['count'] ?: 15);
if ($title): ?>
<h3 class="widget-title"><?php echo $title; ?></h3>
<?php endif;
?>
<div class="tag-cloud">
<?php
$tags = get_tags(['number' => $count, 'orderby' => 'count', 'order' => 'DESC']);
foreach ($tags as $tag):
$size = 12 + min(4, (int)($tag->count / 5));
?>
<a href="<?php echo esc_url(get_tag_link($tag->term_id)); ?>" style="font-size:<?php echo $size ?>px;">#<?php echo esc_html($tag->name); ?></a>
<?php endforeach; ?>
</div>
<?php
echo $args['after_widget'];
}
public function form($instance) {
$title = $instance['title'] ?? __('标签云', 'aksrt');
$count = isset($instance['count']) ? (int)$instance['count'] : 15;
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('标签数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>"
name="<?php echo $this->get_field_name('count'); ?>"
type="number" min="1" max="50" value="<?php echo $count; ?>">
</p>
<?php
}
public function update($new, $old) {
return [
'title' => sanitize_text_field($new['title']),
'count' => absint($new['count']),
];
}
}
// 注册函数已迁移至 class-aksrt-widgets.php(注册中心)
+726
View File
@@ -0,0 +1,726 @@
<?php
/**
* Widget registration and placement rules.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* Register custom widgets.
*/
function aksrt_register_widgets() {
register_widget('AKSRT_About_Widget');
register_widget('AKSRT_Categories_Widget');
register_widget('AKSRT_Tag_Cloud_Widget');
register_widget('AKSRT_Hot_Posts_Widget');
register_widget('AKSRT_Tab_Posts_Widget');
register_widget('AKSRT_Category_Block_Widget');
register_widget('AKSRT_Category_Tabs_Widget');
register_widget('AKSRT_Category_Recommend_Widget');
// 第一批通用 widget
register_widget('AKSRT_Search_Widget');
register_widget('AKSRT_Notice_Widget');
register_widget('AKSRT_Recent_Comments_Widget');
register_widget('AKSRT_Friend_Links_Widget');
register_widget('AKSRT_Icon_Cards_Widget');
register_widget('AKSRT_Buttons_Widget');
register_widget('AKSRT_Custom_HTML_Gap_Widget');
// 公告跑马灯
register_widget('AKSRT_Widget_Announcement');
// 用户卡片
register_widget('AKSRT_Widget_User_Card');
// 用户排行榜
register_widget('AKSRT_Widget_User_Ranking');
// 图文封面卡片
register_widget('AKSRT_Widget_Cover_Cards');
// 视频播放器
register_widget('AKSRT_Widget_Video_Player');
// 语言切换器
register_widget('AKSRT_Widget_Lang_Switch');
// 一言轮播
register_widget('AKSRT_Widget_Yiyan');
// Shop product widgets (§6.9)
register_widget('AKSRT_Widget_Product_List');
register_widget('AKSRT_Widget_Product_Oneline');
register_widget('AKSRT_Widget_Product_Tab');
aksrt_maybe_repair_sidebar_blog_widgets();
}
add_action('widgets_init', 'aksrt_register_widgets');
if (!function_exists('aksrt_widget_sidebar_area_ids')) {
/**
* Sidebars intended for sidebar-only widgets.
*
* @return array<int, string>
*/
function aksrt_widget_sidebar_area_ids(): array {
return [
'home_sidebar',
'sidebar-blog',
'sidebar-article',
'all_sidebar_top',
'all_sidebar_bottom',
'cat_sidebar',
'tag_sidebar',
'search_sidebar',
];
}
}
if (!function_exists('aksrt_widget_archive_area_ids')) {
/**
* Widget zones for archive pages (cat/tag/search).
*
* @return array<int, string>
*/
function aksrt_widget_archive_area_ids(): array {
return [
'cat_top_fluid',
'cat_top_content',
'cat_bottom_content',
'cat_bottom_fluid',
'cat_sidebar',
'tag_top_fluid',
'tag_top_content',
'tag_bottom_content',
'tag_bottom_fluid',
'tag_sidebar',
'search_top_fluid',
'search_top_content',
'search_bottom_content',
'search_bottom_fluid',
'search_sidebar',
];
}
}
if (!function_exists('aksrt_widget_homepage_area_ids')) {
/**
* Homepage widget zones for homepage-only widgets.
*
* @return array<int, string>
*/
function aksrt_widget_homepage_area_ids(): array {
return [
'home_top_fluid',
'home_top_content',
'home_bottom_content',
'home_bottom_fluid',
];
}
}
if (!function_exists('aksrt_widget_single_area_ids')) {
/**
* Single post/page widget zones.
*
* @return array<int, string>
*/
function aksrt_widget_single_area_ids(): array {
return [
'single_top_fluid',
'single_top_content',
'single_bottom_content',
'single_bottom_fluid',
];
}
}
if (!function_exists('aksrt_widget_base_from_id')) {
/**
* Get widget base id from a stored widget instance id.
*/
function aksrt_widget_base_from_id(string $widget_id): string {
return preg_replace('/-\d+$/', '', $widget_id) ?: $widget_id;
}
}
if (!function_exists('aksrt_widget_allowed_sidebars')) {
/**
* Allowed sidebar ids for a widget base id.
*
* @return array<int, string>
*/
function aksrt_widget_allowed_sidebars(string $widget_base_id): array {
$sidebar_only = [
'aksrt_about',
'aksrt_categories',
'aksrt_tag_cloud',
'aksrt_hot_posts',
];
$homepage_only = [
'aksrt_category_recommend',
];
$hybrid = [
'aksrt_tab_posts',
'aksrt_category_block',
'aksrt_category_tabs',
];
if (in_array($widget_base_id, $sidebar_only, true)) {
return aksrt_widget_sidebar_area_ids();
}
if (in_array($widget_base_id, $homepage_only, true)) {
return aksrt_widget_homepage_area_ids();
}
if (in_array($widget_base_id, $hybrid, true)) {
return array_values(array_unique(array_merge(
aksrt_widget_sidebar_area_ids(),
aksrt_widget_homepage_area_ids()
)));
}
return [];
}
}
if (!function_exists('aksrt_widget_is_allowed_in_sidebar')) {
/**
* Whether a widget base id can be assigned to the given sidebar id.
*/
function aksrt_widget_is_allowed_in_sidebar(string $widget_base_id, string $sidebar_id): bool {
if ($widget_base_id === '' || $sidebar_id === '') {
return true;
}
if ($sidebar_id === 'wp_inactive_widgets' || strpos($sidebar_id, 'orphaned_widgets') === 0) {
return true;
}
$allowed = aksrt_widget_allowed_sidebars($widget_base_id);
if (empty($allowed)) {
return true;
}
return in_array($sidebar_id, $allowed, true);
}
}
if (!function_exists('aksrt_widget_get_area_context')) {
/**
* Resolve widget area context from sidebar args.
*/
function aksrt_widget_get_area_context(array $args): string {
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
if (in_array($sidebar_id, aksrt_widget_sidebar_area_ids(), true)) {
return 'sidebar';
}
if (in_array($sidebar_id, aksrt_widget_homepage_area_ids(), true)) {
return 'homepage';
}
if (in_array($sidebar_id, aksrt_widget_archive_area_ids(), true)) {
return 'archive';
}
if (in_array($sidebar_id, aksrt_widget_single_area_ids(), true)) {
return 'single';
}
return 'other';
}
}
// ============================================================
// Shared Widget Form Helper — 公共表单字段与显示规则
// ============================================================
if (!function_exists('aksrt_widget_common_defaults')) {
/**
* Default values for shared widget instance fields.
*
* @return array<string, mixed>
*/
function aksrt_widget_common_defaults(): array {
return [
'subtitle' => '',
'title_link' => '',
'show_type' => 'all',
'show_id_type' => '',
'show_ids' => '',
'sidebar_affix' => false,
'animation_in' => '',
'animation_repeat'=> false,
];
}
}
if (!function_exists('aksrt_widget_normalize_common')) {
/**
* Normalize common widget instance fields, filling defaults for legacy instances.
*
* @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_link'] = esc_url_raw($result['title_link'] ?? '');
$result['show_type'] = in_array($result['show_type'] ?? '', ['all', 'pc', 'mobile'], true) ? $result['show_type'] : 'all';
$result['show_id_type'] = in_array($result['show_id_type'] ?? '', ['', 'include', 'exclude'], true) ? $result['show_id_type'] : '';
$result['show_ids'] = sanitize_text_field($result['show_ids'] ?? '');
$result['sidebar_affix'] = !empty($result['sidebar_affix']);
$result['animation_in'] = sanitize_key($result['animation_in'] ?? '');
$result['animation_repeat']= !empty($result['animation_repeat']);
return $result;
}
}
if (!function_exists('aksrt_widget_render_common_form')) {
/**
* Render shared widget admin form fields.
*
* Call this from your widget's form() method after rendering custom fields.
*
* @param array<string, mixed> $instance Current widget instance.
*/
function aksrt_widget_render_common_form(array $instance): void {
$instance = aksrt_widget_normalize_common($instance);
?>
<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 for="<?php echo esc_attr($instance['show_type_id'] ?? ''); ?>"><?php esc_html_e('显示设备', 'aksrt'); ?></label><br>
<select name="<?php echo esc_attr($instance['show_type_name'] ?? 'show_type'); ?>" id="<?php echo esc_attr($instance['show_type_id'] ?? ''); ?>" class="widefat">
<option value="all" <?php selected($instance['show_type'], 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
<option value="pc" <?php selected($instance['show_type'], 'pc'); ?>><?php esc_html_e('仅 PC', 'aksrt'); ?></option>
<option value="mobile" <?php selected($instance['show_type'], 'mobile'); ?>><?php esc_html_e('仅移动端', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($instance['show_id_type_id'] ?? ''); ?>"><?php esc_html_e('ID 显示规则', 'aksrt'); ?></label><br>
<select name="<?php echo esc_attr($instance['show_id_type_name'] ?? 'show_id_type'); ?>" id="<?php echo esc_attr($instance['show_id_type_id'] ?? ''); ?>" class="widefat">
<option value="" <?php selected($instance['show_id_type'], ''); ?>><?php esc_html_e('不限制', 'aksrt'); ?></option>
<option value="include" <?php selected($instance['show_id_type'], 'include'); ?>><?php esc_html_e('仅指定 ID 显示', 'aksrt'); ?></option>
<option value="exclude" <?php selected($instance['show_id_type'], 'exclude'); ?>><?php esc_html_e('指定 ID 隐藏', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($instance['show_ids_id'] ?? ''); ?>"><?php esc_html_e('文章/页面 ID', 'aksrt'); ?></label><br>
<input type="text" name="<?php echo esc_attr($instance['show_ids_name'] ?? 'show_ids'); ?>" id="<?php echo esc_attr($instance['show_ids_id'] ?? ''); ?>" class="widefat" value="<?php echo esc_attr($instance['show_ids']); ?>" placeholder="<?php esc_attr_e('逗号分隔,如: 1,2,3', 'aksrt'); ?>">
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($instance['sidebar_affix_name'] ?? 'sidebar_affix'); ?>" value="1" <?php checked($instance['sidebar_affix']); ?>>
<?php esc_html_e('侧栏吸顶', 'aksrt'); ?>
</label>
</p>
<p>
<label for="<?php echo esc_attr($instance['animation_in_id'] ?? ''); ?>"><?php esc_html_e('入场动画', 'aksrt'); ?></label><br>
<select name="<?php echo esc_attr($instance['animation_in_name'] ?? 'animation_in'); ?>" id="<?php echo esc_attr($instance['animation_in_id'] ?? ''); ?>" class="widefat">
<option value="" <?php selected($instance['animation_in'], ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="fadeIn" <?php selected($instance['animation_in'], 'fadeIn'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
<option value="fadeInUp" <?php selected($instance['animation_in'], 'fadeInUp'); ?>><?php esc_html_e('上移淡入', 'aksrt'); ?></option>
<option value="fadeInDown" <?php selected($instance['animation_in'], 'fadeInDown'); ?>><?php esc_html_e('下移淡入', 'aksrt'); ?></option>
<option value="fadeInLeft" <?php selected($instance['animation_in'], 'fadeInLeft'); ?>><?php esc_html_e('左移淡入', 'aksrt'); ?></option>
<option value="fadeInRight" <?php selected($instance['animation_in'], 'fadeInRight'); ?>><?php esc_html_e('右移淡入', 'aksrt'); ?></option>
<option value="zoomIn" <?php selected($instance['animation_in'], 'zoomIn'); ?>><?php esc_html_e('缩放进入', 'aksrt'); ?></option>
</select>
</p>
<p>
<label>
<input type="checkbox" name="<?php echo esc_attr($instance['animation_repeat_name'] ?? 'animation_repeat'); ?>" value="1" <?php checked($instance['animation_repeat']); ?>>
<?php esc_html_e('动画重复播放', 'aksrt'); ?>
</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')) {
/**
* Render common wrapper attributes and classes before widget content.
*
* Outputs CSS classes for animation, affix, show_type, etc.
* Call this at the start of widget() before rendering content.
*
* @param array $args Sidebar args.
* @param array $instance Widget instance.
* @return array Modified args with extra classes.
*/
function aksrt_widget_get_common_wrapper_classes(array $args, array $instance): array {
$instance = aksrt_widget_normalize_common($instance);
$classes = [];
// Show type: PC or mobile
if ($instance['show_type'] === 'pc') {
$classes[] = 'aksrt-widget--pc-only';
} elseif ($instance['show_type'] === 'mobile') {
$classes[] = 'aksrt-widget--mobile-only';
}
// Sidebar affix
if ($instance['sidebar_affix']) {
$classes[] = 'aksrt-widget--affix';
}
// Animation
if ($instance['animation_in'] !== '') {
$classes[] = 'aksrt-widget--animated';
$classes[] = 'aksrt-anim-' . $instance['animation_in'];
if ($instance['animation_repeat']) {
$classes[] = 'aksrt-anim-repeat';
}
}
return $classes;
}
}
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.
*
* @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
}
$ids = array_filter(array_map('absint', explode(',', $instance['show_ids'])));
if ($instance['show_id_type'] === 'include') {
return in_array($current_id, $ids, true);
}
if ($instance['show_id_type'] === 'exclude') {
return !in_array($current_id, $ids, true);
}
return true;
}
}
if (!function_exists('aksrt_widget_render_location_help')) {
/**
* Render placement hint inside widget admin forms.
*/
function aksrt_widget_render_location_help(string $scope): void {
$messages = [
'sidebar' => __('仅限侧栏区域:首页侧栏、博客侧栏、文章侧栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'),
'homepage' => __('仅限首页挂载区域:首页顶部/底部全宽、首页内容上方/下方。', 'aksrt'),
'hybrid' => __('支持侧栏、首页和归档页挂载区域。', 'aksrt'),
'archive' => __('仅限归档页挂载区域:分类页、标签页、搜索页的顶部/底部/侧栏。', 'aksrt'),
];
if (empty($messages[$scope])) {
return;
}
printf(
'<p style="margin-top:10px;font-size:12px;color:#666;">%s</p>',
esc_html($messages[$scope])
);
}
}
if (!function_exists('aksrt_filter_widget_display_by_area')) {
/**
* Prevent widgets from rendering outside their allowed areas,
* and apply per-widget display rules (show_type, show_id_type/show_ids).
*
* @param array|false $instance Widget instance.
* @param WP_Widget $widget Widget object.
* @param array $args Sidebar args.
* @return array|false
*/
function aksrt_filter_widget_display_by_area($instance, WP_Widget $widget, array $args) {
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
$widget_base_id = isset($widget->id_base) ? (string) $widget->id_base : '';
if ($instance === false) {
return false;
}
// Layer 1: Area legality check
if (!aksrt_widget_is_allowed_in_sidebar($widget_base_id, $sidebar_id)) {
return false;
}
// Layer 2: Display rule check (show_type for PC/mobile)
if (is_array($instance) && function_exists('aksrt_widget_normalize_common')) {
$normalized = aksrt_widget_normalize_common($instance);
// Show type: PC / mobile filter
if ($normalized['show_type'] === 'pc' && wp_is_mobile()) {
return false;
}
if ($normalized['show_type'] === 'mobile' && !wp_is_mobile()) {
return false;
}
// Show IDs: whitelist / blacklist filter
if (!aksrt_widget_should_display_by_ids($normalized)) {
return false;
}
}
return $instance;
}
}
add_filter('widget_display_callback', 'aksrt_filter_widget_display_by_area', 10, 3);
if (!function_exists('aksrt_filter_widget_sidebar_assignments')) {
/**
* Move disallowed widget assignments back to the inactive widgets bucket.
*
* @param mixed $new_value New sidebars_widgets option value.
* @param mixed $old_value Previous option value.
* @return mixed
*/
function aksrt_filter_widget_sidebar_assignments($new_value, $old_value) {
if (!is_array($new_value)) {
return $new_value;
}
$removed = [];
foreach ($new_value as $sidebar_id => $widget_ids) {
if (!is_array($widget_ids) || $sidebar_id === 'wp_inactive_widgets') {
continue;
}
$filtered = [];
foreach ($widget_ids as $widget_id) {
if (!is_string($widget_id) || $widget_id === '') {
continue;
}
$widget_base_id = aksrt_widget_base_from_id($widget_id);
if (aksrt_widget_is_allowed_in_sidebar($widget_base_id, (string) $sidebar_id)) {
$filtered[] = $widget_id;
} else {
$removed[] = $widget_id;
}
}
$new_value[$sidebar_id] = array_values($filtered);
}
if (empty($removed)) {
return $new_value;
}
if (!isset($new_value['wp_inactive_widgets']) || !is_array($new_value['wp_inactive_widgets'])) {
$new_value['wp_inactive_widgets'] = [];
}
$assigned = [];
foreach ($new_value as $widget_ids) {
if (!is_array($widget_ids)) {
continue;
}
foreach ($widget_ids as $widget_id) {
if (is_string($widget_id) && $widget_id !== '') {
$assigned[$widget_id] = true;
}
}
}
foreach (array_values(array_unique($removed)) as $widget_id) {
if (!isset($assigned[$widget_id]) && !in_array($widget_id, $new_value['wp_inactive_widgets'], true)) {
$new_value['wp_inactive_widgets'][] = $widget_id;
}
}
return $new_value;
}
}
add_filter('pre_update_option_sidebars_widgets', 'aksrt_filter_widget_sidebar_assignments', 10, 2);
/**
* Ensure widget instance config exists.
*/
function aksrt_ensure_widget_instance(string $option_name, int $number, array $defaults): void {
$instances = get_option($option_name, []);
if (!is_array($instances)) {
$instances = [];
}
if (!isset($instances['_multiwidget'])) {
$instances['_multiwidget'] = 1;
}
if (empty($instances[$number]) || !is_array($instances[$number])) {
$instances[$number] = $defaults;
update_option($option_name, $instances);
}
}
/**
* Repair the sidebar-blog structure and seed default sidebar widgets on first install.
*/
function aksrt_maybe_repair_sidebar_blog_widgets(): void {
$sidebars = get_option('sidebars_widgets', []);
$seeded = (bool) get_option('aksrt_sidebar_blog_seeded', false);
if (!is_array($sidebars)) {
$sidebars = [];
}
$current = $sidebars['sidebar-blog'] ?? null;
$needs_repair = false;
$normalized = [];
if (is_array($current)) {
foreach ($current as $item) {
if (is_array($item)) {
$needs_repair = true;
}
if (is_string($item) && $item !== '') {
$normalized[] = $item;
}
}
}
if ($needs_repair) {
$sidebars['sidebar-blog'] = array_values($normalized);
update_option('sidebars_widgets', $sidebars);
update_option('aksrt_sidebar_blog_seeded', true);
return;
}
if ($current !== null) {
if (!$seeded) {
update_option('aksrt_sidebar_blog_seeded', true);
}
return;
}
if ($seeded) {
return;
}
aksrt_ensure_widget_instance('widget_aksrt_about', 2, [
'title' => '',
]);
aksrt_ensure_widget_instance('widget_aksrt_categories', 2, [
'title' => __('分类目录', 'aksrt'),
'count' => 8,
]);
aksrt_ensure_widget_instance('widget_aksrt_tag_cloud', 2, [
'title' => __('标签云', 'aksrt'),
'count' => 15,
]);
$sidebars['sidebar-blog'] = [
'aksrt_about-2',
'aksrt_categories-2',
'aksrt_tag_cloud-2',
];
update_option('sidebars_widgets', $sidebars);
update_option('aksrt_sidebar_blog_seeded', true);
}
/**
* Register a block editor collection for AKSRT widgets.
*/
add_action('enqueue_block_editor_assets', function () {
$screen = get_current_screen();
if (!$screen || $screen->id !== 'widgets') {
return;
}
wp_add_inline_script('wp-blocks', '
document.addEventListener("DOMContentLoaded", function() {
if (typeof wp !== "undefined" && wp.blocks && wp.blocks.registerBlockCollection) {
wp.blocks.registerBlockCollection("aksrt", {
title: "AKSRT 主题小工具",
icon: undefined
});
}
});
');
});
/**
* Assign AKSRT widgets to the AKSRT block category in the widgets editor.
*
* @param array<string, mixed> $metadata Block metadata.
* @return array<string, mixed>
*/
add_filter('block_type_metadata', function ($metadata) {
$aksrt_widgets = [
'aksrt_about',
'aksrt_categories',
'aksrt_tag_cloud',
];
if (!empty($metadata['name']) && in_array($metadata['name'], $aksrt_widgets, true)) {
$metadata['category'] = 'aksrt';
}
return $metadata;
});
/**
* Show widget placement hints in the admin form.
*/
add_action('in_widget_form', function (WP_Widget $widget): void {
$map = [
'aksrt_about' => 'sidebar',
'aksrt_categories' => 'sidebar',
'aksrt_tag_cloud' => 'sidebar',
'aksrt_hot_posts' => 'sidebar',
'aksrt_tab_posts' => 'hybrid',
'aksrt_category_block' => 'hybrid',
'aksrt_category_tabs' => 'hybrid',
'aksrt_category_recommend' => 'homepage',
];
$scope = $map[$widget->id_base] ?? '';
if ($scope !== '') {
aksrt_widget_render_location_help($scope);
}
}, 10, 1);