From 7a91eb7321d49bc98f672f8c39b3a16348a06106 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:06:20 +0800
Subject: [PATCH 01/18] feat(widgets): user-card guest/login depth, main posts,
term aggregation
1:1 zibll user widget guest shell + checkin/actions, main post list
styles, term aggregation cards, and ranking by check-in days.
---
assets/css/frontend/widget-batch5.css | 229 +++++++++++++++
assets/css/frontend/widgets.css | 1 +
inc/modules/widgets-register.php | 2 +
src/AKSRT/Widgets/WidgetMainPosts.php | 207 +++++++++++++
src/AKSRT/Widgets/WidgetTermAggregation.php | 197 +++++++++++++
src/AKSRT/Widgets/WidgetUserCard.php | 304 ++++++++++++--------
src/AKSRT/Widgets/WidgetUserRanking.php | 70 ++++-
7 files changed, 884 insertions(+), 126 deletions(-)
create mode 100644 assets/css/frontend/widget-batch5.css
create mode 100644 src/AKSRT/Widgets/WidgetMainPosts.php
create mode 100644 src/AKSRT/Widgets/WidgetTermAggregation.php
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
new file mode 100644
index 00000000..63f5d4e2
--- /dev/null
+++ b/assets/css/frontend/widget-batch5.css
@@ -0,0 +1,229 @@
+/**
+ * Batch5: user card guest/login depth, main posts, term aggregation.
+ */
+
+.aksrt-user-card {
+ border-radius: 12px;
+ overflow: hidden;
+ border: 1px solid var(--color-line, #e5e7eb);
+ background: var(--surface-1, #fff);
+ text-align: center;
+ margin-bottom: 12px;
+}
+.aksrt-user-card__cover {
+ height: 90px;
+ background: linear-gradient(135deg, #93c5fd, #c4b5fd) center/cover no-repeat;
+}
+.aksrt-user-card__cover.is-empty {
+ background: linear-gradient(135deg, #e2e8f0, #f8fafc);
+}
+.aksrt-user-card__body { padding: 0 14px 16px; position: relative; }
+.aksrt-user-card__checkin {
+ position: absolute;
+ right: 10px;
+ top: 8px;
+ z-index: 2;
+ max-width: 48%;
+}
+.aksrt-user-card__checkin .but {
+ padding: 4px 8px !important;
+ font-size: 12px !important;
+ line-height: 1.2;
+}
+.aksrt-user-card__checkin .aksrt-header-checkin-details { display: none; }
+.aksrt-user-card__avatar {
+ display: inline-block;
+ margin-top: -36px;
+ width: 72px;
+ height: 72px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 3px solid var(--surface-1, #fff);
+ background: var(--surface-2, #eee);
+}
+.aksrt-user-card__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.aksrt-user-card__name {
+ margin: 10px 0 0;
+ font-size: 16px;
+ font-weight: 700;
+}
+.aksrt-user-card__name a { color: inherit; text-decoration: none; }
+.aksrt-user-card__bio,
+.aksrt-user-card__guest-title {
+ margin: 8px 0 0;
+ font-size: 12px;
+ color: var(--muted-2-color, #888);
+ line-height: 1.5;
+}
+.aksrt-user-card__guest-title { font-size: 14px; font-weight: 600; color: inherit; }
+.aksrt-user-card__stats {
+ display: flex;
+ justify-content: center;
+ gap: 18px;
+ margin-top: 12px;
+}
+.aksrt-user-card__stat-value {
+ display: block;
+ font-size: 16px;
+ font-weight: 700;
+}
+.aksrt-user-card__stat-label {
+ font-size: 11px;
+ color: var(--muted-2-color, #999);
+}
+.aksrt-user-card__actions {
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 14px;
+}
+.aksrt-user-card__btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 96px;
+ padding: 7px 14px;
+ border-radius: 999px;
+ font-size: 13px;
+ text-decoration: none;
+ font-weight: 600;
+}
+.aksrt-user-card__btn--primary {
+ background: var(--color-primary, #3b82f6);
+ color: #fff !important;
+}
+.aksrt-user-card__btn--ghost {
+ background: var(--surface-2, #f3f4f6);
+ color: inherit !important;
+}
+
+/* Main posts */
+.aksrt-main-posts { display: flex; flex-direction: column; gap: 10px; }
+.aksrt-main-posts.style-card {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+@media (max-width: 575.98px) {
+ .aksrt-main-posts.style-card { grid-template-columns: 1fr; }
+}
+.aksrt-main-posts__card {
+ display: block;
+ border-radius: 12px;
+ overflow: hidden;
+ border: 1px solid var(--color-line, #e5e7eb);
+ background: var(--surface-1, #fff);
+ text-decoration: none;
+ color: inherit;
+}
+.aksrt-main-posts__thumb {
+ height: 110px;
+ background: var(--surface-2, #f3f4f6);
+ overflow: hidden;
+}
+.aksrt-main-posts__thumb.mini {
+ width: 56px;
+ height: 56px;
+ border-radius: 8px;
+ flex-shrink: 0;
+}
+.aksrt-main-posts__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.aksrt-main-posts__thumb.is-empty { background: linear-gradient(135deg, #e2e8f0, #f8fafc); }
+.aksrt-main-posts__body { padding: 10px 12px 12px; min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
+.aksrt-main-posts__title {
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1.4;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.aksrt-main-posts__meta {
+ display: flex;
+ gap: 10px;
+ font-size: 11px;
+ color: var(--muted-2-color, #999);
+}
+.aksrt-main-posts__row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: inherit;
+ padding: 4px 0;
+ border-bottom: 1px dashed var(--color-line, #e5e7eb);
+}
+.aksrt-main-posts__row:last-child { border-bottom: 0; }
+.aksrt-main-posts__num {
+ width: 20px;
+ flex-shrink: 0;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--color-primary, #3b82f6);
+ text-align: center;
+}
+
+/* Term aggregation */
+.aksrt-term-agg {
+ display: grid;
+ grid-template-columns: repeat(var(--aksrt-ta-cols, 1), minmax(0, 1fr));
+ gap: 12px;
+}
+.aksrt-term-agg__card {
+ border: 1px solid var(--color-line, #e5e7eb);
+ border-radius: 12px;
+ overflow: hidden;
+ background: var(--surface-1, #fff);
+}
+.aksrt-term-agg__head {
+ display: block;
+ text-decoration: none;
+ color: inherit;
+ position: relative;
+}
+.aksrt-term-agg__cover {
+ height: 96px;
+ background: var(--surface-2, #f3f4f6) center/cover no-repeat;
+}
+.aksrt-term-agg__cover.is-empty {
+ background: linear-gradient(135deg, #dbeafe, #eff6ff);
+}
+.aksrt-term-agg__name {
+ position: absolute;
+ left: 12px;
+ bottom: 10px;
+ font-weight: 700;
+ color: #fff;
+ text-shadow: 0 1px 3px rgba(0,0,0,.45);
+}
+.aksrt-term-agg__count {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ font-size: 11px;
+ padding: 2px 8px;
+ border-radius: 999px;
+ background: rgba(0,0,0,.45);
+ color: #fff;
+}
+.aksrt-term-agg__posts {
+ list-style: none;
+ margin: 0;
+ padding: 8px 12px 10px;
+}
+.aksrt-term-agg__posts li {
+ padding: 6px 0;
+ border-bottom: 1px dashed var(--color-line, #eee);
+}
+.aksrt-term-agg__posts li:last-child { border-bottom: 0; }
+.aksrt-term-agg__posts a {
+ color: inherit;
+ text-decoration: none;
+ font-size: 13px;
+ display: block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
diff --git a/assets/css/frontend/widgets.css b/assets/css/frontend/widgets.css
index 69e0c933..69820df2 100644
--- a/assets/css/frontend/widgets.css
+++ b/assets/css/frontend/widgets.css
@@ -8,3 +8,4 @@
@import url("widget-batch3d.css");
@import url("widget-batch3e.css");
@import url("widget-batch3f.css");
+@import url("widget-batch5.css");
diff --git a/inc/modules/widgets-register.php b/inc/modules/widgets-register.php
index 8ba39126..b0390149 100644
--- a/inc/modules/widgets-register.php
+++ b/inc/modules/widgets-register.php
@@ -74,6 +74,8 @@ function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\WidgetAuthorCard');
register_widget('\AKSRT\Widgets\WidgetOnelinePosts');
register_widget('\AKSRT\Widgets\WidgetTiltCases');
+ register_widget('\AKSRT\Widgets\WidgetMainPosts');
+ register_widget('\AKSRT\Widgets\WidgetTermAggregation');
// Forum widgets
diff --git a/src/AKSRT/Widgets/WidgetMainPosts.php b/src/AKSRT/Widgets/WidgetMainPosts.php
new file mode 100644
index 00000000..f2ce4ff8
--- /dev/null
+++ b/src/AKSRT/Widgets/WidgetMainPosts.php
@@ -0,0 +1,207 @@
+ esc_html__('卡片/迷你/列表样式的文章模块。', 'aksrt'),
+ 'classname' => 'widget_aksrt_main_posts',
+ ]
+ );
+ }
+
+ public function widget($args, $instance): void {
+ $count = max(1, min(30, (int) ($instance['count'] ?? 6)));
+ $style = sanitize_key((string) ($instance['style'] ?? 'list'));
+ if (!in_array($style, ['list', 'card', 'mini'], true)) {
+ $style = 'list';
+ }
+ $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
+ $cat = (int) ($instance['cat'] ?? 0);
+ $limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
+ $show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
+ $show_meta = !isset($instance['show_meta']) || !empty($instance['show_meta']);
+ $show_number = !empty($instance['show_number']);
+
+ $query = [
+ 'post_type' => 'post',
+ 'post_status' => 'publish',
+ 'posts_per_page' => $count,
+ 'ignore_sticky_posts' => true,
+ 'no_found_rows' => true,
+ ];
+ if ($cat > 0) {
+ $query['cat'] = $cat;
+ }
+ if ($limit_day > 0) {
+ $query['date_query'] = [['after' => $limit_day . ' days ago']];
+ }
+ switch ($orderby) {
+ case 'views':
+ $query['meta_key'] = 'aksrt_post_views';
+ $query['orderby'] = 'meta_value_num';
+ $query['order'] = 'DESC';
+ break;
+ case 'comments':
+ $query['orderby'] = 'comment_count';
+ $query['order'] = 'DESC';
+ break;
+ case 'rand':
+ $query['orderby'] = 'rand';
+ break;
+ case 'modified':
+ $query['orderby'] = 'modified';
+ $query['order'] = 'DESC';
+ break;
+ default:
+ $query['orderby'] = 'date';
+ $query['order'] = 'DESC';
+ }
+
+ $q = new \WP_Query($query);
+ if (!$q->have_posts()) {
+ wp_reset_postdata();
+ return;
+ }
+
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ echo '';
+ wp_reset_postdata();
+
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ public function form($instance): void {
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))),
+ 'style' => $style,
+ 'orderby' => $orderby,
+ 'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
+ 'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
+ 'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
+ 'show_meta' => !empty($new_instance['show_meta']) ? 1 : 0,
+ 'show_number' => !empty($new_instance['show_number']) ? 1 : 0,
+ ];
+ }
+}
diff --git a/src/AKSRT/Widgets/WidgetTermAggregation.php b/src/AKSRT/Widgets/WidgetTermAggregation.php
new file mode 100644
index 00000000..be75dcff
--- /dev/null
+++ b/src/AKSRT/Widgets/WidgetTermAggregation.php
@@ -0,0 +1,197 @@
+ esc_html__('分类封面 + 最新文章列表聚合卡。', 'aksrt'),
+ 'classname' => 'widget_aksrt_term_aggregation',
+ ]
+ );
+ }
+
+ public function widget($args, $instance): void {
+ $taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
+ if (!taxonomy_exists($taxonomy)) {
+ $taxonomy = 'category';
+ }
+ $term_ids = $this->parse_ids((string) ($instance['term_ids'] ?? ''));
+ $count = max(1, min(12, (int) ($instance['count'] ?? 4)));
+ $orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
+ $columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
+
+ if (!$term_ids) {
+ // Fallback: top terms by count.
+ $terms = get_terms([
+ 'taxonomy' => $taxonomy,
+ 'number' => max(1, min(4, (int) ($instance['auto_terms'] ?? 2))),
+ 'orderby' => 'count',
+ 'order' => 'DESC',
+ 'hide_empty' => true,
+ ]);
+ if (is_wp_error($terms) || !$terms) {
+ return;
+ }
+ $term_ids = array_map(static fn($t) => (int) $t->term_id, $terms);
+ }
+
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ echo '';
+ foreach ($term_ids as $term_id) {
+ $term = get_term($term_id, $taxonomy);
+ if (!$term || is_wp_error($term)) {
+ continue;
+ }
+ $link = get_term_link($term);
+ if (is_wp_error($link)) {
+ continue;
+ }
+ $cover = '';
+ $img_id = (int) get_term_meta($term_id, 'thumbnail_id', true);
+ if (!$img_id) {
+ $img_id = (int) get_term_meta($term_id, '_aksrt_cover', true);
+ }
+ if ($img_id) {
+ $cover = wp_get_attachment_image_url($img_id, 'medium') ?: '';
+ }
+ if ($cover === '') {
+ $cover = (string) get_term_meta($term_id, '_aksrt_cover_url', true);
+ }
+
+ $posts_q = [
+ 'post_type' => 'post',
+ 'post_status' => 'publish',
+ 'posts_per_page' => $count,
+ 'ignore_sticky_posts' => true,
+ 'no_found_rows' => true,
+ 'tax_query' => [[
+ 'taxonomy' => $taxonomy,
+ 'field' => 'term_id',
+ 'terms' => $term_id,
+ ]],
+ ];
+ if ($orderby === 'views') {
+ $posts_q['meta_key'] = 'aksrt_post_views';
+ $posts_q['orderby'] = 'meta_value_num';
+ $posts_q['order'] = 'DESC';
+ } elseif ($orderby === 'modified') {
+ $posts_q['orderby'] = 'modified';
+ $posts_q['order'] = 'DESC';
+ } else {
+ $posts_q['orderby'] = 'date';
+ $posts_q['order'] = 'DESC';
+ }
+ $q = new \WP_Query($posts_q);
+
+ echo '
';
+ }
+ echo '
';
+
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ /** @return array */
+ private function parse_ids(string $raw): array {
+ if ($raw === '') {
+ return [];
+ }
+ $ids = [];
+ foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
+ $id = (int) $p;
+ if ($id > 0) {
+ $ids[] = $id;
+ }
+ }
+ return array_values(array_unique($ids));
+ }
+
+ public function form($instance): void {
+ $taxs = get_taxonomies(['public' => true], 'objects');
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
+ 'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
+ 'count' => max(1, min(12, (int) ($new_instance['count'] ?? 4))),
+ 'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
+ 'orderby' => $orderby,
+ 'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
+ ];
+ }
+}
diff --git a/src/AKSRT/Widgets/WidgetUserCard.php b/src/AKSRT/Widgets/WidgetUserCard.php
index 4810a259..5be7120f 100644
--- a/src/AKSRT/Widgets/WidgetUserCard.php
+++ b/src/AKSRT/Widgets/WidgetUserCard.php
@@ -1,5 +1,7 @@
__('在侧栏显示当前登录用户或指定用户的信息卡片。', 'aksrt')]
- );
- }
+ public function __construct() {
+ parent::__construct(
+ 'aksrt_user_card',
+ __('用户卡片', 'aksrt'),
+ ['description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到与快捷按钮。', 'aksrt')]
+ );
+ }
- public function widget($args, $instance): void {
- $title = !empty($instance['title']) ? $instance['title'] : '';
- $show_stats = !empty($instance['show_stats']);
- $show_actions = !empty($instance['show_actions']);
+ public function widget($args, $instance): void {
+ $show_cover = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']);
+ $show_stats = !empty($instance['show_stats']) || !isset($instance['show_stats']);
+ $show_actions = !empty($instance['show_button']) || !isset($instance['show_button']);
+ $show_checkin = !empty($instance['show_checkin']) || !isset($instance['show_checkin']);
+ $loged_title = (string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'));
+ $btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'));
+ $btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'));
- // 获取目标用户
- $user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
- if ($user_id <= 0 && is_user_logged_in()) {
- $user_id = get_current_user_id();
- }
+ // Fixed user id wins; else current user.
+ $user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
+ if ($user_id <= 0 && is_user_logged_in()) {
+ $user_id = get_current_user_id();
+ }
- if ($user_id <= 0) return;
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- $user = get_userdata($user_id);
- if (!$user) return;
+ if ($user_id <= 0) {
+ $this->render_guest($show_cover, $loged_title);
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ return;
+ }
- // 获取用户统计
- $post_count = count_user_posts($user_id, 'post', true);
- $comment_count = get_comments(['user_id' => $user_id, 'count' => true]);
- $follower_count = 0;
- $following_count = 0;
- if (class_exists('\AKSRT\User\Follow')) {
- $follower_count = \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
- $following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id);
- }
+ $user = get_userdata($user_id);
+ if (!$user) {
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ return;
+ }
- // 头像
- $avatar_url = function_exists('aksrt_get_avatar_url')
- ? aksrt_get_avatar_url($user_id, ['size' => 144])
- : get_avatar_url($user_id, ['size' => 144]);
+ $is_self = is_user_logged_in() && (int) get_current_user_id() === $user_id;
+ $avatar_url = function_exists('aksrt_get_avatar_url')
+ ? aksrt_get_avatar_url($user_id, ['size' => 144])
+ : get_avatar_url($user_id, ['size' => 144]);
+ $profile_url = function_exists('aksrt_get_user_home_url')
+ ? aksrt_get_user_home_url($user_id)
+ : get_author_posts_url($user_id);
+ $center_url = function_exists('aksrt_get_user_center_url')
+ ? aksrt_get_user_center_url()
+ : $profile_url;
+ $compose_url = home_url('/');
+ // Prefer front submit / write page when available.
+ if (function_exists('get_pages')) {
+ $pages = get_pages(['meta_key' => '_wp_page_template', 'meta_value' => 'templates/pages/page-submit.php', 'number' => 1]);
+ if (!empty($pages[0])) {
+ $compose_url = get_permalink($pages[0]);
+ }
+ }
+ if ($compose_url === home_url('/') && function_exists('aksrt_get_user_center_url')) {
+ $compose_url = aksrt_get_user_center_url('posts');
+ }
+ $bio = get_user_meta($user_id, 'description', true);
+ if (!is_string($bio) || $bio === '') {
+ $bio = (string) $user->description;
+ }
+ $cover = (string) get_user_meta($user_id, 'aksrt_cover', true);
+ if ($cover === '') {
+ $cover = (string) get_user_meta($user_id, 'custom_avatar_cover', true);
+ }
- // 用户主页链接
- $profile_url = function_exists('aksrt_get_user_home_url')
- ? aksrt_get_user_home_url($user_id)
- : get_author_posts_url($user_id);
+ $post_count = count_user_posts($user_id, 'post', true);
+ $follower_count = 0;
+ $following_count = 0;
+ if (class_exists('\AKSRT\User\Follow')) {
+ $follower_count = \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
+ $following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id);
+ }
- // 简介
- $bio = get_user_meta($user_id, 'description', true);
- if (!$bio) $bio = $user->description ?: '';
+ echo '';
- echo $args['before_widget'];
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- if ($title) {
- echo $args['before_title'] . esc_html($title) . $args['after_title'];
- }
+ private function render_guest(bool $show_cover, string $loged_title): void {
+ $login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
+ $register = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
+ $default_avatar = function_exists('get_avatar_url')
+ ? get_avatar_url(0, ['size' => 144, 'force_default' => true])
+ : includes_url('images/blank.gif');
- echo '';
+ echo '
';
+ }
- // 头像
- echo '
';
+ public function form($instance): void {
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
- echo '
' . esc_html($user->display_name) . '';
- echo '';
-
- // 简介
- if ($bio) {
- echo '
' . esc_html(wp_trim_words($bio, 20)) . '
';
- }
-
- // 统计
- if ($show_stats) {
- echo '
';
- echo '
' . number_format_i18n($post_count) . '' . esc_html__('文章', 'aksrt') . '
';
- echo '
' . number_format_i18n($follower_count) . '' . esc_html__('粉丝', 'aksrt') . '
';
- echo '
' . number_format_i18n($following_count) . '' . esc_html__('关注', 'aksrt') . '
';
- echo '
';
- }
-
- // 操作按钮
- if ($show_actions) {
- echo '
';
- }
-
- echo '
';
-
- echo $args['after_widget'];
- }
-
- public function form($instance): void {
- $title = !empty($instance['title']) ? $instance['title'] : '';
- $user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
- $show_stats = !empty($instance['show_stats']);
- $show_actions = !empty($instance['show_actions']);
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)),
+ 'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')),
+ 'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')),
+ 'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')),
+ 'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
+ 'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
+ 'show_button' => !empty($new_instance['show_button']) ? 1 : 0,
+ 'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0,
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/WidgetUserRanking.php b/src/AKSRT/Widgets/WidgetUserRanking.php
index 4094f7a5..c64dd3c2 100644
--- a/src/AKSRT/Widgets/WidgetUserRanking.php
+++ b/src/AKSRT/Widgets/WidgetUserRanking.php
@@ -29,6 +29,13 @@ class WidgetUserRanking extends \WP_Widget {
echo $args['before_widget'];
echo $args['before_title'] . esc_html($title) . $args['after_title'];
+ if (!empty($instance['checkin_btn']) && is_user_logged_in() && function_exists('aksrt_header_user_checkin_btn')) {
+ $cbtn = aksrt_header_user_checkin_btn(get_current_user_id());
+ if ($cbtn !== '') {
+ echo '' . $cbtn . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+ }
+
echo '';
$rank = 0;
foreach ($users as $user) {
@@ -110,6 +117,58 @@ class WidgetUserRanking extends \WP_Widget {
}
break;
+ case 'checkin_all_day':
+ global $wpdb;
+ $rows = $wpdb->get_results($wpdb->prepare(
+ "SELECT user_id, meta_value as val FROM {$wpdb->usermeta}
+ WHERE meta_key IN ('aksrt_checkin_all_day','checkin_all_day')
+ ORDER BY CAST(meta_value AS UNSIGNED) DESC
+ LIMIT %d",
+ $count * 2
+ ), ARRAY_A);
+ $seen = [];
+ foreach ((array) $rows as $row) {
+ $uid = (int) $row['user_id'];
+ if (isset($seen[$uid])) continue;
+ $seen[$uid] = true;
+ $user = get_userdata($uid);
+ if ($user) {
+ $users[] = [
+ 'id' => $uid,
+ 'name' => $user->display_name,
+ 'value' => number_format_i18n((int) $row['val']) . ' 天',
+ ];
+ }
+ if (count($users) >= $count) break;
+ }
+ break;
+
+ case 'checkin_continuous_day':
+ global $wpdb;
+ $rows = $wpdb->get_results($wpdb->prepare(
+ "SELECT user_id, meta_value as val FROM {$wpdb->usermeta}
+ WHERE meta_key IN ('aksrt_checkin_continuous_day','checkin_continuous_day')
+ ORDER BY CAST(meta_value AS UNSIGNED) DESC
+ LIMIT %d",
+ $count * 2
+ ), ARRAY_A);
+ $seen = [];
+ foreach ((array) $rows as $row) {
+ $uid = (int) $row['user_id'];
+ if (isset($seen[$uid])) continue;
+ $seen[$uid] = true;
+ $user = get_userdata($uid);
+ if ($user) {
+ $users[] = [
+ 'id' => $uid,
+ 'name' => $user->display_name,
+ 'value' => number_format_i18n((int) $row['val']) . ' 天',
+ ];
+ }
+ if (count($users) >= $count) break;
+ }
+ break;
+
case 'followers':
if (class_exists('\AKSRT\User\Follow')) {
global $wpdb;
@@ -157,6 +216,8 @@ class WidgetUserRanking extends \WP_Widget {
+
+
@@ -169,15 +230,22 @@ class WidgetUserRanking extends \WP_Widget {
+
+
+
Date: Sun, 19 Jul 2026 01:09:07 +0800
Subject: [PATCH 02/18] feat(widgets): icon cover cards + WP bookmark links
list
Close remaining zibll more-widgets gaps: horizontal icon-cover cards
and native WordPress bookmark/links list with go_link/nofollow.
---
assets/css/frontend/widget-batch5.css | 78 ++++++++++
inc/modules/widgets-register.php | 2 +
src/AKSRT/Widgets/WidgetBookmarkLinks.php | 161 ++++++++++++++++++++
src/AKSRT/Widgets/WidgetIconCoverCards.php | 163 +++++++++++++++++++++
4 files changed, 404 insertions(+)
create mode 100644 src/AKSRT/Widgets/WidgetBookmarkLinks.php
create mode 100644 src/AKSRT/Widgets/WidgetIconCoverCards.php
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index 63f5d4e2..9ea93067 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -227,3 +227,81 @@
overflow: hidden;
text-overflow: ellipsis;
}
+
+
+/* Icon cover cards */
+.aksrt-icon-cover {
+ display: grid;
+ grid-template-columns: repeat(var(--aksrt-icc-pc, 2), minmax(0, 1fr));
+ gap: 10px;
+ --aksrt-icc-icon: 25px;
+}
+@media (max-width: 575.98px) {
+ .aksrt-icon-cover {
+ grid-template-columns: repeat(var(--aksrt-icc-m, 1), minmax(0, 1fr));
+ }
+}
+.aksrt-icon-cover__item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px;
+ border-radius: 12px;
+ border: 1px solid var(--color-line, #e5e7eb);
+ background: var(--surface-1, #fff);
+ text-decoration: none;
+ color: inherit;
+}
+.aksrt-icon-cover__icon {
+ width: calc(var(--aksrt-icc-icon) + 18px);
+ height: calc(var(--aksrt-icc-icon) + 18px);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: var(--aksrt-icc-icon);
+ color: var(--color-primary, #3b82f6);
+ background: var(--surface-2, #f3f4f6);
+ flex-shrink: 0;
+ overflow: hidden;
+}
+.aksrt-icon-cover.is-radius .aksrt-icon-cover__icon { border-radius: 10px; }
+.aksrt-icon-cover__icon img { width: 70%; height: 70%; object-fit: contain; }
+.aksrt-icon-cover__title { font-weight: 700; font-size: 14px; }
+.aksrt-icon-cover__desc { font-size: 12px; color: var(--muted-2-color, #888); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+
+/* Bookmark links */
+.aksrt-bookmark-links {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+}
+.aksrt-bookmark-links.has-box {
+ padding: 10px;
+ border: 1px solid var(--color-line, #e5e7eb);
+ border-radius: 12px;
+}
+.aksrt-bookmark-links.style-simple {
+ grid-template-columns: 1fr;
+}
+.aksrt-bookmark-links__item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px;
+ border-radius: 10px;
+ background: var(--surface-2, #f8fafc);
+ text-decoration: none;
+ color: inherit;
+ min-width: 0;
+}
+.aksrt-bookmark-links__img {
+ width: 36px;
+ height: 36px;
+ border-radius: 8px;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+.aksrt-bookmark-links__text { min-width: 0; display: flex; flex-direction: column; }
+.aksrt-bookmark-links__name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.aksrt-bookmark-links__desc { font-size: 11px; color: var(--muted-2-color, #999); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
diff --git a/inc/modules/widgets-register.php b/inc/modules/widgets-register.php
index b0390149..19a5e2bb 100644
--- a/inc/modules/widgets-register.php
+++ b/inc/modules/widgets-register.php
@@ -76,6 +76,8 @@ function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\WidgetTiltCases');
register_widget('\AKSRT\Widgets\WidgetMainPosts');
register_widget('\AKSRT\Widgets\WidgetTermAggregation');
+ register_widget('\AKSRT\Widgets\WidgetIconCoverCards');
+ register_widget('\AKSRT\Widgets\WidgetBookmarkLinks');
// Forum widgets
diff --git a/src/AKSRT/Widgets/WidgetBookmarkLinks.php b/src/AKSRT/Widgets/WidgetBookmarkLinks.php
new file mode 100644
index 00000000..e3d900e0
--- /dev/null
+++ b/src/AKSRT/Widgets/WidgetBookmarkLinks.php
@@ -0,0 +1,161 @@
+ esc_html__('WordPress 链接(书签)列表,适合友链/导航。', 'aksrt'),
+ 'classname' => 'widget_aksrt_bookmark_links',
+ ]
+ );
+ }
+
+ public function widget($args, $instance): void {
+ $limit = max(0, min(100, (int) ($instance['links_limit'] ?? 12)));
+ $orderby = sanitize_key((string) ($instance['links_orderby'] ?? 'name'));
+ if (!in_array($orderby, ['name', 'updated', 'rating', 'rand', 'id'], true)) {
+ $orderby = 'name';
+ }
+ $order = strtoupper((string) ($instance['links_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC';
+ $type = sanitize_key((string) ($instance['type'] ?? 'card'));
+ if (!in_array($type, ['card', 'image', 'simple'], true)) {
+ $type = 'card';
+ }
+ $go_link = !empty($instance['go_link']);
+ $blank = !isset($instance['blank']) || !empty($instance['blank']);
+ $nofollow = !empty($instance['nofollow']);
+ $show_box = !empty($instance['show_box']);
+
+ $bm_args = [
+ 'orderby' => $orderby === 'rand' ? 'rand' : $orderby,
+ 'order' => $order,
+ 'limit' => $limit > 0 ? $limit : -1,
+ ];
+ $cats = trim((string) ($instance['links_cats'] ?? ''));
+ if ($cats !== '' && $cats !== 'all') {
+ $bm_args['category'] = $cats;
+ }
+ $bookmarks = get_bookmarks($bm_args);
+ if (!$bookmarks) {
+ return;
+ }
+
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ $wrap = 'aksrt-bookmark-links style-' . $type . ($show_box ? ' has-box' : '');
+ echo '';
+
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ public function form($instance): void {
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'links_cats' => sanitize_text_field((string) ($new_instance['links_cats'] ?? '')),
+ 'links_limit' => max(0, min(100, (int) ($new_instance['links_limit'] ?? 12))),
+ 'links_orderby' => sanitize_key((string) ($new_instance['links_orderby'] ?? 'name')),
+ 'links_order' => strtoupper((string) ($new_instance['links_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
+ 'type' => in_array(($new_instance['type'] ?? ''), ['card', 'image', 'simple'], true) ? $new_instance['type'] : 'card',
+ 'blank' => !empty($new_instance['blank']) ? 1 : 0,
+ 'nofollow' => !empty($new_instance['nofollow']) ? 1 : 0,
+ 'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
+ 'show_box' => !empty($new_instance['show_box']) ? 1 : 0,
+ ];
+ }
+}
diff --git a/src/AKSRT/Widgets/WidgetIconCoverCards.php b/src/AKSRT/Widgets/WidgetIconCoverCards.php
new file mode 100644
index 00000000..4aca3f14
--- /dev/null
+++ b/src/AKSRT/Widgets/WidgetIconCoverCards.php
@@ -0,0 +1,163 @@
+ esc_html__('横向图标 + 标题/描述的导航卡片。', 'aksrt'),
+ 'classname' => 'widget_aksrt_icon_cover_cards',
+ ]
+ );
+ }
+
+ public function widget($args, $instance): void {
+ $cards = $this->normalize($instance['cards'] ?? []);
+ if (!$cards) {
+ return;
+ }
+ $cols = max(1, min(4, (int) ($instance['pc_row'] ?? 2)));
+ $m_cols = max(1, min(2, (int) ($instance['m_row'] ?? 1)));
+ $icon_size = max(16, min(48, (int) ($instance['icon_size'] ?? 25)));
+ $radius = !empty($instance['icon_radius4']);
+ $go_link = !empty($instance['go_link']);
+
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ $style = sprintf('--aksrt-icc-pc:%d;--aksrt-icc-m:%d;--aksrt-icc-icon:%dpx;', $cols, $m_cols, $icon_size);
+ echo '';
+ foreach ($cards as $card) {
+ $url = $card['url'];
+ if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
+ $url = aksrt_go_link($url, true);
+ }
+ $tag = $url !== '' ? 'a' : 'div';
+ $href = $url !== '' ? ' href="' . esc_url($url) . '"' . ($card['target'] !== '' ? ' target="' . esc_attr($card['target']) . '" rel="noopener noreferrer"' : '') : '';
+ echo '<' . $tag . ' class="aksrt-icon-cover__item"' . $href . '>';
+ echo '
';
+ if ($card['icon_image'] !== '') {
+ echo '
 . ')
';
+ } elseif ($card['icon'] !== '') {
+ echo '
';
+ }
+ echo '
';
+ echo '
';
+ if ($card['title'] !== '') {
+ echo '
' . esc_html($card['title']) . '
';
+ }
+ if ($card['desc'] !== '') {
+ echo '
' . esc_html($card['desc']) . '
';
+ }
+ echo '
' . $tag . '>';
+ }
+ echo '
';
+
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+
+ /**
+ * @param mixed $raw
+ * @return array>
+ */
+ private function normalize($raw): array {
+ if (is_string($raw)) {
+ $decoded = json_decode($raw, true);
+ $raw = is_array($decoded) ? $decoded : [];
+ }
+ if (!is_array($raw)) {
+ return [];
+ }
+ $out = [];
+ foreach ($raw as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+ $title = sanitize_text_field((string) ($row['title'] ?? ''));
+ $icon = sanitize_text_field((string) ($row['icon'] ?? ''));
+ $icon_image = esc_url_raw(trim((string) ($row['icon_image'] ?? $row['customize_icon'] ?? '')));
+ if ($title === '' && $icon === '' && $icon_image === '') {
+ continue;
+ }
+ $url = '';
+ if (is_array($row['link'] ?? null)) {
+ $url = esc_url_raw(trim((string) ($row['link']['url'] ?? '')));
+ $target = sanitize_text_field((string) ($row['link']['target'] ?? ''));
+ } else {
+ $url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
+ $target = sanitize_text_field((string) ($row['target'] ?? '_blank'));
+ }
+ $out[] = [
+ 'title' => $title,
+ 'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
+ 'icon' => $icon,
+ 'icon_image' => $icon_image,
+ 'url' => $url,
+ 'target' => $target,
+ ];
+ if (count($out) >= 16) {
+ break;
+ }
+ }
+ return $out;
+ }
+
+ public function form($instance): void {
+ $json = is_string($instance['cards'] ?? null)
+ ? (string) $instance['cards']
+ : wp_json_encode($this->normalize($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'cards' => $this->normalize($new_instance['cards'] ?? []),
+ 'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))),
+ 'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))),
+ 'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))),
+ 'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0,
+ 'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
+ ];
+ }
+}
From 491fc6baf4880d428161fa791d62e79691ee9fd3 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:12:10 +0800
Subject: [PATCH 03/18] feat(widgets): search hot keywords + multi-item scroll
notice
Deepen zibll search/notice widgets: hot keywords from settings,
custom keywords, more-link, and multi-message marquee notice bar.
---
assets/css/frontend/widget-batch5.css | 62 ++++++++
src/AKSRT/Widgets/NoticeWidget.php | 215 +++++++++++++++++++-------
src/AKSRT/Widgets/SearchWidget.php | 155 ++++++++++++++-----
3 files changed, 334 insertions(+), 98 deletions(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index 9ea93067..1932594b 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -305,3 +305,65 @@
.aksrt-bookmark-links__text { min-width: 0; display: flex; flex-direction: column; }
.aksrt-bookmark-links__name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aksrt-bookmark-links__desc { font-size: 11px; color: var(--muted-2-color, #999); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+
+
+/* Search keywords */
+.aksrt-search-widget__keywords { margin-top: 10px; }
+.aksrt-search-widget__kw-title {
+ font-size: 12px;
+ color: var(--muted-2-color, #888);
+ margin-bottom: 6px;
+}
+.aksrt-search-widget__kw-list { display: flex; flex-wrap: wrap; gap: 6px; }
+.aksrt-search-widget__kw {
+ display: inline-block;
+ padding: 3px 10px;
+ border-radius: 999px;
+ background: var(--surface-2, #f3f4f6);
+ color: inherit;
+ text-decoration: none;
+ font-size: 12px;
+}
+.aksrt-search-widget__more { margin-top: 8px; text-align: right; font-size: 12px; }
+.aksrt-search-widget__more a { color: var(--color-primary, #3b82f6); text-decoration: none; }
+
+/* Scroll notice */
+.aksrt-scroll-notice {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 14px;
+ border-radius: 10px;
+ background: #eff6ff;
+ color: #1d4ed8;
+ margin-bottom: 8px;
+ overflow: hidden;
+}
+.aksrt-scroll-notice.is-radius { border-radius: 999px; }
+.aksrt-scroll-notice.color-c-green { background: #ecfdf5; color: #047857; }
+.aksrt-scroll-notice.color-c-yellow { background: #fffbeb; color: #b45309; }
+.aksrt-scroll-notice.color-c-red { background: #fef2f2; color: #b91c1c; }
+.aksrt-scroll-notice.color-c-purple { background: #f5f3ff; color: #6d28d9; }
+.aksrt-scroll-notice__icon { flex-shrink: 0; }
+.aksrt-scroll-notice__viewport { position: relative; flex: 1; min-width: 0; height: 1.4em; }
+.aksrt-scroll-notice__item {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ transform: translateY(8px);
+ transition: opacity .35s ease, transform .35s ease;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 13px;
+ line-height: 1.4;
+}
+.aksrt-scroll-notice__item.is-active {
+ opacity: 1;
+ transform: translateY(0);
+}
+.aksrt-scroll-notice__item a {
+ color: inherit;
+ text-decoration: none;
+}
+.aksrt-scroll-notice__item i { margin-right: 6px; opacity: .85; }
diff --git a/src/AKSRT/Widgets/NoticeWidget.php b/src/AKSRT/Widgets/NoticeWidget.php
index 08a03955..18ff6ebc 100644
--- a/src/AKSRT/Widgets/NoticeWidget.php
+++ b/src/AKSRT/Widgets/NoticeWidget.php
@@ -1,5 +1,7 @@
esc_html__('显示公告/通知信息', 'aksrt'),
- ]);
- }
+ 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');
+ public function widget($args, $instance): void {
+ $items = $this->normalize_items($instance);
+ if (!$items) {
+ return;
+ }
- if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- 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'),
- ];
- }
+ $uid = 'aksrt-notice-' . esc_attr($this->id);
+ $class = 'aksrt-scroll-notice color-' . $color . ($radius ? ' is-radius' : '');
+ echo '';
+
+ if (count($items) > 1) {
+ ?>
+
+ $instance
+ * @return array
+ */
+ private function normalize_items(array $instance): array {
+ $raw = $instance['items'] ?? $instance['img_ids'] ?? null;
+ if ($raw === null && !empty($instance['content'])) {
+ // Legacy single content.
+ return [[
+ 'title' => wp_strip_all_tags((string) $instance['content']),
+ 'icon' => sanitize_text_field((string) ($instance['icon'] ?? 'fas fa-bullhorn')),
+ 'href' => '',
+ ]];
+ }
+ if (is_string($raw)) {
+ $decoded = json_decode($raw, true);
+ $raw = is_array($decoded) ? $decoded : [];
+ }
+ if (!is_array($raw)) {
+ return [];
+ }
+ $out = [];
+ foreach ($raw as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+ $title = sanitize_text_field((string) ($row['title'] ?? $row['content'] ?? ''));
+ if ($title === '') {
+ continue;
+ }
+ $out[] = [
+ 'title' => $title,
+ 'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
+ 'href' => esc_url_raw(trim((string) ($row['href'] ?? $row['url'] ?? $row['link'] ?? ''))),
+ ];
+ if (count($out) >= 12) {
+ break;
+ }
+ }
+ return $out;
+ }
+
+ public function form($instance): void {
+ $items = $this->normalize_items($instance);
+ if (!$items) {
+ $items = [
+ ['title' => '', 'icon' => 'fas fa-bullhorn', 'href' => ''],
+ ['title' => '', 'icon' => 'fas fa-info-circle', 'href' => ''],
+ ];
+ }
+ $json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'items' => $this->normalize_items(['items' => $new_instance['items'] ?? []]),
+ 'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
+ 'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'c-blue')),
+ 'blank' => !empty($new_instance['blank']) ? 1 : 0,
+ 'radius' => !empty($new_instance['radius']) ? 1 : 0,
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/SearchWidget.php b/src/AKSRT/Widgets/SearchWidget.php
index eca6bc69..c986b90b 100644
--- a/src/AKSRT/Widgets/SearchWidget.php
+++ b/src/AKSRT/Widgets/SearchWidget.php
@@ -1,5 +1,7 @@
esc_html__('带样式的搜索框', 'aksrt'),
- ]);
- }
+ 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'));
- ?>
-
- id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- public function form($instance): void {
- ?>
-
-
-
-
-
-
-
-
- sanitize_text_field($new_instance['title'] ?? ''),
- 'placeholder' => sanitize_text_field($new_instance['placeholder'] ?? ''),
- ];
- }
+ $keywords = [];
+ if ($show_kw) {
+ if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'hot_search_keywords')) {
+ $keywords = \AKSRT\Settings\Settings::hot_search_keywords();
+ } elseif (function_exists('aksrt_get_setting')) {
+ $raw = (string) aksrt_get_setting('hot_search_keywords', '');
+ $keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $raw) ?: [])));
+ }
+ $custom = trim((string) ($instance['keywords'] ?? ''));
+ if ($custom !== '') {
+ $keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $custom) ?: [])));
+ }
+ $keywords = array_slice($keywords, 0, 12);
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'placeholder' => sanitize_text_field((string) ($new_instance['placeholder'] ?? '')),
+ 'show_keywords' => !empty($new_instance['show_keywords']) ? 1 : 0,
+ 'keywords_title' => sanitize_text_field((string) ($new_instance['keywords_title'] ?? '')),
+ 'keywords' => sanitize_textarea_field((string) ($new_instance['keywords'] ?? '')),
+ 'more_but' => sanitize_text_field((string) ($new_instance['more_but'] ?? '')),
+ 'more_but_url' => esc_url_raw(trim((string) ($new_instance['more_but_url'] ?? ''))),
+ ];
+ }
}
From e2decd8d71f7a3bc926a77f8547af489469bf48e Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:14:38 +0800
Subject: [PATCH 04/18] feat(widgets): friend-links bookmarks source, hot
thumbs, cover video
Deepen existing widgets toward zibll: WP bookmarks source for friend
links, optional hot-post thumbnails, and video backgrounds on cover
cards.
---
assets/css/frontend/widget-batch5.css | 10 ++
src/AKSRT/Widgets/FriendLinksWidget.php | 188 +++++++++++++++++-------
src/AKSRT/Widgets/HotPostsWidget.php | 14 +-
src/AKSRT/Widgets/WidgetCoverCards.php | 7 +-
4 files changed, 160 insertions(+), 59 deletions(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index 1932594b..bfcfd826 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -367,3 +367,13 @@
text-decoration: none;
}
.aksrt-scroll-notice__item i { margin-right: 6px; opacity: .85; }
+
+.hot-post-item { display: flex; align-items: center; gap: 10px; }
+.hot-post-item__thumb {
+ width: 48px; height: 48px; border-radius: 8px; overflow: hidden; flex-shrink: 0;
+ background: var(--surface-2, #eee);
+}
+.hot-post-item__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.aksrt-cover-card__bg.is-video video {
+ width: 100%; height: 100%; object-fit: cover; display: block;
+}
diff --git a/src/AKSRT/Widgets/FriendLinksWidget.php b/src/AKSRT/Widgets/FriendLinksWidget.php
index 3114456c..b4b9de51 100644
--- a/src/AKSRT/Widgets/FriendLinksWidget.php
+++ b/src/AKSRT/Widgets/FriendLinksWidget.php
@@ -1,5 +1,7 @@
esc_html__('显示友情链接列表', 'aksrt'),
- ]);
- }
+ public function __construct() {
+ parent::__construct('aksrt_friend_links', esc_html__('AKSRT 友情链接', 'aksrt'), [
+ 'description' => esc_html__('显示友情链接(设置项或 WordPress 书签)。', '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)));
+ public function widget($args, $instance): void {
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? __('友情链接', 'aksrt')), $instance, $this->id_base);
+ $show_icon = !empty($instance['show_icon']) || !isset($instance['show_icon']);
+ $columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
+ $source = sanitize_key((string) ($instance['source'] ?? 'settings'));
+ $go_link = !empty($instance['go_link']);
+ $limit = max(1, min(100, (int) ($instance['limit'] ?? 24)));
- if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
+ if ($title) {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- $links = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_friend_links() : [];
+ $links = $this->resolve_links($source, $instance, $limit);
+ if (!$links) {
+ echo function_exists('aksrt_get_widget_null')
+ ? aksrt_get_widget_null(__('暂无友情链接', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ : '' . esc_html__('暂无友情链接', 'aksrt') . '
';
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ return;
+ }
+ ?>
+
+
-
-
- ' . esc_html__('暂无友情链接', 'aksrt') . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
- $instance
+ * @return array>
+ */
+ private function resolve_links(string $source, array $instance, int $limit): array {
+ if ($source === 'bookmarks') {
+ $bm_args = [
+ 'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'name')) ?: 'name',
+ 'order' => strtoupper((string) ($instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
+ 'limit' => $limit,
+ ];
+ $cats = trim((string) ($instance['cats'] ?? ''));
+ if ($cats !== '') {
+ $bm_args['category'] = $cats;
+ }
+ $out = [];
+ foreach (get_bookmarks($bm_args) as $b) {
+ $out[] = [
+ 'name' => (string) $b->link_name,
+ 'url' => (string) $b->link_url,
+ 'description' => (string) $b->link_description,
+ 'icon' => (string) $b->link_image,
+ ];
+ }
+ return $out;
+ }
- echo $args['after_widget'];
- }
+ $links = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_friend_links() : [];
+ if (!is_array($links) || !$links) {
+ // Fallback to WP bookmarks when settings empty.
+ return $this->resolve_links('bookmarks', $instance, $limit);
+ }
+ return array_slice(array_values($links), 0, $limit);
+ }
- public function form($instance): void {
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field($new_instance['title'] ?? ''),
- 'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
- 'show_icon'=> !empty($new_instance['show_icon']),
- ];
- }
+ public function update($new_instance, $old_instance): array {
+ return [
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'source' => ($new_instance['source'] ?? '') === 'bookmarks' ? 'bookmarks' : 'settings',
+ 'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
+ 'limit' => max(1, min(100, (int) ($new_instance['limit'] ?? 24))),
+ 'cats' => sanitize_text_field((string) ($new_instance['cats'] ?? '')),
+ 'show_icon' => !empty($new_instance['show_icon']) ? 1 : 0,
+ 'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
+ 'orderby' => sanitize_key((string) ($new_instance['orderby'] ?? 'name')),
+ 'order' => strtoupper((string) ($new_instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/HotPostsWidget.php b/src/AKSRT/Widgets/HotPostsWidget.php
index 0ce3448e..0c52ba49 100644
--- a/src/AKSRT/Widgets/HotPostsWidget.php
+++ b/src/AKSRT/Widgets/HotPostsWidget.php
@@ -27,6 +27,7 @@ class HotPostsWidget extends \WP_Widget {
$count = absint($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = absint($instance['days'] ?? 30);
+ $show_thumb = !empty($instance['show_thumb']);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
@@ -85,6 +86,13 @@ class HotPostsWidget extends \WP_Widget {
?>
+
+
+
+
+
@@ -132,7 +140,11 @@ class HotPostsWidget extends \WP_Widget {
-
+
+
+';
- if ($image) {
+ if ($video) {
+ echo '
';
+ echo '';
+ echo '
';
+ } elseif ($image) {
echo '
';
echo '
 . ')
';
echo '
';
From aabc1cd1429623e156de7c7dcc2907c2761df3ae Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:17:41 +0800
Subject: [PATCH 05/18] feat(widgets): deepen buttons, video player, recent
comments
Add go_link/hollow/radius/icon slots for buttons; loop/poster/hide
controls for video player; exclude post/keywords on recent comments.
---
assets/css/frontend/widget-batch5.css | 76 ++++++
src/AKSRT/Widgets/ButtonsWidget.php | 257 +++++++++++++--------
src/AKSRT/Widgets/RecentCommentsWidget.php | 176 ++++++++------
src/AKSRT/Widgets/WidgetVideoPlayer.php | 223 +++++++++---------
4 files changed, 455 insertions(+), 277 deletions(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index bfcfd826..95fc7429 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -377,3 +377,79 @@
.aksrt-cover-card__bg.is-video video {
width: 100%; height: 100%; object-fit: cover; display: block;
}
+
+.widget-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--btn-gap, 10px);
+ margin-bottom: 8px;
+}
+.widget-buttons--vertical { flex-direction: column; }
+.widget-buttons.align-center { justify-content: center; }
+.widget-buttons.align-left { justify-content: flex-start; }
+.widget-buttons.align-right { justify-content: flex-end; }
+.widget-buttons__btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 8px 16px;
+ border-radius: 8px;
+ text-decoration: none;
+ font-size: 13px;
+ font-weight: 600;
+ background: var(--color-primary, #3b82f6);
+ color: #fff !important;
+}
+.widget-buttons.is-radius .widget-buttons__btn { border-radius: 999px; }
+.widget-buttons.is-hollow .widget-buttons__btn {
+ background: transparent;
+ color: var(--color-primary, #3b82f6) !important;
+ border: 1px solid currentColor;
+}
+.widget-buttons__btn--secondary { background: #64748b; }
+.widget-buttons__btn--outline {
+ background: transparent;
+ color: inherit !important;
+ border: 1px solid var(--color-line, #ddd);
+}
+.widget-buttons__btn--text {
+ background: transparent;
+ color: var(--color-primary, #3b82f6) !important;
+ padding-left: 0;
+ padding-right: 0;
+}
+.widget-buttons__btn--pink { background: #ec4899; }
+.widget-buttons__btn--blue { background: #3b82f6; }
+.widget-buttons__btn--green { background: #22c55e; }
+
+.aksrt-video-widget {
+ position: relative;
+ width: 100%;
+ height: 0;
+ border-radius: 8px;
+ overflow: hidden;
+ background: #111;
+ margin-bottom: 10px;
+}
+.aksrt-video-widget iframe,
+.aksrt-video-widget video {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ object-fit: cover;
+}
+.aksrt-video-widget.controller-hide video::-webkit-media-controls {
+ display: none !important;
+}
+
+.widget-recent-comments__post {
+ margin-top: 2px;
+ font-size: 11px;
+}
+.widget-recent-comments__post a {
+ color: var(--muted-2-color, #999);
+ text-decoration: none;
+}
diff --git a/src/AKSRT/Widgets/ButtonsWidget.php b/src/AKSRT/Widgets/ButtonsWidget.php
index cadfde31..20eb79b2 100644
--- a/src/AKSRT/Widgets/ButtonsWidget.php
+++ b/src/AKSRT/Widgets/ButtonsWidget.php
@@ -1,5 +1,7 @@
esc_html__('显示一组自定义按钮', 'aksrt'),
- ]);
- }
+ 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');
+ public function widget($args, $instance): void {
+ $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
+ $layout = sanitize_key((string) ($instance['layout'] ?? 'horizontal'));
+ if (!in_array($layout, ['vertical', 'horizontal'], true)) {
+ $layout = 'horizontal';
+ }
+ $align = sanitize_key((string) ($instance['align'] ?? 'center'));
+ if (!in_array($align, ['left', 'center', 'right'], true)) {
+ $align = 'center';
+ }
+ $radius = !empty($instance['radius']);
+ $hollow = !empty($instance['hollow']);
+ $go_link = !empty($instance['go_link']);
+ $gap = max(4, min(30, (int) ($instance['gap'] ?? 10)));
- if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
+ $items = [];
+ foreach ($buttons as $btn) {
+ if (!is_array($btn)) {
+ continue;
+ }
+ $text = sanitize_text_field((string) ($btn['text'] ?? ''));
+ $url = esc_url_raw(trim((string) ($btn['url'] ?? '')));
+ if ($text === '') {
+ continue;
+ }
+ if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
+ $url = aksrt_go_link($url, true);
+ }
+ $items[] = [
+ 'text' => $text,
+ 'url' => $url,
+ 'style' => sanitize_key((string) ($btn['style'] ?? 'primary')),
+ 'icon' => sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? '')),
+ 'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')),
+ 'new_window' => !empty($btn['new_window']) || !empty($btn['target']),
+ ];
+ }
+ if (!$items) {
+ return;
+ }
- if (!empty($buttons)) : ?>
-
- id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- echo $args['after_widget'];
- }
+ $wrap = 'widget-buttons widget-buttons--' . $layout . ' align-' . $align;
+ if ($radius) {
+ $wrap .= ' is-radius';
+ }
+ if ($hollow) {
+ $wrap .= ' is-hollow';
+ }
+ echo '';
+ foreach ($items as $btn) {
+ $style = in_array($btn['style'], ['primary', 'secondary', 'outline', 'text', 'pink', 'blue', 'green', 'yellow', 'purple'], true)
+ ? $btn['style']
+ : 'primary';
+ $cls = 'widget-buttons__btn widget-buttons__btn--' . $style;
+ $href = $btn['url'] !== '' ? $btn['url'] : 'javascript:void(0)';
+ $tgt = $btn['new_window'] && $btn['url'] !== '' ? ' target="_blank" rel="noopener noreferrer"' : '';
+ echo '
';
+ if ($btn['icon'] !== '') {
+ echo ' ';
+ }
+ echo esc_html($btn['text']);
+ if ($btn['icon_right'] !== '') {
+ echo ' ';
+ }
+ echo '';
+ }
+ echo '
';
- 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];
- }
- ?>
-
-
-
-
-
-
-
-
-
- $btn) : ?>
-
- 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,
- ];
- }
+ public function form($instance): void {
+ $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
+ while (count($buttons) < 4) {
+ $buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'icon_right' => '', 'new_window' => false];
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ $btn) : ?>
+
+ " value="" placeholder="">
+ " value="" placeholder="">
+ " value="" placeholder="">
+ " value="" placeholder="">
+
+
+
+ $text,
+ 'url' => esc_url_raw(trim((string) ($btn['url'] ?? ''))),
+ 'style' => sanitize_key((string) ($btn['style'] ?? 'primary')),
+ 'icon' => sanitize_text_field((string) ($btn['icon'] ?? '')),
+ 'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')),
+ 'new_window' => !empty($btn['new_window']),
+ ];
+ if (count($buttons) >= 8) {
+ break;
+ }
+ }
+ }
+ return [
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'layout' => ($new_instance['layout'] ?? '') === 'vertical' ? 'vertical' : 'horizontal',
+ 'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
+ 'radius' => !empty($new_instance['radius']) ? 1 : 0,
+ 'hollow' => !empty($new_instance['hollow']) ? 1 : 0,
+ 'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
+ 'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 10))),
+ 'buttons' => $buttons,
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/RecentCommentsWidget.php b/src/AKSRT/Widgets/RecentCommentsWidget.php
index f24de874..5820e1aa 100644
--- a/src/AKSRT/Widgets/RecentCommentsWidget.php
+++ b/src/AKSRT/Widgets/RecentCommentsWidget.php
@@ -1,5 +1,7 @@
esc_html__('显示最新评论列表', 'aksrt'),
- ]);
- }
+ 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']);
+ public function widget($args, $instance): void {
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
+ $count = max(1, min(20, (int) ($instance['count'] ?? $instance['limit'] ?? 5)));
+ $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
+ $outpost = max(0, (int) ($instance['outpost'] ?? 0));
+ $outer = sanitize_text_field((string) ($instance['outer'] ?? ''));
- if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- $comments = get_comments([
- 'number' => $count,
- 'status' => 'approve',
- 'post_status' => 'publish',
- ]);
+ $query = [
+ 'number' => $count,
+ 'status' => 'approve',
+ 'post_status' => 'publish',
+ ];
+ if ($outpost > 0) {
+ $query['post__not_in'] = [$outpost];
+ }
+ if ($outer !== '') {
+ // Exclude comments containing outer keywords (comma separated).
+ // Implemented post-filter below.
+ }
+ $comments = get_comments($query);
+ if ($outer !== '' && $comments) {
+ $keys = array_filter(array_map('trim', preg_split('/[,,]/', $outer) ?: []));
+ if ($keys) {
+ $comments = array_values(array_filter($comments, static function ($c) use ($keys) {
+ $body = (string) $c->comment_content;
+ foreach ($keys as $k) {
+ if ($k !== '' && str_contains($body, $k)) {
+ return false;
+ }
+ }
+ return true;
+ }));
+ }
+ }
- if ($comments) : ?>
-
-
- ' . esc_html__('暂无评论', 'aksrt') . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
- ';
+ foreach ($comments as $comment) {
+ $post = get_post($comment->comment_post_ID);
+ if (!$post) {
+ continue;
+ }
+ echo '';
+ }
+ echo '';
+ } else {
+ echo '
' . esc_html__('暂无评论', 'aksrt') . '
';
+ }
- echo $args['after_widget'];
- }
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- public function form($instance): void {
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ sanitize_text_field($new_instance['title'] ?? ''),
- 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
- 'show_avatar' => !empty($new_instance['show_avatar']),
- ];
- }
+ public function update($new_instance, $old_instance): array {
+ return [
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
+ 'outpost' => max(0, (int) ($new_instance['outpost'] ?? 0)),
+ 'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')),
+ 'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/WidgetVideoPlayer.php b/src/AKSRT/Widgets/WidgetVideoPlayer.php
index 40888ed5..d20a2d0f 100644
--- a/src/AKSRT/Widgets/WidgetVideoPlayer.php
+++ b/src/AKSRT/Widgets/WidgetVideoPlayer.php
@@ -1,5 +1,7 @@
__('在侧栏嵌入视频播放器。', 'aksrt')]
- );
- }
+ public function __construct() {
+ parent::__construct(
+ 'aksrt_video_player',
+ __('视频播放器', 'aksrt'),
+ ['description' => __('侧栏/流体区嵌入视频播放器。', 'aksrt')]
+ );
+ }
- public function widget($args, $instance): void {
- $title = !empty($instance['title']) ? $instance['title'] : '';
- $video_url = !empty($instance['video_url']) ? $instance['video_url'] : '';
- $poster = !empty($instance['poster']) ? $instance['poster'] : '';
- $autoplay = !empty($instance['autoplay']);
- $aspect_ratio = !empty($instance['aspect_ratio']) ? $instance['aspect_ratio'] : '16:9';
+ public function widget($args, $instance): void {
+ $title = (string) ($instance['title'] ?? '');
+ $video_url = trim((string) ($instance['video_url'] ?? $instance['url'] ?? ''));
+ $poster = trim((string) ($instance['poster'] ?? $instance['pic'] ?? ''));
+ $autoplay = !empty($instance['autoplay']);
+ $loop = !empty($instance['loop']);
+ $muted = $autoplay || !empty($instance['muted']);
+ $hide_controller = !empty($instance['hide_controller']);
+ $scale = max(20, min(100, (int) ($instance['scale_height'] ?? 56)));
+ $aspect = (string) ($instance['aspect_ratio'] ?? '');
- if (empty($video_url)) return;
+ if ($video_url === '') {
+ return;
+ }
- // 解析视频类型
- $embed_url = $this->get_embed_url($video_url);
- $is_embed = !empty($embed_url);
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- echo $args['before_widget'];
+ if ($aspect !== '' && str_contains($aspect, ':')) {
+ $parts = explode(':', $aspect);
+ if (count($parts) === 2 && (int) $parts[0] > 0) {
+ $scale = (int) round(((int) $parts[1] / (int) $parts[0]) * 100);
+ }
+ }
- if ($title) {
- echo $args['before_title'] . esc_html($title) . $args['after_title'];
- }
+ $embed = $this->get_embed_url($video_url);
+ $cls = 'aksrt-video-widget' . ($hide_controller ? ' controller-hide' : '');
+ echo '
';
+ if ($embed !== '') {
+ $src = $embed;
+ if ($autoplay) {
+ $src = add_query_arg(['autoplay' => '1', 'mute' => '1'], $src);
+ }
+ echo '';
+ } else {
+ $attrs = ' controls playsinline';
+ if ($autoplay) {
+ $attrs .= ' autoplay';
+ }
+ if ($loop) {
+ $attrs .= ' loop';
+ }
+ if ($muted) {
+ $attrs .= ' muted';
+ }
+ if ($poster !== '') {
+ $attrs .= ' poster="' . esc_url($poster) . '"';
+ }
+ echo '';
+ }
+ echo '
';
- // 计算 padding-bottom
- $ratio_parts = explode(':', $aspect_ratio);
- if (count($ratio_parts) === 2 && (int) $ratio_parts[1] > 0) {
- $padding = ((int) $ratio_parts[1] / (int) $ratio_parts[0]) * 100;
- } else {
- $padding = 56.25; // 16:9
- }
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- echo '
';
+
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
- public function form($instance) {
- $title = $instance['title'] ?? __('标签云', 'aksrt');
- $count = isset($instance['count']) ? (int)$instance['count'] : 15;
+ public function form($instance): void {
+ $taxs = get_taxonomies(['public' => true], 'objects');
?>
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
sanitize_text_field($new['title']),
- 'count' => absint($new['count']),
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'post_tag')),
+ 'count' => max(1, min(80, (int) ($new_instance['count'] ?? 15))),
+ 'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
+ 'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'rand')),
+ 'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
+ 'blank' => !empty($new_instance['blank']) ? 1 : 0,
+ 'fixed_width' => !empty($new_instance['fixed_width']) ? 1 : 0,
+ 'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
];
}
}
From 53d85c6ebd78476d08c6e780f4fcddc3cb466971 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:25:57 +0800
Subject: [PATCH 08/18] feat(widgets): user-card guest OAuth social login bar
Match zibll widget_ui_user guest shell with social provider buttons
under login/register CTAs.
---
assets/css/frontend/widget-batch5.css | 15 +++++++++++++++
src/AKSRT/Widgets/WidgetUserCard.php | 18 ++++++++++++++++--
2 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index 0d15fadd..f068cf75 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -512,3 +512,18 @@
color: var(--muted-2-color, #999);
font-size: 12px;
}
+
+.aksrt-user-card__oauth {
+ margin-top: 14px;
+ padding-top: 12px;
+ border-top: 1px dashed var(--color-line, #e5e7eb);
+}
+.aksrt-user-card__oauth .social-login-divider {
+ font-size: 12px;
+ color: var(--muted-2-color, #999);
+ margin-bottom: 8px;
+}
+.aksrt-user-card__oauth .social-login-item,
+.aksrt-user-card__oauth a {
+ margin: 0 4px;
+}
diff --git a/src/AKSRT/Widgets/WidgetUserCard.php b/src/AKSRT/Widgets/WidgetUserCard.php
index 5be7120f..c47afeea 100644
--- a/src/AKSRT/Widgets/WidgetUserCard.php
+++ b/src/AKSRT/Widgets/WidgetUserCard.php
@@ -137,6 +137,16 @@ class WidgetUserCard extends \WP_Widget {
? get_avatar_url(0, ['size' => 144, 'force_default' => true])
: includes_url('images/blank.gif');
+ $oauth_html = '';
+ if (class_exists('\AKSRT\Auth\OAuth')) {
+ $oauth = \AKSRT\Auth\OAuth::get_instance();
+ if (method_exists($oauth, 'render_social_buttons')) {
+ $oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
+ } elseif (method_exists($oauth, 'get_login_buttons')) {
+ $oauth_html = (string) $oauth->get_login_buttons();
+ }
+ }
+
echo ' ';
+ echo '
';
+ if ($oauth_html !== '') {
+ echo '' . $oauth_html . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
+ echo '';
}
public function form($instance): void {
From 5f398dd9d0c5ac6ea3d03b5c98b2255c7bdb03b5 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:28:13 +0800
Subject: [PATCH 09/18] feat(widgets): unregister core WP widgets; About guest
signin-loader
Align widget area with zibll by removing default RSS/search/calendar
widgets, and make About guest login buttons open auth modal classes.
---
inc/modules/widgets-register.php | 10 ++++++++++
src/AKSRT/Widgets/AboutWidget.php | 13 +++++++++----
src/AKSRT/Widgets/CustomHtmlGapWidget.php | 6 +++---
3 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/inc/modules/widgets-register.php b/inc/modules/widgets-register.php
index 19a5e2bb..fcde9935 100644
--- a/inc/modules/widgets-register.php
+++ b/inc/modules/widgets-register.php
@@ -10,6 +10,16 @@ defined('ABSPATH') || exit;
/**
* Register custom widgets.
*/
+function aksrt_unregister_default_widgets(): void {
+ unregister_widget('WP_Widget_RSS');
+ unregister_widget('WP_Widget_Recent_Posts');
+ unregister_widget('WP_Widget_Recent_Comments');
+ unregister_widget('WP_Widget_Pages');
+ unregister_widget('WP_Widget_Search');
+ unregister_widget('WP_Widget_Calendar');
+}
+add_action('widgets_init', 'aksrt_unregister_default_widgets', 1);
+
function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\AboutWidget');
register_widget('\AKSRT\Widgets\CategoriesWidget');
diff --git a/src/AKSRT/Widgets/AboutWidget.php b/src/AKSRT/Widgets/AboutWidget.php
index dd68d1a1..a1aba833 100644
--- a/src/AKSRT/Widgets/AboutWidget.php
+++ b/src/AKSRT/Widgets/AboutWidget.php
@@ -99,8 +99,13 @@ class AboutWidget extends \WP_Widget {
$avatar = get_avatar_url(0, ['size' => 160, 'default' => 'mystery']);
$oauth_html = '';
- if (function_exists('\AKSRT\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'get_login_buttons')) {
- $oauth_html = \AKSRT\Auth\OAuth::get_instance()->get_login_buttons();
+ if (class_exists('\AKSRT\Auth\OAuth')) {
+ $oauth = \AKSRT\Auth\OAuth::get_instance();
+ if (method_exists($oauth, 'render_social_buttons')) {
+ $oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
+ } elseif (method_exists($oauth, 'get_login_buttons')) {
+ $oauth_html = (string) $oauth->get_login_buttons();
+ }
}
?>
@@ -120,9 +125,9 @@ class AboutWidget extends \WP_Widget {
diff --git a/src/AKSRT/Widgets/CustomHtmlGapWidget.php b/src/AKSRT/Widgets/CustomHtmlGapWidget.php
index e6d35042..6a02ef61 100644
--- a/src/AKSRT/Widgets/CustomHtmlGapWidget.php
+++ b/src/AKSRT/Widgets/CustomHtmlGapWidget.php
@@ -19,7 +19,7 @@ class CustomHtmlGapWidget extends \WP_Widget {
$mode = sanitize_key($instance['mode'] ?? 'html');
if ($mode === 'gap') {
- $height = max(0, min(200, (int) ($instance['gap_height'] ?? 20)));
+ $height = max(0, min(500, (int) ($instance['gap_height'] ?? 20)));
echo '';
return;
}
@@ -51,7 +51,7 @@ class CustomHtmlGapWidget extends \WP_Widget {
-
+
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))),
+ 'gap_height' => max(0, min(500, (int) ($new_instance['gap_height'] ?? 20))),
];
}
}
From 179474da3e39fafd8b1c67d40d04d59525f98d24 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:34:40 +0800
Subject: [PATCH 10/18] feat(share): WeChat JS-SDK signature + footer share
config
Port zibll WeChatShare: ticket/token cache, page image resolution,
jweixin enqueue, and WeChatShareDate for in-app timeline/message share.
---
functions.php | 3 +
src/AKSRT/Frontend/WeChatShare.php | 260 +++++++++++++++++++++++++++++
2 files changed, 263 insertions(+)
create mode 100644 src/AKSRT/Frontend/WeChatShare.php
diff --git a/functions.php b/functions.php
index 5767db5d..62e1beaa 100644
--- a/functions.php
+++ b/functions.php
@@ -140,6 +140,9 @@ if (get_option('aksrt_flushed_version') !== AKSRT_VERSION) {
if (class_exists('\AKSRT\Auth\WeixinGzh')) {
\AKSRT\Auth\WeixinGzh::get_instance()->init();
}
+if (class_exists('\AKSRT\Frontend\WeChatShare')) {
+ \AKSRT\Frontend\WeChatShare::get_instance()->init();
+}
// 初始化代理登录(根据配置模式选择性加载)
$aksrt_proxy_mode = \AKSRT\Settings\Settings::oauth_proxy_mode();
diff --git a/src/AKSRT/Frontend/WeChatShare.php b/src/AKSRT/Frontend/WeChatShare.php
new file mode 100644
index 00000000..07836449
--- /dev/null
+++ b/src/AKSRT/Frontend/WeChatShare.php
@@ -0,0 +1,260 @@
+config = class_exists('\AKSRT\Settings\Settings')
+ ? \AKSRT\Settings\Settings::get_share_settings()
+ : [];
+
+ if (empty($this->config['wechat_enabled'])) {
+ return;
+ }
+ if (empty($this->config['wechat_appid']) || empty($this->config['wechat_app_secret'])) {
+ return;
+ }
+
+ add_action('wp_enqueue_scripts', [$this, 'enqueue_sdk'], 20);
+ add_action('wp_footer', [$this, 'print_share_date'], 99);
+ }
+
+ public function enqueue_sdk(): void {
+ // Only needed in WeChat browser, but enqueue lightly for all (script is external CDN).
+ wp_enqueue_script(
+ 'aksrt-jweixin',
+ 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
+ [],
+ '1.6.0',
+ true
+ );
+ // Ensure our bridge runs after jweixin.
+ if (wp_script_is('aksrt-weixin-share', 'registered') || wp_script_is('aksrt-weixin-share', 'enqueued')) {
+ wp_script_add_data('aksrt-weixin-share', 'group', 1);
+ }
+ }
+
+ public function print_share_date(): void {
+ $url = $this->current_url();
+ $sign = $this->get_sign($url);
+ if (!$sign) {
+ return;
+ }
+ $img = $this->resolve_image();
+ $title = wp_get_document_title();
+ $desc = '';
+ if (is_singular()) {
+ $post = get_post();
+ if ($post) {
+ $desc = wp_strip_all_tags(get_the_excerpt($post));
+ }
+ }
+ if ($desc === '') {
+ $desc = (string) get_bloginfo('description');
+ }
+ $payload = [
+ 'appId' => $sign['appId'],
+ 'timestamp' => (string) $sign['timestamp'],
+ 'nonceStr' => $sign['nonceStr'],
+ 'signature' => $sign['signature'],
+ 'url' => $url,
+ 'title' => $title,
+ 'img' => $img,
+ 'desc' => $desc,
+ ];
+ ?>
+
+ get_jsapi_ticket();
+ if ($ticket === '') {
+ return null;
+ }
+ $timestamp = time();
+ $nonce = $this->nonce_string();
+ $raw = "jsapi_ticket={$ticket}&noncestr={$nonce}×tamp={$timestamp}&url={$url}";
+ return [
+ 'appId' => (string) $this->config['wechat_appid'],
+ 'nonceStr' => $nonce,
+ 'timestamp' => $timestamp,
+ 'signature' => sha1($raw),
+ ];
+ }
+
+ private function current_url(): string {
+ $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
+ || (isset($_SERVER['SERVER_PORT']) && (int) $_SERVER['SERVER_PORT'] === 443)
+ || (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https');
+ $protocol = $https ? 'https://' : 'http://';
+ $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash((string) $_SERVER['HTTP_HOST'])) : wp_parse_url(home_url('/'), PHP_URL_HOST);
+ $uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '/';
+ // Drop hash fragment if any (should not be present in REQUEST_URI).
+ return $protocol . $host . $uri;
+ }
+
+ private function resolve_image(): string {
+ $only_logo = !empty($this->config['only_logo']);
+ $default = '';
+ if (!empty($this->config['share_img'])) {
+ $default = (string) $this->config['share_img'];
+ } elseif (class_exists('\AKSRT\Settings\Settings')) {
+ $default = (string) (\AKSRT\Settings\Settings::get_value('favicon', '') ?: \AKSRT\Settings\Settings::get_value('iconpng', ''));
+ }
+ if ($only_logo) {
+ return $default;
+ }
+
+ $pic = '';
+ if (is_singular()) {
+ $post_id = get_queried_object_id();
+ if ($post_id && has_post_thumbnail($post_id)) {
+ $pic = (string) get_the_post_thumbnail_url($post_id, 'full');
+ }
+ } elseif (is_author()) {
+ $author = get_queried_object();
+ if ($author && !empty($author->ID)) {
+ $pic = (string) get_user_meta((int) $author->ID, 'custom_avatar', true);
+ if ($pic === '') {
+ $pic = get_avatar_url((int) $author->ID, ['size' => 300]);
+ }
+ }
+ } elseif (is_category() || is_tag() || is_tax()) {
+ $term = get_queried_object();
+ if ($term && !empty($term->term_id)) {
+ $img_id = (int) get_term_meta((int) $term->term_id, 'thumbnail_id', true);
+ if ($img_id) {
+ $pic = (string) (wp_get_attachment_image_url($img_id, 'full') ?: '');
+ }
+ }
+ }
+
+ return $pic !== '' ? $pic : $default;
+ }
+
+ private function get_jsapi_ticket(): string {
+ $opt = get_option('aksrt_wechatshare_ticket', []);
+ $now = time() + 300;
+ if (is_array($opt) && !empty($opt['ticket']) && !empty($opt['expiration_time']) && (int) $opt['expiration_time'] > $now) {
+ return (string) $opt['ticket'];
+ }
+
+ $token = $this->get_access_token();
+ if ($token === '') {
+ return '';
+ }
+ $url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=' . rawurlencode($token) . '&type=jsapi';
+ $res = $this->http_json($url);
+ if (empty($res['ticket'])) {
+ return '';
+ }
+ $res['expiration_time'] = time() + max(60, (int) ($res['expires_in'] ?? 7200));
+ update_option('aksrt_wechatshare_ticket', $res, false);
+ return (string) $res['ticket'];
+ }
+
+ private function get_access_token(): string {
+ // Prefer shared GZH token cache when present.
+ $opt = get_option('aksrt_weixingzh_access_token', []);
+ $now = time() + 300;
+ if (is_array($opt) && !empty($opt['access_token']) && !empty($opt['expiration_time']) && (int) $opt['expiration_time'] > $now) {
+ return (string) $opt['access_token'];
+ }
+
+ $appid = (string) $this->config['wechat_appid'];
+ $secret = (string) $this->config['wechat_app_secret'];
+ $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . rawurlencode($appid) . '&secret=' . rawurlencode($secret);
+ $res = $this->http_json($url);
+ if (empty($res['access_token'])) {
+ return '';
+ }
+ $res['expiration_time'] = time() + max(60, (int) ($res['expires_in'] ?? 7200));
+ update_option('aksrt_weixingzh_access_token', $res, false);
+ return (string) $res['access_token'];
+ }
+
+ /**
+ * @return array
+ */
+ private function http_json(string $url): array {
+ $response = wp_remote_get($url, [
+ 'timeout' => 8,
+ 'headers' => ['Accept' => 'application/json'],
+ ]);
+ if (is_wp_error($response)) {
+ return [];
+ }
+ $body = (string) wp_remote_retrieve_body($response);
+ $data = json_decode($body, true);
+ return is_array($data) ? $data : [];
+ }
+
+ private function nonce_string(int $length = 16): string {
+ $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+ $str = '';
+ $max = strlen($chars) - 1;
+ for ($i = 0; $i < $length; $i++) {
+ $str .= $chars[random_int(0, $max)];
+ }
+ return $str;
+ }
+}
From c102c9d16deb4126581d948473af34b7c748c8e7 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:36:14 +0800
Subject: [PATCH 11/18] feat(shop): wire product/cart widget zones and extra
sidebars
Render shop_product top/sidebar/bottom and cart fluid areas; register
shop home/order sidebar slots for zibll shop layout parity.
---
src/AKSRT/Core/SetupPart1.php | 4 ++++
templates/pages/page-cart.php | 8 +++++++-
templates/pages/page-product.php | 14 +++++++++++++-
3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/src/AKSRT/Core/SetupPart1.php b/src/AKSRT/Core/SetupPart1.php
index 26634b99..f721a3ec 100644
--- a/src/AKSRT/Core/SetupPart1.php
+++ b/src/AKSRT/Core/SetupPart1.php
@@ -407,11 +407,15 @@ trait SetupPart1 {
// ── Shop widget areas (§6.9) ──
$shop_widgets = [
+ 'shop_home_top_fluid' => ['商城首页 · 顶部通栏', '商城首页顶部全宽区域。'],
+ 'shop_home_sidebar' => ['商城首页 · 侧栏', '商城首页侧栏区域。'],
'shop_product_top_fluid' => ['商品详情页 · 顶部通栏', '商品详情页顶部全宽区域。'],
'shop_product_sidebar' => ['商品详情页 · 侧栏', '商品详情页侧栏区域(PC 端)。'],
'shop_product_bottom_fluid' => ['商品详情页 · 底部通栏', '商品详情页底部全宽区域。'],
'shop_cart_top_fluid' => ['购物车页 · 顶部通栏', '购物车页顶部全宽区域。'],
'shop_cart_bottom_fluid' => ['购物车页 · 底部通栏', '购物车页底部全宽区域。'],
+ 'shop_order_top_fluid' => ['订单页 · 顶部通栏', '订单中心顶部全宽。'],
+ 'shop_order_bottom_fluid' => ['订单页 · 底部通栏', '订单中心底部全宽。'],
];
foreach ($shop_widgets as $sid => $meta) {
register_sidebar([
diff --git a/templates/pages/page-cart.php b/templates/pages/page-cart.php
index ae8bcb0a..d3df38b4 100644
--- a/templates/pages/page-cart.php
+++ b/templates/pages/page-cart.php
@@ -34,7 +34,10 @@ $address_book = is_array($cart_view['address_book'] ?? null) ? $cart_view['addre
?>
-
+
+
+
+
+
+
+
diff --git a/templates/pages/page-product.php b/templates/pages/page-product.php
index c9d70490..5be01cea 100644
--- a/templates/pages/page-product.php
+++ b/templates/pages/page-product.php
@@ -38,11 +38,15 @@ $author = $author_id > 0 ? get_userdata($author_id) : null;
?>
+
+
+
-
+
+
+
From c5a0c576977570dd8b6a8d6eb36c4c52e04fe357 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:39:11 +0800
Subject: [PATCH 12/18] feat(ux): author-card follow + dual-write checkin day
metas
Author sidebar card can follow target users; sign-in now mirrors
checkin_all_day / continuous meta for zibll-compatible ranking.
---
src/AKSRT/Credits/Signin.php | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/AKSRT/Credits/Signin.php b/src/AKSRT/Credits/Signin.php
index fca51f9c..02ea8c4b 100644
--- a/src/AKSRT/Credits/Signin.php
+++ b/src/AKSRT/Credits/Signin.php
@@ -159,8 +159,15 @@ class Signin {
// Keep lightweight meta for header/badge consumers (zibll checkin_all_day).
update_user_meta($user_id, 'aksrt_signin_' . $today_str, 1);
$all_day = (int) get_user_meta($user_id, 'aksrt_checkin_all_day', true);
- update_user_meta($user_id, 'aksrt_checkin_all_day', $all_day + 1);
+ if ($all_day <= 0) {
+ // Fallback to zibll key if migrated users only have that meta.
+ $all_day = (int) get_user_meta($user_id, 'checkin_all_day', true);
+ }
+ $all_day = $all_day + 1;
+ update_user_meta($user_id, 'aksrt_checkin_all_day', $all_day);
+ update_user_meta($user_id, 'checkin_all_day', $all_day); // zibll alias
update_user_meta($user_id, 'aksrt_checkin_continuous_day', $new_streak);
+ update_user_meta($user_id, 'checkin_continuous_day', $new_streak); // zibll alias
return new \WP_REST_Response([
'success' => true,
From 6bcb16fb8a93aba014311ae9ed2f5c03c5597b6c Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:40:45 +0800
Subject: [PATCH 13/18] feat(widgets): author card follow button
Add data-aksrt-follow CTA on post author sidebar card for non-self users.
---
assets/css/frontend/widget-batch5.css | 12 ++++++++++++
src/AKSRT/Widgets/WidgetAuthorCard.php | 7 +++++++
2 files changed, 19 insertions(+)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index f068cf75..b8e2befd 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -527,3 +527,15 @@
.aksrt-user-card__oauth a {
margin: 0 4px;
}
+
+.aksrt-author-card__follow {
+ border: 0;
+ cursor: pointer;
+ margin-right: 6px;
+ background: #ef4444;
+ color: #fff !important;
+}
+.aksrt-author-card__follow.is-following {
+ background: var(--surface-2, #e5e7eb);
+ color: inherit !important;
+}
diff --git a/src/AKSRT/Widgets/WidgetAuthorCard.php b/src/AKSRT/Widgets/WidgetAuthorCard.php
index 70c11018..9796f015 100644
--- a/src/AKSRT/Widgets/WidgetAuthorCard.php
+++ b/src/AKSRT/Widgets/WidgetAuthorCard.php
@@ -94,6 +94,13 @@ class WidgetAuthorCard extends \WP_Widget {
echo '' . esc_html((string) $followers) . '' . esc_html__('粉丝', 'aksrt') . '';
echo '
';
}
+ $cuid = get_current_user_id();
+ $show_follow = !isset($instance['show_follow']) || !empty($instance['show_follow']);
+ if ($show_follow && $cuid > 0 && $cuid !== $author_id && class_exists('\AKSRT\User\Follow')) {
+ $following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $author_id);
+ $label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
+ echo '
';
+ }
echo '
' . esc_html__('查看主页', 'aksrt') . '';
echo '
';
From 9643ce6d4429d1099bc06053c7cf656556455a92 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:43:09 +0800
Subject: [PATCH 14/18] feat(shop): archive/order widget zones for home sidebar
slots
Wire shop_home top/sidebar and product bottom fluid on product archives,
plus order center top/bottom fluid areas.
---
templates/pages/page-orders.php | 6 ++++++
templates/shop/product-cat.php | 11 +++++++++++
templates/shop/product-tag.php | 11 +++++++++++
3 files changed, 28 insertions(+)
diff --git a/templates/pages/page-orders.php b/templates/pages/page-orders.php
index a2abd93a..b762f978 100644
--- a/templates/pages/page-orders.php
+++ b/templates/pages/page-orders.php
@@ -55,6 +55,9 @@ $tabs = [
];
?>
+
+
+
@@ -369,4 +372,7 @@ $tabs = [
+
+
+
diff --git a/templates/shop/product-cat.php b/templates/shop/product-cat.php
index d15133fa..3cbe6dcc 100644
--- a/templates/shop/product-cat.php
+++ b/templates/shop/product-cat.php
@@ -9,6 +9,12 @@ defined('ABSPATH') || exit;
get_header();
+if (is_active_sidebar('shop_home_top_fluid')) {
+ echo '';
+ dynamic_sidebar('shop_home_top_fluid');
+ echo '
';
+}
+
$term = get_queried_object();
$paged = max(1, (int) get_query_var('paged'));
$orderby = '';
@@ -141,4 +147,9 @@ wp_enqueue_script(
AKSRT_VERSION,
true
);
+if (is_active_sidebar('shop_product_bottom_fluid')) {
+ echo '';
+ dynamic_sidebar('shop_product_bottom_fluid');
+ echo '
';
+}
get_footer();
diff --git a/templates/shop/product-tag.php b/templates/shop/product-tag.php
index b6ba0f89..fd4896f2 100644
--- a/templates/shop/product-tag.php
+++ b/templates/shop/product-tag.php
@@ -9,6 +9,12 @@ defined('ABSPATH') || exit;
get_header();
+if (is_active_sidebar('shop_home_top_fluid')) {
+ echo '';
+ dynamic_sidebar('shop_home_top_fluid');
+ echo '
';
+}
+
$term = get_queried_object();
$paged = max(1, (int) get_query_var('paged'));
$orderby = '';
@@ -165,4 +171,9 @@ wp_enqueue_script(
AKSRT_VERSION,
true
);
+if (is_active_sidebar('shop_product_bottom_fluid')) {
+ echo '';
+ dynamic_sidebar('shop_product_bottom_fluid');
+ echo '
';
+}
get_footer();
From 379692d597504ab9b0ed9fd0db6c8a7198003736 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:45:11 +0800
Subject: [PATCH 15/18] feat(widgets/badges): author worn-badge + wear_medal
alias
Show worn medal on author card; dual-read/write zibll wear_medal meta
and default to first owned badge when unset.
---
assets/css/frontend/widget-batch5.css | 13 ++++++++++++
src/AKSRT/Widgets/WidgetAuthorCard.php | 28 +++++++++++++++++++++++++-
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
index b8e2befd..ccb27575 100644
--- a/assets/css/frontend/widget-batch5.css
+++ b/assets/css/frontend/widget-batch5.css
@@ -539,3 +539,16 @@
background: var(--surface-2, #e5e7eb);
color: inherit !important;
}
+
+.aksrt-author-card__badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ margin-top: 8px;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ background: #fef3c7;
+ color: #b45309;
+}
+.aksrt-author-card__badge img { border-radius: 50%; }
diff --git a/src/AKSRT/Widgets/WidgetAuthorCard.php b/src/AKSRT/Widgets/WidgetAuthorCard.php
index 9796f015..6a523eda 100644
--- a/src/AKSRT/Widgets/WidgetAuthorCard.php
+++ b/src/AKSRT/Widgets/WidgetAuthorCard.php
@@ -85,6 +85,28 @@ class WidgetAuthorCard extends \WP_Widget {
if ($bio !== '') {
echo '' . esc_html(wp_trim_words($bio, 28)) . '
';
}
+ $show_badge = !isset($instance['show_badge']) || !empty($instance['show_badge']);
+ if ($show_badge && class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::public_display()) {
+ $wear = \AKSRT\User\Badges::get_wear($author_id);
+ if ($wear === '') {
+ // zibll alias fallback
+ $wear = sanitize_key((string) get_user_meta($author_id, 'wear_medal', true));
+ }
+ if ($wear !== '') {
+ $map = \AKSRT\User\Badges::get_dictionary_map();
+ if (isset($map[$wear])) {
+ $badge = $map[$wear];
+ echo '';
+ if (!empty($badge['icon_url'])) {
+ echo '
 $badge['icon_url']) . ')
';
+ } else {
+ echo '
';
+ }
+ echo esc_html((string) $badge['label']);
+ echo '
';
+ }
+ }
+ }
if ($show_stats) {
echo '';
echo '
' . esc_html((string) $post_count) . '' . esc_html__('文章', 'aksrt') . '';
@@ -122,7 +144,9 @@ class WidgetAuthorCard extends \WP_Widget {
-
+
+
+
max(0, (int) ($new_instance['user_id'] ?? 0)),
'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
+ 'show_follow' => !empty($new_instance['show_follow']) ? 1 : 0,
+ 'show_badge' => !empty($new_instance['show_badge']) ? 1 : 0,
];
}
}
From 8f17ac410b4a1163df23060d6b727d17a8c935d1 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:45:44 +0800
Subject: [PATCH 16/18] feat(badges): dual-read/write wear_medal alias +
default wear
Align badge wear meta with zibll and auto-pick first owned badge.
---
src/AKSRT/User/Badges.php | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/AKSRT/User/Badges.php b/src/AKSRT/User/Badges.php
index 95c8f3d2..bee2531c 100644
--- a/src/AKSRT/User/Badges.php
+++ b/src/AKSRT/User/Badges.php
@@ -185,9 +185,14 @@ class Badges {
}
$wear = sanitize_key((string) get_user_meta($user_id, self::META_WEAR, true));
if ($wear === '') {
- return '';
+ // zibll alias.
+ $wear = sanitize_key((string) get_user_meta($user_id, 'wear_medal', true));
}
$owned = self::get_owned($user_id);
+ if ($wear === '') {
+ // Default to newest/first owned badge when unset (zibll behavior).
+ return $owned[0] ?? '';
+ }
return in_array($wear, $owned, true) ? $wear : '';
}
@@ -211,6 +216,7 @@ class Badges {
return new \WP_Error('aksrt_badge_not_owned', __('尚未获得该徽章', 'aksrt'), ['status' => 403]);
}
update_user_meta($user_id, self::META_WEAR, $slug);
+ update_user_meta($user_id, 'wear_medal', $slug); // zibll alias
return true;
}
From dbe12751732e6d72e824955de3ed8d4b1b79369c Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:47:35 +0800
Subject: [PATCH 17/18] feat(badges): auto-award medals from catalog thresholds
Evaluate medal get_type metrics on login/checkin/content and grant
matching badges; optional search-history slot on search widget.
---
src/AKSRT/User/BadgeAutoAward.php | 168 ++++++++++++++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 src/AKSRT/User/BadgeAutoAward.php
diff --git a/src/AKSRT/User/BadgeAutoAward.php b/src/AKSRT/User/BadgeAutoAward.php
new file mode 100644
index 00000000..e04af9d7
--- /dev/null
+++ b/src/AKSRT/User/BadgeAutoAward.php
@@ -0,0 +1,168 @@
+evaluate((int) $user->ID);
+ }
+ }
+
+ public function maybe_for_current_user(): void {
+ if (!is_user_logged_in() || is_admin()) {
+ return;
+ }
+ // Throttle: once per 10 minutes per user.
+ $uid = get_current_user_id();
+ $key = 'aksrt_badge_auto_' . $uid;
+ if (get_transient($key)) {
+ return;
+ }
+ set_transient($key, 1, 10 * MINUTE_IN_SECONDS);
+ $this->evaluate($uid);
+ }
+
+ public function on_content_change($arg = null): void {
+ $uid = get_current_user_id();
+ if ($uid > 0) {
+ $this->evaluate($uid);
+ }
+ }
+
+ public function on_follow(int $follower_id, int $following_id): void {
+ if ($following_id > 0) {
+ $this->evaluate($following_id);
+ }
+ if ($follower_id > 0) {
+ $this->evaluate($follower_id);
+ }
+ }
+
+ public function on_checkin(int $user_id): void {
+ if ($user_id > 0) {
+ $this->evaluate($user_id);
+ }
+ }
+
+ public function evaluate(int $user_id): void {
+ if ($user_id <= 0 || !class_exists('\AKSRT\User\Badges') || !Badges::enabled()) {
+ return;
+ }
+ if (!class_exists('\AKSRT\Settings\Settings')) {
+ return;
+ }
+ $identity = \AKSRT\Settings\Settings::get_user_identity_settings();
+ $medal = is_array($identity['medal'] ?? null) ? $identity['medal'] : [];
+ $items = is_array($medal['items_by_slug'] ?? null) ? $medal['items_by_slug'] : [];
+ if (!$items) {
+ // Fall back to badge dictionary without thresholds.
+ return;
+ }
+
+ foreach ($items as $slug => $item) {
+ $get_type = sanitize_key((string) ($item['get_type'] ?? 'manually_add'));
+ if ($get_type === 'manually_add' || $get_type === '') {
+ continue;
+ }
+ $need = max(0, (int) ($item['get_val'] ?? 0));
+ if ($need <= 0) {
+ continue;
+ }
+ $val = $this->metric($user_id, $get_type);
+ if ($val >= $need) {
+ Badges::grant($user_id, (string) $slug, 0);
+ }
+ }
+ }
+
+ private function metric(int $user_id, string $type): int {
+ switch ($type) {
+ case 'registration_time':
+ $user = get_userdata($user_id);
+ if (!$user) {
+ return 0;
+ }
+ $reg = strtotime((string) $user->user_registered);
+ if ($reg <= 0) {
+ return 0;
+ }
+ return max(0, (int) floor((time() - $reg) / DAY_IN_SECONDS));
+
+ case 'checkin_all_day':
+ $v = (int) get_user_meta($user_id, 'aksrt_checkin_all_day', true);
+ if ($v <= 0) {
+ $v = (int) get_user_meta($user_id, 'checkin_all_day', true);
+ }
+ return $v;
+
+ case 'new_post':
+ return (int) count_user_posts($user_id, 'post', true);
+
+ case 'bbs_post':
+ return post_type_exists('aksrt_forum_topic')
+ ? (int) count_user_posts($user_id, 'aksrt_forum_topic', true)
+ : 0;
+
+ case 'comment':
+ return (int) get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']);
+
+ case 'followed':
+ if (class_exists('\AKSRT\User\Follow')) {
+ return (int) Follow::get_instance()->get_follower_count($user_id);
+ }
+ return 0;
+
+ case 'like':
+ return (int) get_user_meta($user_id, 'aksrt_like_count', true)
+ ?: (int) get_user_meta($user_id, 'like-posts', true);
+
+ case 'views':
+ return (int) get_user_meta($user_id, 'aksrt_user_views', true);
+
+ case 'pay_income':
+ return (int) get_user_meta($user_id, 'aksrt_pay_income', true);
+
+ case 'pay_rebate':
+ return (int) get_user_meta($user_id, 'aksrt_pay_rebate', true);
+
+ default:
+ /**
+ * Allow extensions to provide metric values.
+ *
+ * @param int $value
+ * @param int $user_id
+ * @param string $type
+ */
+ return (int) apply_filters('aksrt_badge_metric_value', 0, $user_id, $type);
+ }
+ }
+}
From 00cd4573d4d47af2b45d565c62fd7b5bf6048926 Mon Sep 17 00:00:00 2001
From: unknown
Date: Sun, 19 Jul 2026 01:49:09 +0800
Subject: [PATCH 18/18] feat(badges): boot auto-award + checkin hook + search
history slot
Wire BadgeAutoAward/Badges init, fire aksrt_checkin_done, and optional
search-history mount point on the search widget.
---
functions.php | 6 ++++++
src/AKSRT/Credits/Signin.php | 1 +
src/AKSRT/Widgets/SearchWidget.php | 11 +++++++++++
3 files changed, 18 insertions(+)
diff --git a/functions.php b/functions.php
index 62e1beaa..684ca546 100644
--- a/functions.php
+++ b/functions.php
@@ -174,6 +174,12 @@ if (class_exists('\AKSRT\User\Restriction')) {
if (class_exists('\AKSRT\User\Center')) {
\AKSRT\User\Center::get_instance()->init();
}
+if (class_exists('\AKSRT\User\Badges')) {
+ \AKSRT\User\Badges::get_instance()->init();
+}
+if (class_exists('\AKSRT\User\BadgeAutoAward')) {
+ \AKSRT\User\BadgeAutoAward::get_instance()->init();
+}
if (class_exists('\AKSRT\User\Home')) {
\AKSRT\User\Home::get_instance()->init();
}
diff --git a/src/AKSRT/Credits/Signin.php b/src/AKSRT/Credits/Signin.php
index 02ea8c4b..ffd5684c 100644
--- a/src/AKSRT/Credits/Signin.php
+++ b/src/AKSRT/Credits/Signin.php
@@ -168,6 +168,7 @@ class Signin {
update_user_meta($user_id, 'checkin_all_day', $all_day); // zibll alias
update_user_meta($user_id, 'aksrt_checkin_continuous_day', $new_streak);
update_user_meta($user_id, 'checkin_continuous_day', $new_streak); // zibll alias
+ do_action('aksrt_checkin_done', $user_id);
return new \WP_REST_Response([
'success' => true,
diff --git a/src/AKSRT/Widgets/SearchWidget.php b/src/AKSRT/Widgets/SearchWidget.php
index c986b90b..394da33f 100644
--- a/src/AKSRT/Widgets/SearchWidget.php
+++ b/src/AKSRT/Widgets/SearchWidget.php
@@ -26,6 +26,7 @@ class SearchWidget extends \WP_Widget {
$placeholder = sanitize_text_field((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt')));
$show_kw = !empty($instance['show_keywords']);
+ $show_history = !empty($instance['show_history']);
$kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt')));
$more_but = sanitize_text_field((string) ($instance['more_but'] ?? ''));
$more_url = esc_url_raw(trim((string) ($instance['more_but_url'] ?? '')));
@@ -54,6 +55,12 @@ class SearchWidget extends \WP_Widget {
+
+
+
- comment_content, 15, '...')); ?> -
- - ID)); ?> - -