diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css
new file mode 100644
index 00000000..ccb27575
--- /dev/null
+++ b/assets/css/frontend/widget-batch5.css
@@ -0,0 +1,554 @@
+/**
+ * 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;
+}
+
+
+/* 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; }
+
+
+/* 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; }
+
+.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;
+}
+
+.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;
+}
+
+.widget-icon-cards {
+ display: grid;
+ grid-template-columns: repeat(var(--ic-columns, 2), minmax(0, 1fr));
+ gap: 10px;
+}
+@media (max-width: 575.98px) {
+ .widget-icon-cards {
+ grid-template-columns: repeat(var(--ic-m-columns, 2), minmax(0, 1fr));
+ }
+}
+.widget-icon-cards.is-bare .widget-icon-cards__item {
+ background: transparent;
+ border: 0;
+ box-shadow: none;
+}
+.widget-icon-cards__item.is-vertical {
+ flex-direction: column;
+ text-align: center;
+ padding: 16px 10px;
+}
+.widget-icon-cards__item.is-vertical .widget-icon-cards__icon {
+ margin: 0 auto 10px;
+ width: calc(var(--ic-icon, 28px) + 20px);
+ height: calc(var(--ic-icon, 28px) + 20px);
+ font-size: var(--ic-icon, 28px);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--surface-2, #f3f4f6);
+ color: var(--color-primary, #3b82f6);
+}
+.aksrt-tag-cloud.tag-cloud {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+.aksrt-tag-cloud .but {
+ display: inline-flex;
+ align-items: center;
+ padding: 4px 10px;
+ border-radius: 999px;
+ background: var(--surface-2, #f3f4f6);
+ color: inherit;
+ text-decoration: none;
+ font-size: 12px;
+}
+.aksrt-tag-cloud .c-blue { background: #dbeafe; color: #1d4ed8; }
+.aksrt-tag-cloud .c-green { background: #dcfce7; color: #15803d; }
+.aksrt-tag-cloud .c-yellow { background: #fef3c7; color: #b45309; }
+.aksrt-tag-cloud .c-purple { background: #ede9fe; color: #6d28d9; }
+.aksrt-tag-cloud .c-red { background: #fee2e2; color: #b91c1c; }
+.aksrt-tag-cloud.fixed-width a { min-width: 72px; justify-content: center; }
+.aksrt-cat-list .count {
+ float: right;
+ 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;
+}
+
+.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;
+}
+
+.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/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/functions.php b/functions.php
index 5767db5d..684ca546 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();
@@ -171,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/inc/modules/widgets-register.php b/inc/modules/widgets-register.php
index 8ba39126..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');
@@ -74,6 +84,10 @@ 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');
+ register_widget('\AKSRT\Widgets\WidgetIconCoverCards');
+ register_widget('\AKSRT\Widgets\WidgetBookmarkLinks');
// Forum widgets
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/src/AKSRT/Credits/Signin.php b/src/AKSRT/Credits/Signin.php
index fca51f9c..ffd5684c 100644
--- a/src/AKSRT/Credits/Signin.php
+++ b/src/AKSRT/Credits/Signin.php
@@ -159,8 +159,16 @@ 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
+ do_action('aksrt_checkin_done', $user_id);
return new \WP_REST_Response([
'success' => true,
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;
+ }
+}
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);
+ }
+ }
+}
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;
}
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/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/CategoriesWidget.php b/src/AKSRT/Widgets/CategoriesWidget.php
index df7a969e..fe2362ce 100644
--- a/src/AKSRT/Widgets/CategoriesWidget.php
+++ b/src/AKSRT/Widgets/CategoriesWidget.php
@@ -1,5 +1,7 @@
esc_html__('以列表形式展示文章分类', 'aksrt'),
+ [
+ 'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
'customize_selective_refresh' => true,
- )
+ ]
);
}
- public function widget($args, $instance) {
- echo $args['before_widget'];
+ public function widget($args, $instance): void {
+ echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- $title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
+ $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
+ }
- if ($title): ?>
-
- parse_ids((string) ($instance['exclude'] ?? ''));
+ $include = $this->parse_ids((string) ($instance['include'] ?? ''));
- $count = absint($instance['count'] ?: 8);
- $cats = get_categories(array('number' => $count, 'orderby' => 'count', 'order' => 'DESC'));
- ?>
-
- 'category',
+ 'number' => $count,
+ 'orderby' => $orderby,
+ 'order' => $order,
+ 'hide_empty' => $hide_empty,
+ 'hierarchical' => $hierarchical,
+ ];
+ if ($parent > 0) {
+ $term_args['parent'] = $parent;
+ }
+ if ($include) {
+ $term_args['include'] = $include;
+ $term_args['orderby'] = 'include';
+ unset($term_args['number']);
+ }
+ if ($exclude) {
+ $term_args['exclude'] = $exclude;
+ }
+
+ $cats = get_categories($term_args);
+ if (!$cats) {
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ return;
+ }
+
+ 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'] : 8;
+ /** @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 {
?>
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
sanitize_text_field($new['title']),
- 'count' => absint($new['count']),
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'count' => max(1, min(50, (int) ($new_instance['count'] ?? 8))),
+ 'orderby' => $orderby,
+ 'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
+ 'parent' => max(0, (int) ($new_instance['parent'] ?? 0)),
+ 'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
+ 'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
+ 'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
+ 'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
+ 'hierarchical' => !empty($new_instance['hierarchical']) ? 1 : 0,
];
}
}
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))),
];
}
}
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 {
-
+
+
+ esc_html__('显示图标+文字卡片网格', 'aksrt'),
- ]);
- }
+ public function __construct() {
+ parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
+ 'description' => esc_html__('图标在上、文案在下的竖版图标卡片网格。', 'aksrt'),
+ ]);
+ }
- public function widget($args, $instance): void {
- echo $args['before_widget'];
- $title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
- $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
- $columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
+ public function widget($args, $instance): void {
+ $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
+ $columns = max(1, min(4, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2)));
+ $m_cols = max(1, min(3, (int) ($instance['m_row'] ?? 2)));
+ $go_link = !empty($instance['go_link']);
+ $show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']);
+ $icon_size = max(18, min(48, (int) ($instance['icon_size'] ?? 28)));
- if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
+ $valid = [];
+ foreach ($cards as $card) {
+ if (!is_array($card)) {
+ continue;
+ }
+ $title = sanitize_text_field((string) ($card['title'] ?? ''));
+ if ($title === '') {
+ continue;
+ }
+ $url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? '')));
+ if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
+ $url = aksrt_go_link($url, true);
+ }
+ $valid[] = [
+ 'title' => $title,
+ 'icon' => sanitize_text_field((string) ($card['icon'] ?? '')),
+ 'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')),
+ 'url' => $url,
+ ];
+ }
+ if (!$valid) {
+ return;
+ }
- if (!empty($cards)) : ?>
-
- id_base);
+ if ($title !== '') {
+ echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ }
- echo $args['after_widget'];
- }
+ $style = sprintf('--ic-columns:%d;--ic-m-columns:%d;--ic-icon:%dpx;', $columns, $m_cols, $icon_size);
+ echo '
';
- public function form($instance): void {
- $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
- while (count($cards) < 4) {
- $cards[] = ['title' => '', 'icon' => '', 'link' => '', 'description' => ''];
- }
- ?>
-
-
-
-
-
-
-
-
-
- $card) : ?>
-
- sanitize_text_field($card['title'] ?? ''),
- 'icon' => sanitize_text_field($card['icon'] ?? ''),
- 'link' => esc_url_raw($card['link'] ?? ''),
- 'description' => sanitize_text_field($card['description'] ?? ''),
- ];
- }
- }
- return [
- 'title' => sanitize_text_field($new_instance['title'] ?? ''),
- 'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
- 'cards' => $cards,
- ];
- }
+ public function form($instance): void {
+ $cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
+ while (count($cards) < 4) {
+ $cards[] = ['title' => '', 'icon' => '', 'link' => '', 'description' => ''];
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ $card) : ?>
+
+ " value="" placeholder="">
+ " value="" placeholder="">
+ " value="" placeholder="">
+ " value="" placeholder="">
+
+ $title,
+ 'icon' => sanitize_text_field((string) ($card['icon'] ?? '')),
+ 'link' => esc_url_raw(trim((string) ($card['link'] ?? ''))),
+ 'description' => sanitize_text_field((string) ($card['description'] ?? '')),
+ ];
+ if (count($cards) >= 12) {
+ break;
+ }
+ }
+ }
+ return [
+ 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
+ 'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
+ 'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 2))),
+ 'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
+ 'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
+ 'cards' => $cards,
+ ];
+ }
}
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/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/SearchWidget.php b/src/AKSRT/Widgets/SearchWidget.php
index eca6bc69..394da33f 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,
+ 'show_history' => !empty($new_instance['show_history']) ? 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'] ?? ''))),
+ ];
+ }
}
diff --git a/src/AKSRT/Widgets/TagCloudWidget.php b/src/AKSRT/Widgets/TagCloudWidget.php
index 22eeb779..53044419 100644
--- a/src/AKSRT/Widgets/TagCloudWidget.php
+++ b/src/AKSRT/Widgets/TagCloudWidget.php
@@ -1,5 +1,7 @@
esc_html__('展示文章标签云', 'aksrt'),
+ [
+ 'description' => esc_html__('展示文章标签/分类云', 'aksrt'),
'customize_selective_refresh' => true,
- )
+ ]
);
}
- public function widget($args, $instance) {
- echo $args['before_widget'];
+ public function widget($args, $instance): void {
+ 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
+ }
- $title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
- $count = absint($instance['count'] ?: 15);
+ $taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'post_tag'));
+ if (!taxonomy_exists($taxonomy)) {
+ $taxonomy = 'post_tag';
+ }
+ $number = max(1, min(80, (int) ($instance['count'] ?? $instance['number'] ?? 15)));
+ $orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
+ if (!in_array($orderby, ['count', 'name', 'id'], true)) {
+ $orderby = 'count';
+ }
+ $show_count = !empty($instance['show_count']);
+ $blank = !empty($instance['blank']);
+ $color = sanitize_html_class((string) ($instance['color'] ?? 'rand'));
+ $fixed_width = !empty($instance['fixed_width']);
- if ($title): ?>
-
-
+ $tags = get_terms([
+ 'taxonomy' => $taxonomy,
+ 'orderby' => $orderby,
+ 'order' => 'DESC',
+ 'number' => $number,
+ 'hide_empty' => !empty($instance['hide_empty']),
+ ]);
+ if (is_wp_error($tags) || !$tags) {
+ echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ return;
+ }
-
- $count, 'orderby' => 'count', 'order' => 'DESC']);
- foreach ($tags as $tag):
- $size = 12 + min(4, (int)($tag->count / 5));
- ?>
-
#name); ?>
-
-
- ';
+ foreach ($tags as $tag) {
+ $url = get_term_link($tag);
+ if (is_wp_error($url)) {
+ continue;
+ }
+ $cls = 'but';
+ if ($color === 'rand') {
+ if ($i > 10) {
+ $i = 0;
+ }
+ $cls .= ' ' . $palette[$i];
+ $i++;
+ } elseif ($color !== '') {
+ $cls .= ' ' . $color;
+ }
+ $tgt = $blank ? ' target="_blank" rel="noopener noreferrer"' : '';
+ $count = $show_count ? '
(' . (int) $tag->count . ')' : '';
+ echo '
' . esc_html($tag->name) . $count . ''; // 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,
];
}
}
diff --git a/src/AKSRT/Widgets/WidgetAuthorCard.php b/src/AKSRT/Widgets/WidgetAuthorCard.php
index 70c11018..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') . '';
@@ -94,6 +116,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 '
';
@@ -115,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,
];
}
}
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/WidgetCoverCards.php b/src/AKSRT/Widgets/WidgetCoverCards.php
index 99875055..61dc1f52 100644
--- a/src/AKSRT/Widgets/WidgetCoverCards.php
+++ b/src/AKSRT/Widgets/WidgetCoverCards.php
@@ -35,6 +35,7 @@ class WidgetCoverCards extends \WP_Widget {
foreach ($items as $item) {
$image = esc_url($item['image'] ?? '');
+ $video = esc_url($item['video'] ?? '');
$link = esc_url($item['link'] ?? '');
$card_title = sanitize_text_field($item['title'] ?? '');
$desc = sanitize_text_field($item['desc'] ?? '');
@@ -53,7 +54,11 @@ class WidgetCoverCards extends \WP_Widget {
echo '';
- if ($image) {
+ if ($video) {
+ echo '
';
+ echo '';
+ echo '
';
+ } elseif ($image) {
echo '
';
echo '
 . ')
';
echo '
';
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,
+ ];
+ }
+}
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..c47afeea 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 $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 {
+
+
+
__('在侧栏嵌入视频播放器。', '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 '
- comment_content, 15, '...')); ?> -
- - ID)); ?> - -