feat(widgets): partners/iframe/combo/text/user + forum plate widgets
Close major zibll widget-more and bbs plate gaps: partners marquee, super embed, combo info cards, text title, user lists, and plate list/info/moderator sidebars with batch3 CSS registration.
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
/**
|
||||
* Batch3 widgets: partners scroll, iframe, combo info, text title, user lists, plate widgets.
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
/* ---- Partners scroll ---- */
|
||||
.aksrt-partners-scroll-wrap {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.aksrt-partners-scroll-wrap.is-bare {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
.aksrt-partners-scroll {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
--aksrt-ps-w: 220px;
|
||||
--aksrt-ps-h: 90px;
|
||||
--aksrt-ps-radius: 10px;
|
||||
--aksrt-ps-speed: 3;
|
||||
}
|
||||
.aksrt-ps-end-fade::before,
|
||||
.aksrt-ps-end-fade::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 48px;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.aksrt-ps-end-fade::before {
|
||||
left: 0;
|
||||
background: linear-gradient(90deg, var(--surface-1, #fff), transparent);
|
||||
}
|
||||
.aksrt-ps-end-fade::after {
|
||||
right: 0;
|
||||
background: linear-gradient(270deg, var(--surface-1, #fff), transparent);
|
||||
}
|
||||
.aksrt-ps-row {
|
||||
overflow: hidden;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.aksrt-ps-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.aksrt-ps-track {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
animation: aksrt-ps-marquee calc(40s / var(--aksrt-ps-speed)) linear infinite;
|
||||
}
|
||||
.aksrt-ps-dir-right .aksrt-ps-track {
|
||||
animation-name: aksrt-ps-marquee-rev;
|
||||
}
|
||||
.aksrt-ps-pause-hover:hover .aksrt-ps-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.aksrt-ps-group {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
.aksrt-ps-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
width: var(--aksrt-ps-w);
|
||||
height: var(--aksrt-ps-h);
|
||||
margin-right: 12px;
|
||||
border-radius: var(--aksrt-ps-radius);
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.aksrt-ps-card-bg .aksrt-ps-item {
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
}
|
||||
.aksrt-ps-img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
.aksrt-ps-style-logo .aksrt-ps-item {
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.aksrt-ps-card-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.aksrt-ps-card-icon .aksrt-ps-img {
|
||||
max-height: 42px;
|
||||
}
|
||||
.aksrt-ps-card-text {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.aksrt-ps-card-title {
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-ps-card-desc {
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #999);
|
||||
margin-top: 3px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-ps-style-chip .aksrt-ps-item {
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-height: 36px;
|
||||
border-radius: 999px;
|
||||
padding: 6px 14px;
|
||||
gap: 6px;
|
||||
}
|
||||
.aksrt-ps-chip-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.aksrt-ps-chip-title {
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@keyframes aksrt-ps-marquee {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(-50%); }
|
||||
}
|
||||
@keyframes aksrt-ps-marquee-rev {
|
||||
from { transform: translateX(-50%); }
|
||||
to { transform: translateX(0); }
|
||||
}
|
||||
|
||||
/* ---- Iframe embed ---- */
|
||||
.aksrt-iframe-embed {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.aksrt-iframe-ratio {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
}
|
||||
.aksrt-iframe-ratio iframe,
|
||||
.aksrt-iframe-el {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* ---- Combo info cards ---- */
|
||||
.aksrt-combo-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--aksrt-zci-pc, 4), minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
margin-bottom: 16px;
|
||||
--aksrt-zci-radius: 12px;
|
||||
--aksrt-zci-band-h: 70px;
|
||||
}
|
||||
@media (max-width: 991.98px) {
|
||||
.aksrt-combo-info {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.aksrt-combo-info {
|
||||
grid-template-columns: repeat(var(--aksrt-zci-m, 1), minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.aksrt-zci-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--aksrt-zci-radius);
|
||||
background: var(--surface-1, #fff);
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
padding: 18px 16px 16px;
|
||||
overflow: hidden;
|
||||
min-height: 100%;
|
||||
}
|
||||
.aksrt-combo-info.has-band .aksrt-zci-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
background: var(--color-primary, #3b82f6);
|
||||
}
|
||||
.aksrt-zci-band-blue::before { background: #3b82f6; }
|
||||
.aksrt-zci-band-cyan::before { background: #06b6d4; }
|
||||
.aksrt-zci-band-green::before { background: #22c55e; }
|
||||
.aksrt-zci-band-yellow::before { background: #eab308; }
|
||||
.aksrt-zci-band-pink::before { background: #ec4899; }
|
||||
.aksrt-zci-band-purple::before { background: #a855f7; }
|
||||
.aksrt-zci-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
}
|
||||
.aksrt-zci-icon img {
|
||||
max-width: 48px;
|
||||
max-height: 48px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.aksrt-zci-title {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-zci-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.aksrt-zci-tag {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
color: var(--color-ink-3, #666);
|
||||
}
|
||||
.aksrt-zci-desc {
|
||||
font-size: 13px;
|
||||
color: var(--muted-2-color, #888);
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
.aksrt-zci-cta {
|
||||
margin-top: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
text-decoration: none;
|
||||
}
|
||||
.aksrt-zci-cta:hover i {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
.aksrt-zci-cta i {
|
||||
transition: transform .2s ease;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ---- Text title ---- */
|
||||
.aksrt-text-title {
|
||||
margin: 8px 0 18px;
|
||||
--aksrt-tt-title-size: 22px;
|
||||
--aksrt-tt-desc-size: 14px;
|
||||
}
|
||||
.aksrt-text-title.align-center { text-align: center; }
|
||||
.aksrt-text-title.align-left { text-align: left; }
|
||||
.aksrt-text-title.align-right { text-align: right; }
|
||||
.aksrt-text-title__main {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: var(--aksrt-tt-title-size);
|
||||
font-weight: 700;
|
||||
color: var(--aksrt-tt-title-color, inherit);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.aksrt-text-title.align-center .aksrt-text-title__main {
|
||||
justify-content: center;
|
||||
}
|
||||
.aksrt-text-title__desc {
|
||||
margin-top: 8px;
|
||||
font-size: var(--aksrt-tt-desc-size);
|
||||
color: var(--aksrt-tt-desc-color, var(--muted-2-color, #888));
|
||||
line-height: 1.5;
|
||||
}
|
||||
.aksrt-text-title__icon {
|
||||
display: inline-flex;
|
||||
opacity: .85;
|
||||
}
|
||||
|
||||
/* ---- User lists ---- */
|
||||
.aksrt-user-lists {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px 8px;
|
||||
text-align: center;
|
||||
padding: 4px 0 8px;
|
||||
}
|
||||
.aksrt-user-lists.is-bare {
|
||||
padding: 0;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.aksrt-user-lists {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.aksrt-user-lists__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
min-width: 0;
|
||||
}
|
||||
.aksrt-user-lists__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: var(--surface-2, #eee);
|
||||
}
|
||||
.aksrt-user-lists__name {
|
||||
font-size: 12px;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ---- Forum plate lists ---- */
|
||||
.aksrt-plate-lists {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.aksrt-plate-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px dashed var(--color-line, #e5e7eb);
|
||||
}
|
||||
.aksrt-plate-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.aksrt-plate-item__thumb {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: var(--surface-2, #f3f4f6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
font-size: 18px;
|
||||
}
|
||||
.aksrt-plate-item__thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.aksrt-plate-item__body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.aksrt-plate-item__title {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-plate-item__desc {
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #888);
|
||||
margin-top: 3px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-plate-item__meta {
|
||||
font-size: 11px;
|
||||
color: var(--color-ink-4, #999);
|
||||
margin-top: 4px;
|
||||
}
|
||||
.aksrt-plate-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.aksrt-plate-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
.aksrt-plate-card {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
background: var(--surface-1, #fff);
|
||||
}
|
||||
.aksrt-plate-card__cover {
|
||||
height: 88px;
|
||||
background: var(--surface-2, #f3f4f6) center/cover no-repeat;
|
||||
}
|
||||
.aksrt-plate-card__title {
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
padding: 10px 10px 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-plate-card__meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 11px;
|
||||
color: var(--muted-2-color, #888);
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
|
||||
/* ---- Plate info ---- */
|
||||
.aksrt-plate-info {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line, #e5e7eb);
|
||||
background: var(--surface-1, #fff);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.aksrt-plate-info__cover {
|
||||
height: 90px;
|
||||
background: var(--surface-2, #f3f4f6) center/cover no-repeat;
|
||||
}
|
||||
.aksrt-plate-info__body {
|
||||
padding: 12px 14px 14px;
|
||||
}
|
||||
.aksrt-plate-info__title {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.aksrt-plate-info__desc {
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #888);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.aksrt-plate-info__stats {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3, #666);
|
||||
}
|
||||
.aksrt-plate-info__stats i {
|
||||
margin-right: 4px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
/* ---- Plate moderators ---- */
|
||||
.aksrt-plate-mods {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.aksrt-plate-mods__head {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.aksrt-plate-mods__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.aksrt-plate-mods__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.aksrt-plate-mods__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.aksrt-plate-mods__name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.aksrt-plate-mods__role {
|
||||
font-size: 11px;
|
||||
color: var(--color-primary, #3b82f6);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
@import url("widgets/part-3.css");
|
||||
@import url("widgets/part-4.css");
|
||||
@import url("widget-slider.css");
|
||||
@import url("widget-batch3.css");
|
||||
|
||||
@@ -56,6 +56,13 @@ function aksrt_register_widgets() {
|
||||
// 幻灯片
|
||||
register_widget('\AKSRT\Widgets\WidgetSlider');
|
||||
|
||||
// zibll more widgets batch
|
||||
register_widget('\AKSRT\Widgets\WidgetPartnersScroll');
|
||||
register_widget('\AKSRT\Widgets\WidgetIframe');
|
||||
register_widget('\AKSRT\Widgets\WidgetComboInfo');
|
||||
register_widget('\AKSRT\Widgets\WidgetTextTitle');
|
||||
register_widget('\AKSRT\Widgets\WidgetUserLists');
|
||||
|
||||
// Forum widgets
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumBoardList')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumBoardList');
|
||||
@@ -66,6 +73,15 @@ function aksrt_register_widgets() {
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumRecentTopics')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumRecentTopics');
|
||||
}
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumPlateLists')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumPlateLists');
|
||||
}
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumPlateInfo')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumPlateInfo');
|
||||
}
|
||||
if (class_exists('\AKSRT\Widgets\WidgetForumPlateModerator')) {
|
||||
register_widget('\AKSRT\Widgets\WidgetForumPlateModerator');
|
||||
}
|
||||
|
||||
// Shop product widgets (§6.9)
|
||||
register_widget('\AKSRT\Widgets\WidgetProductList');
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
/**
|
||||
* Combo info cards (zibll zib_widget_ui_combo_info_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetComboInfo extends \WP_Widget {
|
||||
|
||||
private const BANDS = ['blue', 'cyan', 'green', 'yellow', 'pink', 'purple'];
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_combo_info',
|
||||
esc_html__('AKSRT 复合信息卡片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('图标 + 标题 + 标签 + 描述 + CTA 的多列信息卡。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_combo_info',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$cards = $this->normalize_cards($instance['cards'] ?? []);
|
||||
if (!$cards) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? 4)));
|
||||
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
|
||||
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 12)));
|
||||
$top_band = !empty($instance['top_band']) || !isset($instance['top_band']);
|
||||
$band_h = max(20, min(160, (int) ($instance['top_band_height'] ?? 70)));
|
||||
$cta_default = (string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'));
|
||||
$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-zci-radius:%dpx;--aksrt-zci-band-h:%dpx;--aksrt-zci-pc:%d;--aksrt-zci-m:%d;', $radius, $band_h, $pc_row, $m_row);
|
||||
$wrap = 'aksrt-combo-info' . ($top_band ? ' has-band' : '');
|
||||
|
||||
echo '<div class="' . esc_attr($wrap) . '" style="' . esc_attr($style) . '">';
|
||||
$auto_idx = 0;
|
||||
foreach ($cards as $card) {
|
||||
$html = $this->render_card($card, $cta_default, $auto_idx, $go_link, $top_band);
|
||||
if ($html !== '') {
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $card
|
||||
*/
|
||||
private function render_card(array $card, string $default_cta, int &$auto_idx, bool $go_link, bool $top_band): string {
|
||||
$title = trim((string) ($card['title'] ?? ''));
|
||||
$desc = trim((string) ($card['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$band = sanitize_key((string) ($card['band_color'] ?? 'auto'));
|
||||
if ($band === '' || $band === 'auto' || !in_array($band, self::BANDS, true)) {
|
||||
$band = self::BANDS[$auto_idx % count(self::BANDS)];
|
||||
$auto_idx++;
|
||||
}
|
||||
|
||||
$icon_html = '';
|
||||
$icon_img = (string) ($card['icon_image'] ?? '');
|
||||
$icon_fa = (string) ($card['icon'] ?? '');
|
||||
if ($icon_img !== '') {
|
||||
$icon_html = '<div class="aksrt-zci-icon"><img src="' . esc_url($icon_img) . '" alt="" loading="lazy" /></div>';
|
||||
} elseif ($icon_fa !== '') {
|
||||
$icon_html = '<div class="aksrt-zci-icon"><i class="' . esc_attr($icon_fa) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
|
||||
$tags_html = '';
|
||||
$tags = $card['tags'] ?? [];
|
||||
if (is_string($tags)) {
|
||||
$tags = array_filter(array_map('trim', explode(',', $tags)));
|
||||
$tags = array_map(static fn($t) => ['text' => $t, 'color' => ''], $tags);
|
||||
}
|
||||
if (is_array($tags) && $tags) {
|
||||
$tags_html .= '<div class="aksrt-zci-tags">';
|
||||
foreach ($tags as $tag) {
|
||||
if (is_string($tag)) {
|
||||
$tag = ['text' => $tag, 'color' => ''];
|
||||
}
|
||||
if (!is_array($tag)) {
|
||||
continue;
|
||||
}
|
||||
$tt = trim((string) ($tag['text'] ?? ''));
|
||||
if ($tt === '') {
|
||||
continue;
|
||||
}
|
||||
$color = sanitize_html_class((string) ($tag['color'] ?? ''));
|
||||
$tags_html .= '<span class="aksrt-zci-tag' . ($color !== '' ? ' ' . esc_attr($color) : '') . '">' . esc_html($tt) . '</span>';
|
||||
}
|
||||
$tags_html .= '</div>';
|
||||
}
|
||||
|
||||
$cta_text = trim((string) ($card['cta_text'] ?? ''));
|
||||
if ($cta_text === '') {
|
||||
$cta_text = $default_cta;
|
||||
}
|
||||
$url = (string) ($card['url'] ?? '');
|
||||
if ($url === '' && is_array($card['link'] ?? null)) {
|
||||
$url = (string) ($card['link']['url'] ?? '');
|
||||
}
|
||||
$target = (string) ($card['target'] ?? '');
|
||||
if ($target === '' && is_array($card['link'] ?? null)) {
|
||||
$target = (string) ($card['link']['target'] ?? '');
|
||||
}
|
||||
$cta_html = '';
|
||||
if ($cta_text !== '' && $url !== '') {
|
||||
if ($go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
|
||||
$cta_html = '<a class="aksrt-zci-cta" href="' . esc_url($url) . '"' . $tgt . '><span>' . esc_html($cta_text) . '</span><i class="fa fa-arrow-right" aria-hidden="true"></i></a>';
|
||||
}
|
||||
|
||||
$band_class = $top_band ? ' aksrt-zci-band-' . esc_attr($band) : '';
|
||||
$html = '<div class="aksrt-zci-card' . $band_class . '">';
|
||||
$html .= $icon_html;
|
||||
if ($title !== '') {
|
||||
$html .= '<div class="aksrt-zci-title">' . esc_html($title) . '</div>';
|
||||
}
|
||||
$html .= $tags_html;
|
||||
if ($desc !== '') {
|
||||
$html .= '<div class="aksrt-zci-desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$html .= $cta_html;
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize_cards($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'] ?? ''));
|
||||
$desc = sanitize_text_field((string) ($row['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => $desc,
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'icon_image' => esc_url_raw(trim((string) ($row['icon_image'] ?? ''))),
|
||||
'url' => esc_url_raw(trim((string) ($row['url'] ?? (is_array($row['link'] ?? null) ? ($row['link']['url'] ?? '') : '')))),
|
||||
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
|
||||
'cta_text' => sanitize_text_field((string) ($row['cta_text'] ?? '')),
|
||||
'band_color' => sanitize_key((string) ($row['band_color'] ?? 'auto')),
|
||||
'tags' => $row['tags'] ?? [],
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$cards = is_string($instance['cards'] ?? null)
|
||||
? (string) $instance['cards']
|
||||
: wp_json_encode($this->normalize_cards($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"功能","desc":"说明","icon":"fas fa-star","url":"https://","cta_text":"了解更多","tags":[{"text":"热门","color":"c-blue"}]}]'><?php echo esc_textarea((string) $cards); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 4))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('默认 CTA 文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('cta_text')); ?>" value="<?php echo esc_attr((string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_band')); ?>" value="1" <?php checked(!empty($instance['top_band']) || !isset($instance['top_band'])); ?>> <?php esc_html_e('顶部色带', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'cards' => $this->normalize_cards($new_instance['cards'] ?? []),
|
||||
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 4))),
|
||||
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
|
||||
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 12))),
|
||||
'top_band' => !empty($new_instance['top_band']) ? 1 : 0,
|
||||
'top_band_height' => max(20, min(160, (int) ($new_instance['top_band_height'] ?? 70))),
|
||||
'cta_text' => sanitize_text_field((string) ($new_instance['cta_text'] ?? '')),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Current plate info mini-box (zibll zib_bbs_widget_ui_plate_info).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetForumPlateInfo extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_plate_info',
|
||||
esc_html__('论坛 - 当前版块信息', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('在版块/帖子页侧栏显示当前 plate 信息。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_forum_plate_info',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$plate = $this->resolve_current_plate();
|
||||
if (!$plate) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$id = (int) $plate->ID;
|
||||
$url = get_permalink($id);
|
||||
$thumb = get_the_post_thumbnail_url($id, 'medium');
|
||||
$posts = (int) get_post_meta($id, '_aksrt_posts_count', true);
|
||||
$reply = (int) get_post_meta($id, '_aksrt_reply_count', true);
|
||||
$follow = (int) get_post_meta($id, '_aksrt_follow_count', true);
|
||||
$desc = $plate->post_excerpt !== '' ? $plate->post_excerpt : wp_trim_words(wp_strip_all_tags($plate->post_content), 30);
|
||||
|
||||
echo '<div class="aksrt-plate-info">';
|
||||
if ($thumb) {
|
||||
echo '<div class="aksrt-plate-info__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>';
|
||||
}
|
||||
echo '<div class="aksrt-plate-info__body">';
|
||||
echo '<a class="aksrt-plate-info__title" href="' . esc_url($url) . '">' . esc_html(get_the_title($plate)) . '</a>';
|
||||
if ($desc !== '') {
|
||||
echo '<div class="aksrt-plate-info__desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
echo '<div class="aksrt-plate-info__stats">';
|
||||
echo '<span><i class="fas fa-file-alt" aria-hidden="true"></i> ' . esc_html((string) $posts) . '</span>';
|
||||
echo '<span><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $reply) . '</span>';
|
||||
echo '<span><i class="fas fa-heart" aria-hidden="true"></i> ' . esc_html((string) $follow) . '</span>';
|
||||
echo '</div></div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function resolve_current_plate(): ?\WP_Post {
|
||||
if (is_singular('aksrt_forum_plate')) {
|
||||
$p = get_queried_object();
|
||||
return $p instanceof \WP_Post ? $p : null;
|
||||
}
|
||||
if (is_singular('aksrt_forum_topic')) {
|
||||
$topic_id = (int) get_queried_object_id();
|
||||
$plate_id = (int) get_post_meta($topic_id, '_aksrt_plate_id', true);
|
||||
if ($plate_id <= 0) {
|
||||
// Fallback: first linked board term → plate meta, or parent.
|
||||
$boards = get_the_terms($topic_id, 'aksrt_forum_board');
|
||||
if (!is_wp_error($boards) && !empty($boards[0])) {
|
||||
$linked = (int) get_term_meta((int) $boards[0]->term_id, '_aksrt_plate_id', true);
|
||||
if ($linked > 0) {
|
||||
$plate_id = $linked;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($plate_id > 0) {
|
||||
$p = get_post($plate_id);
|
||||
if ($p instanceof \WP_Post && $p->post_type === 'aksrt_forum_plate') {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
echo '<p class="description">' . esc_html__('无需配置。仅在版块页或帖子页显示对应版块信息。', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* Forum plate list (zibll zib_bbs_widget_ui_plate_lists lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetForumPlateLists extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_plate_lists',
|
||||
esc_html__('论坛 - 版块列表(Plate)', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('展示论坛 plate 版块列表,支持排序与卡片样式。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_forum_plate_lists',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
if (!post_type_exists('aksrt_forum_plate')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = max(1, min(30, (int) ($instance['showposts'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'menu_order'));
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'default'));
|
||||
$filter = sanitize_key((string) ($instance['filter'] ?? ''));
|
||||
$cat_raw = (string) ($instance['cat'] ?? '');
|
||||
$cat_ids = array_values(array_filter(array_map('intval', preg_split('/[\s,]+/', $cat_raw) ?: [])));
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_forum_plate',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $limit,
|
||||
'order' => 'ASC',
|
||||
];
|
||||
|
||||
switch ($orderby) {
|
||||
case 'date':
|
||||
$query_args['orderby'] = 'date';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'posts':
|
||||
$query_args['meta_key'] = '_aksrt_posts_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'views':
|
||||
$query_args['meta_key'] = '_aksrt_views';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'follow':
|
||||
$query_args['meta_key'] = '_aksrt_follow_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
default:
|
||||
$query_args['orderby'] = 'menu_order title';
|
||||
}
|
||||
|
||||
if ($cat_ids) {
|
||||
$query_args['tax_query'] = [[
|
||||
'taxonomy' => 'aksrt_plate_cat',
|
||||
'field' => 'term_id',
|
||||
'terms' => $cat_ids,
|
||||
]];
|
||||
}
|
||||
|
||||
if ($filter === 'is_hot') {
|
||||
$query_args['meta_query'] = [[
|
||||
'key' => '_aksrt_is_hot',
|
||||
'value' => '1',
|
||||
'compare' => '=',
|
||||
]];
|
||||
}
|
||||
|
||||
$plates = get_posts($query_args);
|
||||
if (!$plates) {
|
||||
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
|
||||
}
|
||||
|
||||
if ($style === 'card') {
|
||||
echo '<div class="aksrt-plate-cards">';
|
||||
foreach ($plates as $post) {
|
||||
$this->render_card($post);
|
||||
}
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<div class="aksrt-plate-lists">';
|
||||
foreach ($plates as $post) {
|
||||
$this->render_row($post);
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function render_row(\WP_Post $post): void {
|
||||
$id = (int) $post->ID;
|
||||
$url = get_permalink($id);
|
||||
$thumb = get_the_post_thumbnail_url($id, 'thumbnail');
|
||||
$icon = (string) get_post_meta($id, '_aksrt_icon', true);
|
||||
$count = (int) get_post_meta($id, '_aksrt_posts_count', true);
|
||||
$desc = $post->post_excerpt !== '' ? $post->post_excerpt : wp_trim_words(wp_strip_all_tags($post->post_content), 18);
|
||||
|
||||
echo '<a class="aksrt-plate-item" href="' . esc_url($url) . '">';
|
||||
echo '<div class="aksrt-plate-item__thumb">';
|
||||
if ($thumb) {
|
||||
echo '<img src="' . esc_url($thumb) . '" alt="" loading="lazy" />';
|
||||
} elseif ($icon !== '') {
|
||||
echo '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i>';
|
||||
} else {
|
||||
echo '<i class="fas fa-comments" aria-hidden="true"></i>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<div class="aksrt-plate-item__body">';
|
||||
echo '<div class="aksrt-plate-item__title">' . esc_html(get_the_title($post)) . '</div>';
|
||||
if ($desc !== '') {
|
||||
echo '<div class="aksrt-plate-item__desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
echo '<div class="aksrt-plate-item__meta"><span>' . esc_html(sprintf(/* translators: %d topic count */ __('%d 帖', 'aksrt'), $count)) . '</span></div>';
|
||||
echo '</div></a>';
|
||||
}
|
||||
|
||||
private function render_card(\WP_Post $post): void {
|
||||
$id = (int) $post->ID;
|
||||
$url = get_permalink($id);
|
||||
$thumb = get_the_post_thumbnail_url($id, 'medium');
|
||||
$count = (int) get_post_meta($id, '_aksrt_posts_count', true);
|
||||
$follow = (int) get_post_meta($id, '_aksrt_follow_count', true);
|
||||
|
||||
echo '<a class="aksrt-plate-card" href="' . esc_url($url) . '">';
|
||||
echo '<div class="aksrt-plate-card__cover"' . ($thumb ? ' style="background-image:url(' . esc_url($thumb) . ')"' : '') . '></div>';
|
||||
echo '<div class="aksrt-plate-card__title">' . esc_html(get_the_title($post)) . '</div>';
|
||||
echo '<div class="aksrt-plate-card__meta">';
|
||||
echo '<span>' . esc_html(sprintf(__('%d 帖', 'aksrt'), $count)) . '</span>';
|
||||
echo '<span>' . esc_html(sprintf(__('%d 关注', 'aksrt'), $follow)) . '</span>';
|
||||
echo '</div></a>';
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('版块列表', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('showposts')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['showposts'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="menu_order" <?php selected(($instance['orderby'] ?? 'menu_order'), 'menu_order'); ?>><?php esc_html_e('菜单顺序', 'aksrt'); ?></option>
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? ''), 'date'); ?>><?php esc_html_e('创建时间', 'aksrt'); ?></option>
|
||||
<option value="posts" <?php selected(($instance['orderby'] ?? ''), 'posts'); ?>><?php esc_html_e('帖子数', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
|
||||
<option value="follow" <?php selected(($instance['orderby'] ?? ''), 'follow'); ?>><?php esc_html_e('关注数', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="default" <?php selected(($instance['style'] ?? 'default'), 'default'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('筛选', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('filter')); ?>">
|
||||
<option value="" <?php selected(($instance['filter'] ?? ''), ''); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
|
||||
<option value="is_hot" <?php selected(($instance['filter'] ?? ''), 'is_hot'); ?>><?php esc_html_e('仅热门', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('版块分类 term ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ($instance['cat'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'menu_order'));
|
||||
if (!in_array($orderby, ['menu_order', 'date', 'posts', 'views', 'follow'], true)) {
|
||||
$orderby = 'menu_order';
|
||||
}
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'showposts' => max(1, min(30, (int) ($new_instance['showposts'] ?? 8))),
|
||||
'orderby' => $orderby,
|
||||
'style' => ($new_instance['style'] ?? '') === 'card' ? 'card' : 'default',
|
||||
'filter' => ($new_instance['filter'] ?? '') === 'is_hot' ? 'is_hot' : '',
|
||||
'cat' => sanitize_text_field((string) ($new_instance['cat'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* Current plate moderators (zibll zib_bbs_widget_ui_plate_moderator).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetForumPlateModerator extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_plate_moderator',
|
||||
esc_html__('论坛 - 版主列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('显示当前版块的版主与超级版主。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_forum_plate_moderator',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$plate = $this->resolve_current_plate();
|
||||
if (!$plate) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = (int) $plate->ID;
|
||||
$author_id = (int) $plate->post_author;
|
||||
$mods = get_post_meta($id, '_aksrt_moderators', true);
|
||||
if (!is_array($mods)) {
|
||||
$mods = [];
|
||||
}
|
||||
$mods = array_values(array_unique(array_filter(array_map('intval', $mods))));
|
||||
|
||||
$people = [];
|
||||
if ($author_id > 0) {
|
||||
$people[] = ['id' => $author_id, 'role' => 'author'];
|
||||
}
|
||||
foreach ($mods as $mid) {
|
||||
if ($mid === $author_id) {
|
||||
continue;
|
||||
}
|
||||
$people[] = ['id' => $mid, 'role' => 'moderator'];
|
||||
}
|
||||
if (!$people) {
|
||||
return;
|
||||
}
|
||||
|
||||
$title_text = (string) ($instance['title_text'] ?? __('版主', 'aksrt'));
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
echo '<div class="aksrt-plate-mods">';
|
||||
if ($title_text !== '') {
|
||||
echo '<div class="aksrt-plate-mods__head"><div class="title-theme">' . esc_html($title_text) . '</div></div>';
|
||||
}
|
||||
echo '<div class="aksrt-plate-mods__list">';
|
||||
foreach ($people as $row) {
|
||||
$uid = (int) $row['id'];
|
||||
$user = get_userdata($uid);
|
||||
if (!$user) {
|
||||
continue;
|
||||
}
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($uid, ['size' => 64])
|
||||
: get_avatar_url($uid, ['size' => 64]);
|
||||
$home = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($uid)
|
||||
: get_author_posts_url($uid);
|
||||
$role_label = $row['role'] === 'author'
|
||||
? __('超级版主', 'aksrt')
|
||||
: __('版主', 'aksrt');
|
||||
echo '<a class="aksrt-plate-mods__item" href="' . esc_url($home) . '">';
|
||||
echo '<img src="' . esc_url($avatar) . '" alt="" class="aksrt-plate-mods__avatar" loading="lazy" width="40" height="40" />';
|
||||
echo '<span class="aksrt-plate-mods__name">' . esc_html($user->display_name) . '</span>';
|
||||
echo '<span class="aksrt-plate-mods__role">' . esc_html($role_label) . '</span>';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function resolve_current_plate(): ?\WP_Post {
|
||||
if (is_singular('aksrt_forum_plate')) {
|
||||
$p = get_queried_object();
|
||||
return $p instanceof \WP_Post ? $p : null;
|
||||
}
|
||||
if (is_singular('aksrt_forum_topic')) {
|
||||
$topic_id = (int) get_queried_object_id();
|
||||
$plate_id = (int) get_post_meta($topic_id, '_aksrt_plate_id', true);
|
||||
if ($plate_id <= 0) {
|
||||
$boards = get_the_terms($topic_id, 'aksrt_forum_board');
|
||||
if (!is_wp_error($boards) && !empty($boards[0])) {
|
||||
$linked = (int) get_term_meta((int) $boards[0]->term_id, '_aksrt_plate_id', true);
|
||||
if ($linked > 0) {
|
||||
$plate_id = $linked;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($plate_id > 0) {
|
||||
$p = get_post($plate_id);
|
||||
if ($p instanceof \WP_Post && $p->post_type === 'aksrt_forum_plate') {
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_text')); ?>" value="<?php echo esc_attr((string) ($instance['title_text'] ?? __('版主', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p class="description"><?php esc_html_e('仅在版块/帖子页显示。', 'aksrt'); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title_text' => sanitize_text_field((string) ($new_instance['title_text'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Super embed / iframe widget (zibll zib_widget_ui_iframe).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetIframe extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_iframe',
|
||||
esc_html__('AKSRT 超级嵌入', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('嵌入在线视频/音乐播放器或任意 iframe 内容。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_iframe',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$raw = trim((string) ($instance['url'] ?? ''));
|
||||
if ($raw === '') {
|
||||
return;
|
||||
}
|
||||
$aspect = max(20, min(300, (int) ($instance['aspect'] ?? 55)));
|
||||
$allow_fs = !empty($instance['allowfullscreen']);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$iframe_html = '';
|
||||
if (stripos($raw, '<iframe') !== false && stripos($raw, '</iframe>') !== false) {
|
||||
$iframe_html = wp_kses(
|
||||
$raw,
|
||||
[
|
||||
'iframe' => [
|
||||
'src' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'frameborder' => true,
|
||||
'allow' => true,
|
||||
'allowfullscreen' => true,
|
||||
'loading' => true,
|
||||
'class' => true,
|
||||
'style' => true,
|
||||
'referrerpolicy' => true,
|
||||
'title' => true,
|
||||
'sandbox' => true,
|
||||
'data-src' => true,
|
||||
],
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$url = esc_url($raw);
|
||||
if ($url === '') {
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
$fs = $allow_fs ? ' allowfullscreen="allowfullscreen"' : '';
|
||||
$iframe_html = '<iframe class="aksrt-iframe-el" src="' . $url . '" loading="lazy" frameborder="0"' . $fs . '></iframe>';
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-iframe-embed">';
|
||||
echo '<div class="aksrt-iframe-ratio" style="padding-bottom:' . esc_attr((string) $aspect) . '%">';
|
||||
echo $iframe_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$url = (string) ($instance['url'] ?? '');
|
||||
$aspect = (int) ($instance['aspect'] ?? 55);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('嵌入地址或 iframe 代码', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="4" name="<?php echo esc_attr($this->get_field_name('url')); ?>"><?php echo esc_textarea($url); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="20" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('aspect')); ?>" value="<?php echo esc_attr((string) $aspect); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('allowfullscreen')); ?>" value="1" <?php checked(!empty($instance['allowfullscreen'])); ?>> <?php esc_html_e('允许全屏', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'url' => trim((string) ($new_instance['url'] ?? '')),
|
||||
'aspect' => max(20, min(300, (int) ($new_instance['aspect'] ?? 55))),
|
||||
'allowfullscreen' => !empty($new_instance['allowfullscreen']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
/**
|
||||
* Horizontal partners / friends scroll (zibll zib_widget_ui_partners_scroll).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPartnersScroll extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_partners_scroll',
|
||||
esc_html__('AKSRT 合作伙伴滚动', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('横向无缝滚动的合作伙伴/友情链接条。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_partners_scroll',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$partners = $this->resolve_partners($instance);
|
||||
if (!$partners) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = max(1, min(4, (int) ($instance['rows'] ?? 1)));
|
||||
$m_rows = max(1, min(4, (int) ($instance['m_rows'] ?? 1)));
|
||||
$style_type = sanitize_key((string) ($instance['style_type'] ?? 'card'));
|
||||
if (!in_array($style_type, ['card', 'logo', 'chip'], true)) {
|
||||
$style_type = 'card';
|
||||
}
|
||||
$card_w = max(60, min(400, (int) ($instance['card_width'] ?? 220)));
|
||||
$card_h = max(40, min(200, (int) ($instance['card_height'] ?? 90)));
|
||||
$speed = max(1, min(20, (int) ($instance['speed'] ?? 3)));
|
||||
$direction = sanitize_key((string) ($instance['direction'] ?? 'alternate'));
|
||||
$pause = !empty($instance['pause_hover']) || !isset($instance['pause_hover']);
|
||||
$card_bg = !empty($instance['card_bg']) || !isset($instance['card_bg']);
|
||||
$end_fade = !empty($instance['end_fade']) || !isset($instance['end_fade']);
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$show_bg = !empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg']);
|
||||
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 10)));
|
||||
|
||||
$use_rows = wp_is_mobile() ? $m_rows : $rows;
|
||||
$rows_data = array_fill(0, $use_rows, []);
|
||||
foreach (array_values($partners) as $i => $p) {
|
||||
$rows_data[$i % $use_rows][] = $p;
|
||||
}
|
||||
$rows_data = array_values(array_filter($rows_data, static fn($r) => !empty($r)));
|
||||
|
||||
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_class = 'aksrt-partners-scroll aksrt-ps-style-' . $style_type;
|
||||
if ($pause) {
|
||||
$wrap_class .= ' aksrt-ps-pause-hover';
|
||||
}
|
||||
if ($card_bg) {
|
||||
$wrap_class .= ' aksrt-ps-card-bg';
|
||||
}
|
||||
if ($end_fade) {
|
||||
$wrap_class .= ' aksrt-ps-end-fade';
|
||||
}
|
||||
|
||||
$style = sprintf(
|
||||
'--aksrt-ps-w:%dpx;--aksrt-ps-h:%dpx;--aksrt-ps-radius:%dpx;--aksrt-ps-speed:%d;',
|
||||
$card_w,
|
||||
$card_h,
|
||||
$radius,
|
||||
$speed
|
||||
);
|
||||
|
||||
$outer = $show_bg ? 'aksrt-partners-scroll-wrap' : 'aksrt-partners-scroll-wrap is-bare';
|
||||
echo '<div class="' . esc_attr($outer) . '">';
|
||||
echo '<div class="' . esc_attr($wrap_class) . '" style="' . esc_attr($style) . '">';
|
||||
|
||||
foreach ($rows_data as $row_index => $row_partners) {
|
||||
switch ($direction) {
|
||||
case 'left':
|
||||
$dir = 'left';
|
||||
break;
|
||||
case 'right':
|
||||
$dir = 'right';
|
||||
break;
|
||||
default:
|
||||
$dir = ($row_index % 2 === 0) ? 'left' : 'right';
|
||||
}
|
||||
echo '<div class="aksrt-ps-row aksrt-ps-dir-' . esc_attr($dir) . '">';
|
||||
echo '<div class="aksrt-ps-track">';
|
||||
// Duplicate group for seamless CSS marquee.
|
||||
for ($dup = 0; $dup < 2; $dup++) {
|
||||
echo '<div class="aksrt-ps-group">';
|
||||
foreach ($row_partners as $partner) {
|
||||
echo $this->render_card($partner, $style_type, $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
|
||||
echo '</div></div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $partner
|
||||
*/
|
||||
private function render_card(array $partner, string $style_type, bool $go_link): string {
|
||||
$title = trim((string) ($partner['title'] ?? ''));
|
||||
$desc = trim((string) ($partner['desc'] ?? ''));
|
||||
$image = (string) ($partner['image'] ?? '');
|
||||
$url = (string) ($partner['url'] ?? '');
|
||||
$target = (string) ($partner['target'] ?? '');
|
||||
|
||||
if ($image === '' && $title === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$alt = esc_attr($title !== '' ? $title : __('合作伙伴', 'aksrt'));
|
||||
$img_tag = $image !== ''
|
||||
? '<img class="aksrt-ps-img" src="' . esc_url($image) . '" alt="' . $alt . '" loading="lazy" />'
|
||||
: '';
|
||||
|
||||
$inner = '';
|
||||
if ($style_type === 'logo') {
|
||||
if ($img_tag === '') {
|
||||
return '';
|
||||
}
|
||||
$inner = $img_tag;
|
||||
} elseif ($style_type === 'chip') {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<span class="aksrt-ps-chip-icon">' . $img_tag . '</span>';
|
||||
}
|
||||
$inner .= '<span class="aksrt-ps-chip-title">' . esc_html($title) . '</span>';
|
||||
} else {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-icon">' . $img_tag . '</div>';
|
||||
}
|
||||
$inner .= '<div class="aksrt-ps-card-text">';
|
||||
if ($title !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-title">' . esc_html($title) . '</div>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$inner .= '</div>';
|
||||
}
|
||||
|
||||
if ($url !== '') {
|
||||
if ($go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
|
||||
return '<a class="aksrt-ps-item" href="' . esc_url($url) . '"' . $tgt . '>' . $inner . '</a>';
|
||||
}
|
||||
|
||||
return '<div class="aksrt-ps-item">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
|
||||
*/
|
||||
private function resolve_partners(array $instance): array {
|
||||
$source = sanitize_key((string) ($instance['link_type'] ?? 'manual'));
|
||||
if ($source === 'link') {
|
||||
$args = [
|
||||
'orderby' => sanitize_key((string) ($instance['link_orderby'] ?? 'name')) ?: 'name',
|
||||
'order' => strtoupper((string) ($instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'limit' => max(1, min(100, (int) ($instance['link_limit'] ?? 50))),
|
||||
];
|
||||
$cats = $instance['link_cats'] ?? '';
|
||||
if (is_array($cats)) {
|
||||
$cats = implode(',', array_map('intval', $cats));
|
||||
}
|
||||
$cats = trim((string) $cats);
|
||||
if ($cats !== '') {
|
||||
$args['category'] = $cats;
|
||||
}
|
||||
$bookmarks = get_bookmarks($args);
|
||||
$out = [];
|
||||
foreach ($bookmarks as $b) {
|
||||
$out[] = [
|
||||
'title' => (string) $b->link_name,
|
||||
'desc' => (string) $b->link_description,
|
||||
'image' => (string) $b->link_image,
|
||||
'url' => (string) $b->link_url,
|
||||
'target' => (string) $b->link_target,
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
return $this->normalize_manual($instance['partners'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
|
||||
*/
|
||||
private function normalize_manual($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'] ?? ''));
|
||||
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
|
||||
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
|
||||
if ($title === '' && $image === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'image' => $image,
|
||||
'url' => $url,
|
||||
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
|
||||
];
|
||||
if (count($out) >= 40) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$link_type = (string) ($instance['link_type'] ?? 'manual');
|
||||
$partners_json = is_string($instance['partners'] ?? null)
|
||||
? (string) $instance['partners']
|
||||
: wp_json_encode($this->normalize_manual($instance['partners'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数据来源', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('link_type')); ?>">
|
||||
<option value="manual" <?php selected($link_type, 'manual'); ?>><?php esc_html_e('手动 JSON', 'aksrt'); ?></option>
|
||||
<option value="link" <?php selected($link_type, 'link'); ?>><?php esc_html_e('WordPress 链接(书签)', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('合作伙伴 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('partners')); ?>" placeholder='[{"title":"Name","image":"https://...","url":"https://...","desc":""}]'><?php echo esc_textarea((string) $partners_json); ?></textarea>
|
||||
</label>
|
||||
<small><?php esc_html_e('字段: title, image, url, desc, target', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('链接分类 ID(逗号分隔,书签模式)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('link_cats')); ?>" value="<?php echo esc_attr((string) ($instance['link_cats'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('书签数量上限', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('link_limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['link_limit'] ?? 50))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style_type')); ?>">
|
||||
<option value="card" <?php selected(($instance['style_type'] ?? 'card'), 'card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
|
||||
<option value="logo" <?php selected(($instance['style_type'] ?? ''), 'logo'); ?>><?php esc_html_e('纯 Logo', 'aksrt'); ?></option>
|
||||
<option value="chip" <?php selected(($instance['style_type'] ?? ''), 'chip'); ?>><?php esc_html_e('胶囊', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 行数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['rows'] ?? 1))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动行数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('m_rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_rows'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片宽', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="60" max="400" name="<?php echo esc_attr($this->get_field_name('card_width')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_width'] ?? 220))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('高', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="40" max="200" name="<?php echo esc_attr($this->get_field_name('card_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_height'] ?? 90))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('速度', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['speed'] ?? 3))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('方向', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
|
||||
<option value="alternate" <?php selected(($instance['direction'] ?? 'alternate'), 'alternate'); ?>><?php esc_html_e('交替', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['direction'] ?? ''), 'left'); ?>><?php esc_html_e('向左', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['direction'] ?? ''), 'right'); ?>><?php esc_html_e('向右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pause_hover')); ?>" value="1" <?php checked(!empty($instance['pause_hover']) || !isset($instance['pause_hover'])); ?>> <?php esc_html_e('悬停暂停', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('card_bg')); ?>" value="1" <?php checked(!empty($instance['card_bg']) || !isset($instance['card_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('end_fade')); ?>" value="1" <?php checked(!empty($instance['end_fade']) || !isset($instance['end_fade'])); ?>> <?php esc_html_e('两端渐隐', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg'])); ?>> <?php esc_html_e('显示背景盒子', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'link_type' => in_array(($new_instance['link_type'] ?? ''), ['manual', 'link'], true) ? $new_instance['link_type'] : 'manual',
|
||||
'partners' => $this->normalize_manual($new_instance['partners'] ?? []),
|
||||
'link_cats' => sanitize_text_field((string) ($new_instance['link_cats'] ?? '')),
|
||||
'link_limit' => max(1, min(100, (int) ($new_instance['link_limit'] ?? 50))),
|
||||
'link_orderby' => sanitize_key((string) ($new_instance['link_orderby'] ?? 'name')),
|
||||
'link_order' => strtoupper((string) ($new_instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'style_type' => in_array(($new_instance['style_type'] ?? ''), ['card', 'logo', 'chip'], true) ? $new_instance['style_type'] : 'card',
|
||||
'rows' => max(1, min(4, (int) ($new_instance['rows'] ?? 1))),
|
||||
'm_rows' => max(1, min(4, (int) ($new_instance['m_rows'] ?? 1))),
|
||||
'card_width' => max(60, min(400, (int) ($new_instance['card_width'] ?? 220))),
|
||||
'card_height' => max(40, min(200, (int) ($new_instance['card_height'] ?? 90))),
|
||||
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 10))),
|
||||
'speed' => max(1, min(20, (int) ($new_instance['speed'] ?? 3))),
|
||||
'direction' => in_array(($new_instance['direction'] ?? ''), ['alternate', 'left', 'right'], true) ? $new_instance['direction'] : 'alternate',
|
||||
'pause_hover' => !empty($new_instance['pause_hover']) ? 1 : 0,
|
||||
'card_bg' => !empty($new_instance['card_bg']) ? 1 : 0,
|
||||
'end_fade' => !empty($new_instance['end_fade']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Text title / section heading (zibll zib_widget_ui_text_title lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetTextTitle extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_text_title',
|
||||
esc_html__('AKSRT 文字标题', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('自定义标题 + 副标题区块,用于模块分隔。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_text_title',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = trim((string) ($instance['title'] ?? ''));
|
||||
$desc = trim((string) ($instance['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
|
||||
if (!in_array($align, ['left', 'center', 'right'], true)) {
|
||||
$align = 'center';
|
||||
}
|
||||
$icon_left = sanitize_text_field((string) ($instance['title_icon_left'] ?? ''));
|
||||
$icon_right = sanitize_text_field((string) ($instance['title_icon_right'] ?? ''));
|
||||
$title_size = max(12, min(48, (int) ($instance['title_size'] ?? 22)));
|
||||
$desc_size = max(11, min(24, (int) ($instance['desc_size'] ?? 14)));
|
||||
$title_color = sanitize_hex_color((string) ($instance['title_color'] ?? '')) ?: '';
|
||||
$desc_color = sanitize_hex_color((string) ($instance['desc_color'] ?? '')) ?: '';
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$style = sprintf(
|
||||
'--aksrt-tt-title-size:%dpx;--aksrt-tt-desc-size:%dpx;%s%s',
|
||||
$title_size,
|
||||
$desc_size,
|
||||
$title_color !== '' ? '--aksrt-tt-title-color:' . $title_color . ';' : '',
|
||||
$desc_color !== '' ? '--aksrt-tt-desc-color:' . $desc_color . ';' : ''
|
||||
);
|
||||
|
||||
echo '<div class="aksrt-text-title align-' . esc_attr($align) . '" style="' . esc_attr($style) . '">';
|
||||
if ($title !== '') {
|
||||
echo '<div class="aksrt-text-title__main">';
|
||||
if ($icon_left !== '') {
|
||||
echo '<span class="aksrt-text-title__icon"><i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '<span class="aksrt-text-title__text">' . esc_html($title) . '</span>';
|
||||
if ($icon_right !== '') {
|
||||
echo '<span class="aksrt-text-title__icon"><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
echo '<div class="aksrt-text-title__desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('副标题 / 描述', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('desc')); ?>"><?php echo esc_textarea((string) ($instance['desc'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('对齐', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('align')); ?>">
|
||||
<option value="center" <?php selected(($instance['align'] ?? 'center'), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['align'] ?? ''), 'left'); ?>><?php esc_html_e('左', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['align'] ?? ''), 'right'); ?>><?php esc_html_e('右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('左图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_left')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_left'] ?? '')); ?>" placeholder="fas fa-star">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('右图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_right')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_right'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题字号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="12" max="48" name="<?php echo esc_attr($this->get_field_name('title_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['title_size'] ?? 22))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('描述字号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="11" max="24" name="<?php echo esc_attr($this->get_field_name('desc_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['desc_size'] ?? 14))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题色 #hex', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_color')); ?>" value="<?php echo esc_attr((string) ($instance['title_color'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('描述色 #hex', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('desc_color')); ?>" value="<?php echo esc_attr((string) ($instance['desc_color'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'desc' => sanitize_text_field((string) ($new_instance['desc'] ?? '')),
|
||||
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
|
||||
'title_icon_left' => sanitize_text_field((string) ($new_instance['title_icon_left'] ?? '')),
|
||||
'title_icon_right' => sanitize_text_field((string) ($new_instance['title_icon_right'] ?? '')),
|
||||
'title_size' => max(12, min(48, (int) ($new_instance['title_size'] ?? 22))),
|
||||
'desc_size' => max(11, min(24, (int) ($new_instance['desc_size'] ?? 14))),
|
||||
'title_color' => sanitize_hex_color((string) ($new_instance['title_color'] ?? '')) ?: '',
|
||||
'desc_color' => sanitize_hex_color((string) ($new_instance['desc_color'] ?? '')) ?: '',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
/**
|
||||
* Registered user list (zibll widget_ui_user_lists).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserLists extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_lists',
|
||||
esc_html__('AKSRT 用户列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('展示站点用户头像网格,支持排序与筛选。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_user_lists',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
|
||||
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
|
||||
$exclude = $this->parse_ids((string) ($instance['exclude'] ?? ''));
|
||||
$hide_box = !empty($instance['hide_box']);
|
||||
|
||||
$query = [
|
||||
'number' => $number,
|
||||
'order' => $order,
|
||||
'fields' => ['ID', 'display_name', 'user_nicename'],
|
||||
];
|
||||
|
||||
if ($include) {
|
||||
$query['include'] = $include;
|
||||
$query['orderby'] = 'include';
|
||||
} else {
|
||||
switch ($orderby) {
|
||||
case 'display_name':
|
||||
$query['orderby'] = 'display_name';
|
||||
break;
|
||||
case 'post_count':
|
||||
$query['orderby'] = 'post_count';
|
||||
break;
|
||||
case 'last_login':
|
||||
$query['meta_key'] = 'aksrt_last_login';
|
||||
$query['orderby'] = 'meta_value';
|
||||
break;
|
||||
case 'followed-user-count':
|
||||
// Fallback: registered date; fan ranking needs denormalized meta when available.
|
||||
$query['meta_key'] = 'aksrt_follower_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
break;
|
||||
default:
|
||||
$query['orderby'] = 'user_registered';
|
||||
}
|
||||
}
|
||||
if ($exclude) {
|
||||
$query['exclude'] = $exclude;
|
||||
}
|
||||
|
||||
$users = get_users($query);
|
||||
if (!$users) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If follower meta empty for all, re-query by registered when fan order requested.
|
||||
if ($orderby === 'followed-user-count' && !$include) {
|
||||
$has_meta = false;
|
||||
foreach ($users as $u) {
|
||||
if ((int) get_user_meta($u->ID, 'aksrt_follower_count', true) > 0) {
|
||||
$has_meta = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$has_meta && class_exists('\AKSRT\User\Follow')) {
|
||||
// Rank by live follower counts (small N only).
|
||||
usort($users, static function ($a, $b) {
|
||||
$fa = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $a->ID);
|
||||
$fb = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $b->ID);
|
||||
return $fb <=> $fa;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 = $hide_box ? 'aksrt-user-lists is-bare' : 'aksrt-user-lists';
|
||||
echo '<div class="' . esc_attr($wrap) . '">';
|
||||
foreach ($users as $user) {
|
||||
$uid = (int) $user->ID;
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($uid, ['size' => 96])
|
||||
: get_avatar_url($uid, ['size' => 96]);
|
||||
$home = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($uid)
|
||||
: get_author_posts_url($uid);
|
||||
$name = $user->display_name ?: $user->user_nicename;
|
||||
echo '<a class="aksrt-user-lists__item" href="' . esc_url($home) . '" title="' . esc_attr($name) . '">';
|
||||
echo '<img class="aksrt-user-lists__avatar" src="' . esc_url($avatar) . '" alt="' . esc_attr($name) . '" loading="lazy" width="48" height="48" />';
|
||||
echo '<span class="aksrt-user-lists__name">' . esc_html($name) . '</span>';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int>
|
||||
*/
|
||||
private function parse_ids(string $raw): array {
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$parts = preg_split('/[\s,]+/', $raw) ?: [];
|
||||
$ids = [];
|
||||
foreach ($parts as $p) {
|
||||
$id = (int) $p;
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('显示数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('包含用户 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排除用户 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('exclude')); ?>" value="<?php echo esc_attr((string) ($instance['exclude'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="user_registered" <?php selected(($instance['orderby'] ?? 'user_registered'), 'user_registered'); ?>><?php esc_html_e('注册时间', 'aksrt'); ?></option>
|
||||
<option value="display_name" <?php selected(($instance['orderby'] ?? ''), 'display_name'); ?>><?php esc_html_e('昵称', 'aksrt'); ?></option>
|
||||
<option value="post_count" <?php selected(($instance['orderby'] ?? ''), 'post_count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
|
||||
<option value="last_login" <?php selected(($instance['orderby'] ?? ''), 'last_login'); ?>><?php esc_html_e('最后登录', 'aksrt'); ?></option>
|
||||
<option value="followed-user-count" <?php selected(($instance['orderby'] ?? ''), 'followed-user-count'); ?>><?php esc_html_e('粉丝数', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('顺序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected(($instance['order'] ?? 'DESC'), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="ASC" <?php selected(($instance['order'] ?? ''), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'user_registered'));
|
||||
$allowed = ['user_registered', 'display_name', 'post_count', 'last_login', 'followed-user-count'];
|
||||
if (!in_array($orderby, $allowed, true)) {
|
||||
$orderby = 'user_registered';
|
||||
}
|
||||
return [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))),
|
||||
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
|
||||
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'hide_box'=> !empty($new_instance['hide_box']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user