Merge pull request 'Worktree ux depth batch3' (#220) from worktree-ux-depth-batch3 into main

Reviewed-on: CYL/wordpress-theme-aksrt#220
This commit is contained in:
2026-07-19 00:29:56 +08:00
28 changed files with 4253 additions and 24 deletions
+739
View File
@@ -0,0 +1,739 @@
/**
* 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;
}
/* ---- Pricing plans ---- */
.aksrt-pricing { margin-bottom: 16px; }
.aksrt-pricing__cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.aksrt-pricing__card {
position: relative;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 14px;
padding: 22px 18px 18px;
background: var(--surface-1, #fff);
display: flex;
flex-direction: column;
min-height: 100%;
}
.aksrt-pricing__card.is-featured {
box-shadow: 0 10px 28px rgba(0,0,0,.08);
border-color: transparent;
border-top: 3px solid var(--plan-accent, #3b82f6);
}
.aksrt-pricing__card.accent-pink { --plan-accent: #ec4899; }
.aksrt-pricing__card.accent-purple { --plan-accent: #a855f7; }
.aksrt-pricing__card.accent-blue { --plan-accent: #3b82f6; }
.aksrt-pricing__card.accent-green { --plan-accent: #22c55e; }
.aksrt-pricing__card.accent-yellow { --plan-accent: #eab308; }
.aksrt-pricing__card.accent-cyan { --plan-accent: #06b6d4; }
.aksrt-pricing__badge {
position: absolute;
top: 12px;
right: 12px;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: var(--plan-accent, #3b82f6);
color: #fff;
}
.aksrt-pricing__name { font-size: 18px; font-weight: 700; margin: 0 0 6px; }
.aksrt-pricing__desc { font-size: 13px; color: var(--muted-2-color, #888); margin: 0 0 12px; }
.aksrt-pricing__price { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.aksrt-pricing__main { font-size: 32px; font-weight: 800; color: var(--plan-accent, #3b82f6); line-height: 1; }
.aksrt-pricing__mark { font-size: .55em; margin-right: 2px; }
.aksrt-pricing__original { font-size: 13px; color: #999; text-decoration: line-through; }
.aksrt-pricing__features { list-style: none; margin: 0 0 16px; padding: 0; flex: 1; }
.aksrt-pricing__features li { display: flex; gap: 8px; font-size: 13px; padding: 5px 0; }
.aksrt-pricing__features i { color: var(--plan-accent, #3b82f6); margin-top: 2px; }
.aksrt-pricing__btn {
display: block;
text-align: center;
padding: 10px 14px;
border-radius: 999px;
background: var(--plan-accent, #3b82f6);
color: #fff !important;
text-decoration: none;
font-weight: 600;
}
.aksrt-pricing__trust {
display: flex;
flex-wrap: wrap;
gap: 14px 22px;
margin-top: 18px;
justify-content: center;
}
.aksrt-pricing__trust-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; }
.aksrt-pricing__trust-item i { color: var(--color-primary, #3b82f6); margin-top: 2px; }
.aksrt-pricing__trust-item strong { display: block; }
.aksrt-pricing__trust-item span { color: var(--muted-2-color, #888); font-size: 12px; }
/* ---- FAQ ---- */
.aksrt-faq { margin-bottom: 16px; }
.aksrt-faq__search { display: flex; gap: 8px; margin-bottom: 14px; }
.aksrt-faq__search-input {
flex: 1;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 8px;
padding: 8px 12px;
}
.aksrt-faq__search-btn {
border: 0;
border-radius: 8px;
padding: 8px 14px;
background: var(--color-primary, #3b82f6);
color: #fff;
cursor: pointer;
}
.aksrt-faq__item {
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 10px;
padding: 0;
margin-bottom: 10px;
background: var(--surface-1, #fff);
}
.aksrt-faq__q {
list-style: none;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
cursor: pointer;
font-weight: 600;
}
.aksrt-faq__q::-webkit-details-marker { display: none; }
.aksrt-faq__num {
width: 28px;
height: 28px;
border-radius: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
background: var(--surface-2, #f3f4f6);
color: var(--color-primary, #3b82f6);
flex-shrink: 0;
}
.aksrt-faq__a {
padding: 0 14px 14px 52px;
font-size: 13px;
color: var(--muted-2-color, #666);
line-height: 1.6;
}
.aksrt-faq__aside { display: grid; gap: 12px; margin-top: 14px; }
.aksrt-faq__doc, .aksrt-faq__help {
border-radius: 10px;
border: 1px solid var(--color-line, #e5e7eb);
padding: 14px;
background: var(--surface-2, #f8fafc);
}
.aksrt-faq__help-btn {
display: inline-block;
margin-top: 8px;
padding: 6px 12px;
border-radius: 999px;
background: var(--color-primary, #3b82f6);
color: #fff !important;
text-decoration: none;
font-size: 13px;
}
/* ---- Stats ---- */
.aksrt-stats { margin-bottom: 16px; }
.aksrt-stats__intro { margin-bottom: 14px; }
.aksrt-stats__intro-title { font-size: 22px; margin: 0 0 6px; }
.aksrt-stats__intro-sub { color: var(--muted-2-color, #888); margin: 0; font-size: 13px; }
.aksrt-stats__grid {
display: grid;
grid-template-columns: repeat(var(--aksrt-stats-pc, 4), minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 767.98px) {
.aksrt-stats__grid {
grid-template-columns: repeat(var(--aksrt-stats-m, 2), minmax(0, 1fr));
}
}
.aksrt-stats__item {
border-radius: 12px;
padding: 16px 14px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
text-align: center;
}
.aksrt-stats__item.color-pink { --stat-c: #ec4899; }
.aksrt-stats__item.color-purple { --stat-c: #a855f7; }
.aksrt-stats__item.color-orange { --stat-c: #f97316; }
.aksrt-stats__item.color-blue { --stat-c: #3b82f6; }
.aksrt-stats__item.color-green { --stat-c: #22c55e; }
.aksrt-stats__icon { font-size: 20px; color: var(--stat-c, #3b82f6); margin-bottom: 8px; }
.aksrt-stats__value { font-size: 28px; font-weight: 800; color: var(--stat-c, #3b82f6); line-height: 1.1; }
.aksrt-stats__prefix, .aksrt-stats__unit { font-size: .55em; font-weight: 600; }
.aksrt-stats__label { margin-top: 6px; font-size: 12px; color: var(--muted-2-color, #888); }
/* ---- Forum topic lists ---- */
.aksrt-ftopic-list { list-style: none; margin: 0; padding: 0; }
.aksrt-ftopic-list__item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 8px 0;
border-bottom: 1px dashed var(--color-line, #e5e7eb);
}
.aksrt-ftopic-list__item:last-child { border-bottom: 0; }
.aksrt-ftopic-list__link {
flex: 1;
min-width: 0;
color: inherit;
text-decoration: none;
font-size: 13px;
line-height: 1.45;
}
.aksrt-ftopic-list__views {
flex-shrink: 0;
font-size: 11px;
color: var(--muted-2-color, #999);
}
.aksrt-ftopic-list.is-detail .aksrt-ftopic-list__item { flex-direction: column; gap: 4px; }
.aksrt-ftopic-list__meta {
display: flex;
gap: 10px;
font-size: 11px;
color: var(--muted-2-color, #999);
}
/* Forum widget sidebar shell */
.aksrt-forum-widget-zone { margin-top: 12px; }
.aksrt-forum-widget-zone .zib-widget { margin-bottom: 14px; }
+110
View File
@@ -0,0 +1,110 @@
/**
* Batch3c: reviews + highlights.
*/
.aksrt-reviews {
margin-bottom: 16px;
--aksrt-rev-pc: 3;
--aksrt-rev-m: 1.2;
}
.aksrt-reviews__track {
display: grid;
grid-auto-flow: column;
grid-auto-columns: calc((100% - (var(--aksrt-rev-pc) - 1) * 12px) / var(--aksrt-rev-pc));
gap: 12px;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 6px;
-webkit-overflow-scrolling: touch;
}
@media (max-width: 767.98px) {
.aksrt-reviews__track {
grid-auto-columns: calc((100% - (var(--aksrt-rev-m) - 1) * 12px) / var(--aksrt-rev-m));
}
}
.aksrt-reviews__slide { scroll-snap-align: start; min-width: 0; }
.aksrt-reviews__card {
position: relative;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 12px;
padding: 16px;
background: var(--surface-1, #fff);
min-height: 100%;
}
.aksrt-reviews__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.aksrt-reviews__avatar {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
background: var(--surface-2, #eee);
display: inline-flex;
align-items: center;
justify-content: center;
color: #999;
flex-shrink: 0;
}
.aksrt-reviews__name { font-weight: 700; font-size: 14px; }
.aksrt-reviews__name a { color: inherit; text-decoration: none; }
.aksrt-reviews__role { font-size: 12px; color: var(--muted-2-color, #888); }
.aksrt-reviews__stars { margin-bottom: 8px; font-size: 12px; }
.aksrt-reviews__stars .is-on { color: #f5a623; }
.aksrt-reviews__stars .is-off { color: #ddd; }
.aksrt-reviews__content {
font-size: 13px;
line-height: 1.55;
color: var(--color-ink-2, #444);
margin: 0;
}
.aksrt-reviews__quote {
position: absolute;
right: 14px;
bottom: 10px;
opacity: .12;
font-size: 28px;
}
.aksrt-highlights { margin-bottom: 16px; display: flex; flex-direction: column; gap: 12px; }
.aksrt-highlights__row {
display: grid;
gap: 12px;
}
.aksrt-highlights__row.is-large { grid-template-columns: 1fr; }
.aksrt-highlights__row.is-medium { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.aksrt-highlights__row.is-small { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 767.98px) {
.aksrt-highlights__row.is-medium,
.aksrt-highlights__row.is-small { grid-template-columns: 1fr; }
}
.aksrt-hl-card {
display: flex;
gap: 12px;
border-radius: 12px;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
padding: 14px;
text-decoration: none;
color: inherit;
overflow: hidden;
min-height: 100%;
}
.aksrt-hl-card.color-blue { --hl: #3b82f6; }
.aksrt-hl-card.color-pink { --hl: #ec4899; }
.aksrt-hl-card.color-purple { --hl: #a855f7; }
.aksrt-hl-card.color-green { --hl: #22c55e; }
.aksrt-hl-card.color-orange { --hl: #f97316; }
.aksrt-hl-card.color-cyan { --hl: #06b6d4; }
.aksrt-hl-card.size-large { min-height: 140px; }
.aksrt-hl-card__media {
width: 96px;
flex-shrink: 0;
border-radius: 10px;
overflow: hidden;
background: var(--surface-2, #f3f4f6);
}
.aksrt-hl-card.size-large .aksrt-hl-card__media { width: 160px; }
.aksrt-hl-card__media img { width: 100%; height: 100%; object-fit: cover; display: block; min-height: 80px; }
.aksrt-hl-card__body { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.aksrt-hl-card__icon { color: var(--hl, #3b82f6); font-size: 22px; margin-bottom: 8px; }
.aksrt-hl-card__title { font-weight: 700; font-size: 15px; margin-bottom: 4px; }
.aksrt-hl-card__desc { font-size: 12px; color: var(--muted-2-color, #888); line-height: 1.5; }
+163
View File
@@ -0,0 +1,163 @@
/**
* Batch3d: feature intro, term cards, paid posts.
*/
.aksrt-fi {
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: 28px;
align-items: center;
margin-bottom: 20px;
}
.aksrt-fi.media-left { grid-template-columns: 1.1fr 1fr; }
.aksrt-fi.media-left .aksrt-fi__text { order: 2; }
.aksrt-fi.media-left .aksrt-fi__media { order: 1; }
@media (max-width: 991.98px) {
.aksrt-fi,
.aksrt-fi.media-left {
grid-template-columns: 1fr;
}
.aksrt-fi.media-left .aksrt-fi__text,
.aksrt-fi.media-left .aksrt-fi__media { order: initial; }
}
.aksrt-fi__title {
font-size: 28px;
line-height: 1.25;
margin: 0 0 12px;
}
.aksrt-fi__desc {
font-size: 14px;
color: var(--muted-2-color, #666);
line-height: 1.7;
margin-bottom: 14px;
}
.aksrt-fi__stat {
display: flex;
align-items: baseline;
gap: 6px;
margin-bottom: 14px;
}
.aksrt-fi__stat-num {
font-size: 36px;
font-weight: 800;
color: var(--color-primary, #3b82f6);
line-height: 1;
}
.aksrt-fi__stat-plus { font-size: 22px; font-weight: 700; color: var(--color-primary, #3b82f6); }
.aksrt-fi__stat-label { font-size: 13px; color: var(--muted-2-color, #888); margin-left: 4px; }
.aksrt-fi__tabs { display: flex; flex-wrap: wrap; gap: 8px; }
.aksrt-fi__tab {
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
border-radius: 999px;
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
}
.aksrt-fi__tab.is-active {
background: var(--color-primary, #3b82f6);
border-color: transparent;
color: #fff;
}
.aksrt-fi__panel { display: none; }
.aksrt-fi__panel.is-active { display: block; }
.aksrt-fi__browser {
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-2, #f3f4f6);
box-shadow: 0 12px 32px rgba(0,0,0,.08);
}
.aksrt-fi__browser.is-plain { box-shadow: none; }
.aksrt-fi__browser-bar {
height: 28px;
background: #eef1f5;
display: flex;
align-items: center;
gap: 6px;
padding: 0 12px;
}
.aksrt-fi__browser-bar span {
width: 8px;
height: 8px;
border-radius: 50%;
background: #d0d5dd;
}
.aksrt-fi__browser-bar span:nth-child(1) { background: #ff5f57; }
.aksrt-fi__browser-bar span:nth-child(2) { background: #febc2e; }
.aksrt-fi__browser-bar span:nth-child(3) { background: #28c840; }
.aksrt-fi__browser-body {
position: relative;
width: 100%;
height: 0;
background: #111;
}
.aksrt-fi__img,
.aksrt-fi__video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-term-cards {
display: grid;
grid-template-columns: repeat(var(--aksrt-tc-cols, 2), minmax(0, 1fr));
gap: 12px;
margin-bottom: 12px;
}
.aksrt-term-card {
display: block;
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--color-line, #e5e7eb);
text-decoration: none;
color: inherit;
background: var(--surface-1, #fff);
}
.aksrt-term-card__cover {
height: 88px;
background: var(--surface-2, #f3f4f6) center/cover no-repeat;
}
.aksrt-term-card__cover.is-empty {
background: linear-gradient(135deg, #dbeafe, #eff6ff);
}
.aksrt-term-card__body { padding: 10px 12px 12px; }
.aksrt-term-card__name { font-weight: 700; font-size: 14px; }
.aksrt-term-card__count { font-size: 12px; color: var(--muted-2-color, #888); margin-top: 2px; }
.aksrt-paid-posts { list-style: none; margin: 0; padding: 0; }
.aksrt-paid-posts__item {
display: flex;
gap: 10px;
padding: 8px 0;
border-bottom: 1px dashed var(--color-line, #e5e7eb);
}
.aksrt-paid-posts__item:last-child { border-bottom: 0; }
.aksrt-paid-posts__thumb {
width: 56px;
height: 56px;
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
background: var(--surface-2, #eee);
}
.aksrt-paid-posts__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-paid-posts__body { min-width: 0; flex: 1; }
.aksrt-paid-posts__title {
display: block;
color: inherit;
text-decoration: none;
font-size: 13px;
font-weight: 600;
line-height: 1.4;
}
.aksrt-paid-posts__price {
display: inline-block;
margin-top: 4px;
font-size: 12px;
color: #e11d48;
font-weight: 700;
}
+3
View File
@@ -3,3 +3,6 @@
@import url("widgets/part-3.css");
@import url("widgets/part-4.css");
@import url("widget-slider.css");
@import url("widget-batch3.css");
@import url("widget-batch3c.css");
@import url("widget-batch3d.css");
+29
View File
@@ -56,6 +56,23 @@ 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');
register_widget('\AKSRT\Widgets\WidgetPricingPlans');
register_widget('\AKSRT\Widgets\WidgetFaq');
register_widget('\AKSRT\Widgets\WidgetStats');
register_widget('\AKSRT\Widgets\WidgetReviews');
register_widget('\AKSRT\Widgets\WidgetHighlights');
register_widget('\AKSRT\Widgets\WidgetFeatureIntro');
register_widget('\AKSRT\Widgets\WidgetTermCards');
register_widget('\AKSRT\Widgets\WidgetPaidPosts');
// Forum widgets
if (class_exists('\AKSRT\Widgets\WidgetForumBoardList')) {
register_widget('\AKSRT\Widgets\WidgetForumBoardList');
@@ -66,6 +83,18 @@ 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');
}
if (class_exists('\AKSRT\Widgets\WidgetForumTopicLists')) {
register_widget('\AKSRT\Widgets\WidgetForumTopicLists');
}
// Shop product widgets (§6.9)
register_widget('\AKSRT\Widgets\WidgetProductList');
+23
View File
@@ -382,6 +382,29 @@ trait SetupPart1 {
'after_title' => $title_after,
]);
// ── Forum / BBS widget areas (zibll bbs_* parity lite) ──
$forum_widgets = [
'bbs_home_sidebar' => [__('论坛首页 · 侧栏', 'aksrt'), __('论坛首页侧栏区域。', 'aksrt')],
'bbs_home_top_fluid' => [__('论坛首页 · 顶部全宽', 'aksrt'), __('论坛首页顶部全宽。', 'aksrt')],
'bbs_plate_sidebar' => [__('论坛版块 · 侧栏', 'aksrt'), __('版块页侧栏。', 'aksrt')],
'bbs_plate_top_fluid' => [__('论坛版块 · 顶部全宽', 'aksrt'), __('版块页顶部全宽。', 'aksrt')],
'bbs_posts_sidebar' => [__('论坛帖子 · 侧栏', 'aksrt'), __('帖子页侧栏挂件区。', 'aksrt')],
'bbs_posts_top_fluid' => [__('论坛帖子 · 顶部全宽', 'aksrt'), __('帖子页顶部全宽。', 'aksrt')],
'bbs_new_posts_sidebar_top' => [__('论坛发帖 · 侧栏顶部', 'aksrt'), __('发帖页侧栏顶部。', 'aksrt')],
'bbs_new_posts_sidebar_bottom' => [__('论坛发帖 · 侧栏底部', 'aksrt'), __('发帖页侧栏底部。', 'aksrt')],
];
foreach ($forum_widgets as $sid => $meta) {
register_sidebar([
'id' => $sid,
'name' => $meta[0],
'description' => $meta[1],
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
}
// ── Shop widget areas (§6.9) ──
$shop_widgets = [
'shop_product_top_fluid' => ['商品详情页 · 顶部通栏', '商品详情页顶部全宽区域。'],
+238
View File
@@ -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,
];
}
}
+189
View File
@@ -0,0 +1,189 @@
<?php
/**
* FAQ accordion (zibll zib_widget_faq lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetFaq extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_faq',
esc_html__('AKSRT 常见问题', 'aksrt'),
[
'description' => esc_html__('可折叠 FAQ 列表,支持搜索框与侧栏文档卡。', 'aksrt'),
'classname' => 'widget_aksrt_faq',
]
);
}
public function widget($args, $instance): void {
$faqs = $this->normalize_faqs($instance['faqs'] ?? []);
if (!$faqs) {
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
}
$uid = 'aksrt-faq-' . esc_attr($this->id);
echo '<div class="aksrt-faq" id="' . $uid . '">';
if (!empty($instance['search_s'])) {
$ph = (string) ($instance['search_placeholder'] ?? __('搜索问题…', 'aksrt'));
echo '<form class="aksrt-faq__search" method="get" action="' . esc_url(home_url('/')) . '" role="search">';
echo '<input type="search" name="s" class="aksrt-faq__search-input" placeholder="' . esc_attr($ph) . '" />';
echo '<button type="submit" class="aksrt-faq__search-btn">' . esc_html((string) ($instance['search_btn_text'] ?? __('搜索', 'aksrt'))) . '</button>';
echo '</form>';
}
echo '<div class="aksrt-faq__list">';
$n = 0;
foreach ($faqs as $f) {
$n++;
$num = str_pad((string) $n, 2, '0', STR_PAD_LEFT);
echo '<details class="aksrt-faq__item">';
echo '<summary class="aksrt-faq__q"><span class="aksrt-faq__num">' . esc_html($num) . '</span><span class="aksrt-faq__qtext">' . esc_html($f['question']) . '</span></summary>';
if ($f['answer'] !== '') {
echo '<div class="aksrt-faq__a">' . wp_kses_post(wpautop($f['answer'])) . '</div>';
}
echo '</details>';
}
echo '</div>';
$doc_title = trim((string) ($instance['doc_title'] ?? ''));
$doc_desc = trim((string) ($instance['doc_desc'] ?? ''));
$help_title = trim((string) ($instance['help_title'] ?? ''));
$help_url = esc_url_raw(trim((string) ($instance['help_btn_url'] ?? '')));
$help_btn = trim((string) ($instance['help_btn_text'] ?? ''));
if ($doc_title !== '' || $help_title !== '') {
echo '<div class="aksrt-faq__aside">';
if ($doc_title !== '') {
echo '<div class="aksrt-faq__doc"><strong>' . esc_html($doc_title) . '</strong>';
if ($doc_desc !== '') {
echo '<p>' . esc_html($doc_desc) . '</p>';
}
echo '</div>';
}
if ($help_title !== '') {
echo '<div class="aksrt-faq__help"><strong>' . esc_html($help_title) . '</strong>';
$sub = trim((string) ($instance['help_subtitle'] ?? ''));
if ($sub !== '') {
echo '<p>' . esc_html($sub) . '</p>';
}
if ($help_btn !== '' && $help_url !== '') {
echo '<a class="aksrt-faq__help-btn" href="' . esc_url($help_url) . '">' . esc_html($help_btn) . '</a>';
}
echo '</div>';
}
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array{question:string,answer:string}>
*/
private function normalize_faqs($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;
}
$q = sanitize_text_field((string) ($row['question'] ?? $row['q'] ?? ''));
if ($q === '') {
continue;
}
$a = (string) ($row['answer'] ?? $row['a'] ?? '');
// Allow basic HTML in answers after kses on output.
$out[] = [
'question' => $q,
'answer' => wp_kses_post($a),
];
if (count($out) >= 30) {
break;
}
}
return $out;
}
public function form($instance): void {
$faqs = is_string($instance['faqs'] ?? null)
? (string) $instance['faqs']
: wp_json_encode($this->normalize_faqs($instance['faqs'] ?? []), 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((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('FAQ JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('faqs')); ?>" placeholder='[{"question":"如何付费?","answer":"进入用户中心…"}]'><?php echo esc_textarea((string) $faqs); ?></textarea>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('search_s')); ?>" value="1" <?php checked(!empty($instance['search_s'])); ?>> <?php esc_html_e('显示搜索框', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('文档卡标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_title')); ?>" value="<?php echo esc_attr((string) ($instance['doc_title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('文档卡描述', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_desc')); ?>" value="<?php echo esc_attr((string) ($instance['doc_desc'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('帮助卡标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_title')); ?>" value="<?php echo esc_attr((string) ($instance['help_title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('帮助按钮文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_text')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_text'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('帮助按钮链接', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_url')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_url'] ?? '')); ?>">
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'faqs' => $this->normalize_faqs($new_instance['faqs'] ?? []),
'search_s' => !empty($new_instance['search_s']) ? 1 : 0,
'search_placeholder' => sanitize_text_field((string) ($new_instance['search_placeholder'] ?? '')),
'search_btn_text' => sanitize_text_field((string) ($new_instance['search_btn_text'] ?? '')),
'doc_title' => sanitize_text_field((string) ($new_instance['doc_title'] ?? '')),
'doc_desc' => sanitize_text_field((string) ($new_instance['doc_desc'] ?? '')),
'help_title' => sanitize_text_field((string) ($new_instance['help_title'] ?? '')),
'help_subtitle' => sanitize_text_field((string) ($new_instance['help_subtitle'] ?? '')),
'help_btn_text' => sanitize_text_field((string) ($new_instance['help_btn_text'] ?? '')),
'help_btn_url' => esc_url_raw(trim((string) ($new_instance['help_btn_url'] ?? ''))),
];
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
/**
* Feature intro with media tabs (zibll zib_widget_feature_intro lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetFeatureIntro extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_feature_intro',
esc_html__('AKSRT 功能介绍', 'aksrt'),
[
'description' => esc_html__('左文右图/视频的 TAB 功能介绍模块。', 'aksrt'),
'classname' => 'widget_aksrt_feature_intro',
]
);
}
public function widget($args, $instance): void {
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
return;
}
$heading = trim((string) ($instance['heading'] ?? ''));
$desc = trim((string) ($instance['description'] ?? ''));
$media_pos = (($instance['media_position'] ?? 'right') === 'left') ? 'left' : 'right';
$chrome = !empty($instance['browser_chrome']) || !isset($instance['browser_chrome']);
$ratio = max(30, min(100, (int) ($instance['media_ratio'] ?? 66)));
$show_stat = !empty($instance['show_stat']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$uid = 'aksrt-fi-' . esc_attr($this->id);
echo '<div class="aksrt-fi media-' . esc_attr($media_pos) . '" id="' . $uid . '">';
echo '<div class="aksrt-fi__text">';
if ($heading !== '') {
echo '<h2 class="aksrt-fi__title">' . esc_html($heading) . '</h2>';
}
if ($desc !== '') {
echo '<div class="aksrt-fi__desc">' . wp_kses_post(wpautop($desc)) . '</div>';
}
if ($show_stat) {
$sn = sanitize_text_field((string) ($instance['stat_number'] ?? ''));
$ss = sanitize_text_field((string) ($instance['stat_suffix'] ?? '+'));
$sl = sanitize_text_field((string) ($instance['stat_label'] ?? ''));
if ($sn !== '') {
echo '<div class="aksrt-fi__stat"><span class="aksrt-fi__stat-num">' . esc_html($sn) . '</span>';
if ($ss !== '') {
echo '<span class="aksrt-fi__stat-plus">' . esc_html($ss) . '</span>';
}
if ($sl !== '') {
echo '<span class="aksrt-fi__stat-label">' . esc_html($sl) . '</span>';
}
echo '</div>';
}
}
if (count($slides) > 1) {
echo '<div class="aksrt-fi__tabs" role="tablist">';
foreach ($slides as $i => $s) {
$cls = $i === 0 ? ' is-active' : '';
$label = $s['tab_label'] !== '' ? $s['tab_label'] : sprintf(__('面板 %d', 'aksrt'), $i + 1);
echo '<button type="button" class="aksrt-fi__tab' . esc_attr($cls) . '" data-index="' . (int) $i . '" role="tab">' . esc_html($label) . '</button>';
}
echo '</div>';
}
echo '</div>';
echo '<div class="aksrt-fi__media">';
foreach ($slides as $i => $s) {
$cls = $i === 0 ? ' is-active' : '';
echo '<div class="aksrt-fi__panel' . esc_attr($cls) . '" data-index="' . (int) $i . '">';
$box_cls = $chrome ? 'aksrt-fi__browser' : 'aksrt-fi__browser is-plain';
echo '<div class="' . esc_attr($box_cls) . '">';
if ($chrome) {
echo '<div class="aksrt-fi__browser-bar" aria-hidden="true"><span></span><span></span><span></span></div>';
}
echo '<div class="aksrt-fi__browser-body" style="padding-bottom:' . esc_attr((string) $ratio) . '%">';
if ($s['media_type'] === 'video' && $s['video_url'] !== '') {
$poster = $s['video_poster'] !== '' ? ' poster="' . esc_url($s['video_poster']) . '"' : '';
echo '<video class="aksrt-fi__video" src="' . esc_url($s['video_url']) . '"' . $poster . ' autoplay muted loop playsinline></video>';
} elseif ($s['image'] !== '') {
echo '<img class="aksrt-fi__img" src="' . esc_url($s['image']) . '" alt="' . esc_attr($s['tab_label']) . '" loading="lazy" />';
}
echo '</div></div></div>';
}
echo '</div></div>';
if (count($slides) > 1) {
?>
<script>
(function(){
var root=document.getElementById(<?php echo wp_json_encode($uid); ?>);
if(!root||root._aksrtFi)return; root._aksrtFi=1;
var tabs=[].slice.call(root.querySelectorAll('.aksrt-fi__tab'));
var panels=[].slice.call(root.querySelectorAll('.aksrt-fi__panel'));
function go(i){
tabs.forEach(function(t,idx){t.classList.toggle('is-active',idx===i);});
panels.forEach(function(p,idx){p.classList.toggle('is-active',idx===i);});
}
tabs.forEach(function(t){ t.addEventListener('click', function(){ go(parseInt(t.getAttribute('data-index')||'0',10)); }); });
})();
</script>
<?php
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,string>>
*/
private function normalize_slides($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;
}
$type = sanitize_key((string) ($row['media_type'] ?? 'image'));
if ($type !== 'video') {
$type = 'image';
}
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
$video = esc_url_raw(trim((string) ($row['video_url'] ?? '')));
if ($type === 'video' && $video === '') {
continue;
}
if ($type === 'image' && $image === '') {
continue;
}
$out[] = [
'tab_label' => sanitize_text_field((string) ($row['tab_label'] ?? '')),
'media_type' => $type,
'image' => $image,
'video_url' => $video,
'video_poster' => esc_url_raw(trim((string) ($row['video_poster'] ?? ''))),
];
if (count($out) >= 8) {
break;
}
}
return $out;
}
public function form($instance): void {
$json = is_string($instance['slides'] ?? null)
? (string) $instance['slides']
: wp_json_encode($this->normalize_slides($instance['slides'] ?? []), 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('heading')); ?>" value="<?php echo esc_attr((string) ($instance['heading'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('描述', 'aksrt'); ?>
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('description')); ?>"><?php echo esc_textarea((string) ($instance['description'] ?? '')); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('媒体 TAB JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('slides')); ?>" placeholder='[{"tab_label":"面板1","media_type":"image","image":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('媒体位置', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('media_position')); ?>">
<option value="right" <?php selected(($instance['media_position'] ?? 'right'), 'right'); ?>><?php esc_html_e('右侧', 'aksrt'); ?></option>
<option value="left" <?php selected(($instance['media_position'] ?? ''), 'left'); ?>><?php esc_html_e('左侧', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('媒体高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="100" name="<?php echo esc_attr($this->get_field_name('media_ratio')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['media_ratio'] ?? 66))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('browser_chrome')); ?>" value="1" <?php checked(!empty($instance['browser_chrome']) || !isset($instance['browser_chrome'])); ?>> <?php esc_html_e('浏览器外框', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stat')); ?>" value="1" <?php checked(!empty($instance['show_stat'])); ?>> <?php esc_html_e('显示统计数字', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('统计数字', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_number')); ?>" value="<?php echo esc_attr((string) ($instance['stat_number'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('统计后缀', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_suffix')); ?>" value="<?php echo esc_attr((string) ($instance['stat_suffix'] ?? '+')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('统计标签', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_label')); ?>" value="<?php echo esc_attr((string) ($instance['stat_label'] ?? '')); ?>">
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'heading' => sanitize_text_field((string) ($new_instance['heading'] ?? '')),
'description' => wp_kses_post((string) ($new_instance['description'] ?? '')),
'slides' => $this->normalize_slides($new_instance['slides'] ?? []),
'media_position' => ($new_instance['media_position'] ?? '') === 'left' ? 'left' : 'right',
'media_ratio' => max(30, min(100, (int) ($new_instance['media_ratio'] ?? 66))),
'browser_chrome' => !empty($new_instance['browser_chrome']) ? 1 : 0,
'show_stat' => !empty($new_instance['show_stat']) ? 1 : 0,
'stat_number' => sanitize_text_field((string) ($new_instance['stat_number'] ?? '')),
'stat_suffix' => sanitize_text_field((string) ($new_instance['stat_suffix'] ?? '')),
'stat_label' => sanitize_text_field((string) ($new_instance['stat_label'] ?? '')),
];
}
}
+52 -23
View File
@@ -26,31 +26,60 @@ class WidgetForumBoardList extends \WP_Widget {
echo $args['before_widget'];
echo $args['before_title'] . esc_html($title) . $args['after_title'];
$boards = get_terms([
'taxonomy' => 'aksrt_forum_board',
'hide_empty' => true,
'orderby' => 'meta_value_num',
'meta_key' => '_aksrt_sort_order',
'order' => 'ASC',
]);
if (!is_wp_error($boards) && !empty($boards)) {
echo '<ul class="aksrt-forum-widget__boards">';
foreach ($boards as $board) {
$icon = get_term_meta($board->term_id, '_aksrt_icon', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_term_link($board)) . '">';
if ($icon) {
echo '<i class="' . esc_attr($icon) . '"></i> ';
$rendered = false;
if (post_type_exists('aksrt_forum_plate')) {
$plates = get_posts([
'post_type' => 'aksrt_forum_plate',
'post_status' => 'publish',
'posts_per_page' => 30,
'orderby' => 'menu_order title',
'order' => 'ASC',
]);
if ($plates) {
$rendered = true;
echo '<ul class="aksrt-forum-widget__boards">';
foreach ($plates as $plate) {
$icon = (string) get_post_meta($plate->ID, '_aksrt_icon', true);
$count = (int) get_post_meta($plate->ID, '_aksrt_posts_count', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_permalink($plate)) . '">';
if ($icon !== '') {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo esc_html(get_the_title($plate));
echo '<span class="aksrt-forum-widget__count">' . $count . '</span>';
echo '</a>';
echo '</li>';
}
echo esc_html($board->name);
echo '<span class="aksrt-forum-widget__count">' . (int) $board->count . '</span>';
echo '</a>';
echo '</li>';
echo '</ul>';
}
}
if (!$rendered) {
$boards = get_terms([
'taxonomy' => 'aksrt_forum_board',
'hide_empty' => true,
'orderby' => 'meta_value_num',
'meta_key' => '_aksrt_sort_order',
'order' => 'ASC',
]);
if (!is_wp_error($boards) && !empty($boards)) {
echo '<ul class="aksrt-forum-widget__boards">';
foreach ($boards as $board) {
$icon = get_term_meta($board->term_id, '_aksrt_icon', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_term_link($board)) . '">';
if ($icon) {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo esc_html($board->name);
echo '<span class="aksrt-forum-widget__count">' . (int) $board->count . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
} else {
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无版块', 'aksrt') . '</p>';
}
echo '</ul>';
} else {
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无版块', 'aksrt') . '</p>';
}
echo $args['after_widget'];
@@ -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 [];
}
}
+216
View File
@@ -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'] ?? '')),
];
}
}
+176
View File
@@ -0,0 +1,176 @@
<?php
/**
* Forum topic list widget (zibll zib_bbs_widget_ui_posts_lists lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetForumTopicLists extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_forum_topic_lists',
esc_html__('论坛 - 帖子列表', 'aksrt'),
[
'description' => esc_html__('按排序/版块筛选展示论坛帖子。', 'aksrt'),
'classname' => 'widget_aksrt_forum_topic_lists',
]
);
}
public function widget($args, $instance): void {
if (!post_type_exists('aksrt_forum_topic')) {
return;
}
$count = max(1, min(30, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
$plate_id = (int) ($instance['plate_id'] ?? 0);
$current_plate = !empty($instance['current_plate']);
if ($current_plate) {
$resolved = $this->resolve_current_plate_id();
if ($resolved <= 0) {
return;
}
$plate_id = $resolved;
}
$query = [
'post_type' => 'aksrt_forum_topic',
'post_status' => 'publish',
'posts_per_page' => $count,
'order' => 'DESC',
];
switch ($orderby) {
case 'views':
$query['meta_key'] = '_aksrt_views';
$query['orderby'] = 'meta_value_num';
break;
case 'comments':
$query['orderby'] = 'comment_count';
break;
case 'modified':
$query['orderby'] = 'modified';
break;
default:
$query['orderby'] = 'date';
}
if ($plate_id > 0) {
$query['meta_query'] = [[
'key' => '_aksrt_plate_id',
'value' => $plate_id,
]];
}
$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
}
$list_class = $style === 'detail' ? 'aksrt-ftopic-list is-detail' : 'aksrt-ftopic-list is-mini';
echo '<ul class="' . esc_attr($list_class) . '">';
while ($q->have_posts()) {
$q->the_post();
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
echo '<li class="aksrt-ftopic-list__item">';
echo '<a class="aksrt-ftopic-list__link" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
if ($style === 'detail') {
echo '<div class="aksrt-ftopic-list__meta">';
echo '<span>' . esc_html(get_the_author()) . '</span>';
echo '<span>' . esc_html(get_the_date()) . '</span>';
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
echo '</div>';
} else {
echo '<span class="aksrt-ftopic-list__views">' . esc_html((string) $views) . '</span>';
}
echo '</li>';
}
echo '</ul>';
wp_reset_postdata();
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function resolve_current_plate_id(): int {
if (is_singular('aksrt_forum_plate')) {
return (int) get_queried_object_id();
}
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);
return max(0, $plate_id);
}
return 0;
}
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('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('发布时间', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?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="mini" <?php selected(($instance['style'] ?? 'mini'), 'mini'); ?>><?php esc_html_e('紧凑', 'aksrt'); ?></option>
<option value="detail" <?php selected(($instance['style'] ?? ''), 'detail'); ?>><?php esc_html_e('详情', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('限定版块 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('plate_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['plate_id'] ?? 0))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('current_plate')); ?>" value="1" <?php checked(!empty($instance['current_plate'])); ?>> <?php esc_html_e('仅当前版块(版块/帖子页)', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'views', 'comments', 'modified'], true)) {
$orderby = 'date';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 8))),
'orderby' => $orderby,
'style' => ($new_instance['style'] ?? '') === 'detail' ? 'detail' : 'mini',
'plate_id' => max(0, (int) ($new_instance['plate_id'] ?? 0)),
'current_plate' => !empty($new_instance['current_plate']) ? 1 : 0,
];
}
}
+177
View File
@@ -0,0 +1,177 @@
<?php
/**
* Highlights cards (zibll zib_widget_highlights lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetHighlights extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_highlights',
esc_html__('AKSRT 亮点卡片', 'aksrt'),
[
'description' => esc_html__('大中小尺寸亮点/卖点卡片栅格。', 'aksrt'),
'classname' => 'widget_aksrt_highlights',
]
);
}
public function widget($args, $instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
if (!$cards) {
return;
}
$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
}
$large = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'large'));
$medium = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'medium'));
$small = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'small'));
// Fallback: if no size tags, treat all as medium.
if (!$large && !$medium && !$small) {
$medium = $cards;
}
echo '<div class="aksrt-highlights">';
if ($large) {
echo '<div class="aksrt-highlights__row is-large">';
foreach ($large as $c) {
echo $this->card($c, 'large', $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
if ($medium) {
echo '<div class="aksrt-highlights__row is-medium">';
foreach ($medium as $c) {
echo $this->card($c, 'medium', $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
if ($small) {
echo '<div class="aksrt-highlights__row is-small">';
foreach ($small as $c) {
echo $this->card($c, 'small', $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $c
*/
private function card(array $c, string $size, bool $go_link): string {
$color = sanitize_key((string) ($c['color'] ?? 'blue'));
if (!in_array($color, ['blue', 'pink', 'purple', 'green', 'orange', 'cyan'], true)) {
$color = 'blue';
}
$url = (string) ($c['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) . '"' : '';
$img = (string) ($c['image'] ?? '');
$html = '<' . $tag . ' class="aksrt-hl-card size-' . esc_attr($size) . ' color-' . esc_attr($color) . '"' . $href . '>';
if ($img !== '') {
$html .= '<div class="aksrt-hl-card__media"><img src="' . esc_url($img) . '" alt="" loading="lazy" /></div>';
}
$html .= '<div class="aksrt-hl-card__body">';
if (!empty($c['icon'])) {
$html .= '<div class="aksrt-hl-card__icon"><i class="' . esc_attr((string) $c['icon']) . '" aria-hidden="true"></i></div>';
}
if ($c['title'] !== '') {
$html .= '<div class="aksrt-hl-card__title">' . esc_html($c['title']) . '</div>';
}
if ($c['desc'] !== '') {
$html .= '<div class="aksrt-hl-card__desc">' . esc_html($c['desc']) . '</div>';
}
$html .= '</div></' . $tag . '>';
return $html;
}
/**
* @param mixed $raw
* @return array<int, array<string,mixed>>
*/
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'] ?? ''));
$desc = sanitize_text_field((string) ($row['desc'] ?? ''));
if ($title === '' && $desc === '') {
continue;
}
$size = sanitize_key((string) ($row['size'] ?? 'medium'));
if (!in_array($size, ['large', 'medium', 'small'], true)) {
$size = 'medium';
}
$out[] = [
'title' => $title,
'desc' => $desc,
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'image' => esc_url_raw(trim((string) ($row['image'] ?? ''))),
'url' => esc_url_raw(trim((string) ($row['url'] ?? ''))),
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
'size' => $size,
];
if (count($out) >= 12) {
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);
?>
<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('卡片 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"高速","desc":"…","size":"large","color":"blue","icon":"fas fa-bolt"}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
<small><?php esc_html_e('size: large|medium|small;color: blue/pink/…', 'aksrt'); ?></small>
</p>
<p>
<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($new_instance['cards'] ?? []),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
];
}
}
+113
View File
@@ -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,
];
}
}
+140
View File
@@ -0,0 +1,140 @@
<?php
/**
* Paid posts list (zibll zib_widget_ui_posts_pay lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetPaidPosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_paid_posts',
esc_html__('AKSRT 付费内容列表', 'aksrt'),
[
'description' => esc_html__('展示开启付费/积分可见的文章。', 'aksrt'),
'classname' => 'widget_aksrt_paid_posts',
]
);
}
public function widget($args, $instance): void {
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'order' => 'DESC',
'meta_query' => [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_credits_cost',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
],
];
if ($orderby === 'views') {
$query['meta_key'] = '_aksrt_views';
$query['orderby'] = 'meta_value_num';
} elseif ($orderby === 'price') {
$query['meta_key'] = '_aksrt_pay_price';
$query['orderby'] = 'meta_value_num';
} else {
$query['orderby'] = 'date';
}
$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 '<ul class="aksrt-paid-posts">';
while ($q->have_posts()) {
$q->the_post();
$price = (float) get_post_meta(get_the_ID(), '_aksrt_pay_price', true);
$credits = (int) get_post_meta(get_the_ID(), '_aksrt_credits_cost', true);
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
echo '<li class="aksrt-paid-posts__item">';
if ($thumb) {
echo '<a class="aksrt-paid-posts__thumb" href="' . esc_url(get_permalink()) . '"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></a>';
}
echo '<div class="aksrt-paid-posts__body">';
echo '<a class="aksrt-paid-posts__title" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
echo '<div class="aksrt-paid-posts__meta">';
if ($price > 0) {
echo '<span class="aksrt-paid-posts__price">¥' . esc_html(number_format($price, 2, '.', '')) . '</span>';
} elseif ($credits > 0) {
echo '<span class="aksrt-paid-posts__price">' . esc_html(sprintf(__('%d 积分', 'aksrt'), $credits)) . '</span>';
} else {
echo '<span class="aksrt-paid-posts__price">' . esc_html__('付费', 'aksrt') . '</span>';
}
echo '</div></div></li>';
}
echo '</ul>';
wp_reset_postdata();
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'] ?? __('付费精选', 'aksrt'))); ?>">
</label>
</p>
<p>
<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('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="price" <?php selected(($instance['orderby'] ?? ''), 'price'); ?>><?php esc_html_e('价格', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'views', 'price'], true)) {
$orderby = 'date';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))),
'orderby' => $orderby,
];
}
}
+348
View File
@@ -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,
];
}
}
+236
View File
@@ -0,0 +1,236 @@
<?php
/**
* Pricing plan cards (zibll zib_widget_pricing_plans lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetPricingPlans extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_pricing_plans',
esc_html__('AKSRT 价格方案', 'aksrt'),
[
'description' => esc_html__('多列定价卡片 + 信任条(落地页/VIP 介绍)。', 'aksrt'),
'classname' => 'widget_aksrt_pricing_plans',
]
);
}
public function widget($args, $instance): void {
$plans = $this->normalize_plans($instance['lists'] ?? $instance['plans'] ?? []);
if (!$plans) {
return;
}
$trust = $this->normalize_trust($instance['trust_items'] ?? []);
$currency = sanitize_text_field((string) ($instance['currency'] ?? '¥'));
if ($currency === '') {
$currency = '¥';
}
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 '<div class="aksrt-pricing">';
echo '<div class="aksrt-pricing__cards">';
foreach ($plans as $plan) {
$accent = sanitize_key((string) ($plan['color'] ?? 'blue'));
if (!in_array($accent, ['pink', 'purple', 'blue', 'green', 'yellow', 'cyan'], true)) {
$accent = 'blue';
}
$featured = !empty($plan['is_featured']);
$class = 'aksrt-pricing__card accent-' . $accent . ($featured ? ' is-featured' : '');
echo '<div class="' . esc_attr($class) . '">';
if ($featured || !empty($plan['tag'])) {
$badge = (string) ($plan['tag'] !== '' ? $plan['tag'] : __('推荐', 'aksrt'));
echo '<span class="aksrt-pricing__badge">' . esc_html($badge) . '</span>';
}
echo '<h3 class="aksrt-pricing__name">' . esc_html($plan['name']) . '</h3>';
if ($plan['desc'] !== '') {
echo '<p class="aksrt-pricing__desc">' . esc_html($plan['desc']) . '</p>';
}
$price = (float) $plan['price'];
$show = (float) $plan['show_price'];
echo '<div class="aksrt-pricing__price">';
echo '<span class="aksrt-pricing__main"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($price)) . '</span>';
if ($show > $price && $show > 0) {
echo '<span class="aksrt-pricing__original"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($show)) . '</span>';
}
echo '</div>';
if ($plan['features']) {
echo '<ul class="aksrt-pricing__features">';
foreach ($plan['features'] as $ft) {
echo '<li><i class="fa fa-check" aria-hidden="true"></i><span>' . esc_html($ft) . '</span></li>';
}
echo '</ul>';
}
if ($plan['btn_text'] !== '' && $plan['btn_url'] !== '') {
$url = $plan['btn_url'];
if (!empty($instance['go_link']) && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
echo '<div class="aksrt-pricing__action"><a class="aksrt-pricing__btn" href="' . esc_url($url) . '">' . esc_html($plan['btn_text']) . '</a></div>';
}
echo '</div>';
}
echo '</div>';
if ($trust) {
echo '<div class="aksrt-pricing__trust">';
foreach ($trust as $ti) {
echo '<div class="aksrt-pricing__trust-item">';
if ($ti['icon'] !== '') {
echo '<i class="' . esc_attr($ti['icon']) . '" aria-hidden="true"></i>';
}
echo '<div><strong>' . esc_html($ti['title']) . '</strong>';
if ($ti['desc'] !== '') {
echo '<span>' . esc_html($ti['desc']) . '</span>';
}
echo '</div></div>';
}
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function fmt(float $n): string {
if (abs($n - round($n)) < 0.001) {
return (string) (int) round($n);
}
return number_format($n, 2, '.', '');
}
/**
* @param mixed $raw
* @return array<int, array<string,mixed>>
*/
private function normalize_plans($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;
}
$name = sanitize_text_field((string) ($row['name'] ?? $row['title'] ?? ''));
if ($name === '') {
continue;
}
$intro = (string) ($row['intro'] ?? $row['features'] ?? '');
if (is_array($row['features'] ?? null)) {
$features = array_values(array_filter(array_map('sanitize_text_field', $row['features'])));
} else {
$features = array_values(array_filter(array_map('sanitize_text_field', preg_split('/\r\n|\r|\n/', $intro) ?: [])));
}
$btn = is_array($row['btn'] ?? null) ? $row['btn'] : [];
$out[] = [
'name' => $name,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'price' => (float) ($row['price'] ?? 0),
'show_price' => (float) ($row['show_price'] ?? 0),
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
'is_featured' => !empty($row['is_featured']) ? 1 : 0,
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
'features' => $features,
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? $btn['text'] ?? '')),
'btn_url' => esc_url_raw(trim((string) ($row['btn_url'] ?? $btn['url'] ?? ''))),
];
if (count($out) >= 6) {
break;
}
}
return $out;
}
/**
* @param mixed $raw
* @return array<int, array{title:string,desc:string,icon:string}>
*/
private function normalize_trust($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'] ?? ''));
if ($title === '') {
continue;
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'icon' => sanitize_text_field((string) ($row['icon'] ?? 'fa fa-check-circle')),
];
if (count($out) >= 6) {
break;
}
}
return $out;
}
public function form($instance): void {
$plans = is_string($instance['lists'] ?? null)
? (string) $instance['lists']
: wp_json_encode($this->normalize_plans($instance['lists'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$trust = is_string($instance['trust_items'] ?? null)
? (string) $instance['trust_items']
: wp_json_encode($this->normalize_trust($instance['trust_items'] ?? []), 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((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('货币符号', 'aksrt'); ?>
<input class="tiny-text" type="text" name="<?php echo esc_attr($this->get_field_name('currency')); ?>" value="<?php echo esc_attr((string) ($instance['currency'] ?? '¥')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('方案 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('lists')); ?>" placeholder='[{"name":"Pro","price":99,"show_price":199,"desc":"适合创作者","features":["无限下载","优先支持"],"btn_text":"立即开通","btn_url":"#","is_featured":1,"color":"pink"}]'><?php echo esc_textarea((string) $plans); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('信任条 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('trust_items')); ?>"><?php echo esc_textarea((string) $trust); ?></textarea>
</label>
</p>
<p>
<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'] ?? '')),
'currency' => sanitize_text_field((string) ($new_instance['currency'] ?? '¥')),
'lists' => $this->normalize_plans($new_instance['lists'] ?? []),
'trust_items' => $this->normalize_trust($new_instance['trust_items'] ?? []),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
];
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
/**
* Testimonials / virtual reviews carousel (zibll zib_widget_reviews lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetReviews extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_reviews',
esc_html__('AKSRT 用户评价', 'aksrt'),
[
'description' => esc_html__('虚拟用户评价卡片横向滚动。', 'aksrt'),
'classname' => 'widget_aksrt_reviews',
]
);
}
public function widget($args, $instance): void {
$reviews = $this->normalize($instance['reviews'] ?? []);
if (!$reviews) {
return;
}
$pc = max(1, min(6, (float) ($instance['desktop_per_view'] ?? 3)));
$m = max(1, min(3, (float) ($instance['mobile_per_view'] ?? 1.2)));
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-rev-pc:%s;--aksrt-rev-m:%s;', rtrim(rtrim(number_format($pc, 2, '.', ''), '0'), '.'), rtrim(rtrim(number_format($m, 2, '.', ''), '0'), '.'));
echo '<div class="aksrt-reviews" style="' . esc_attr($style) . '">';
echo '<div class="aksrt-reviews__track">';
foreach ($reviews as $r) {
echo '<div class="aksrt-reviews__slide"><div class="aksrt-reviews__card">';
echo '<div class="aksrt-reviews__head">';
if ($r['avatar'] !== '') {
echo '<img class="aksrt-reviews__avatar" src="' . esc_url($r['avatar']) . '" alt="" loading="lazy" width="44" height="44" />';
} else {
echo '<span class="aksrt-reviews__avatar is-placeholder"><i class="fa fa-user" aria-hidden="true"></i></span>';
}
echo '<div class="aksrt-reviews__meta">';
if ($r['name'] !== '') {
$name_html = esc_html($r['name']);
if ($r['url'] !== '') {
$name_html = '<a href="' . esc_url($r['url']) . '" target="_blank" rel="noopener noreferrer">' . $name_html . '</a>';
}
echo '<div class="aksrt-reviews__name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($r['role'] !== '') {
echo '<div class="aksrt-reviews__role">' . esc_html($r['role']) . '</div>';
}
echo '</div></div>';
echo '<div class="aksrt-reviews__stars" aria-label="' . esc_attr(sprintf(__('%d 星', 'aksrt'), $r['rating'])) . '">';
for ($i = 1; $i <= 5; $i++) {
echo '<i class="fa fa-star' . ($i <= $r['rating'] ? ' is-on' : ' is-off') . '" aria-hidden="true"></i>';
}
echo '</div>';
if ($r['content'] !== '') {
echo '<p class="aksrt-reviews__content">' . nl2br(esc_html($r['content'])) . '</p>';
}
echo '<i class="fa fa-quote-right aksrt-reviews__quote" aria-hidden="true"></i>';
echo '</div></div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array{name:string,role:string,avatar:string,content:string,rating:int,url:string}>
*/
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;
}
$name = sanitize_text_field((string) ($row['name'] ?? ''));
$content = sanitize_textarea_field((string) ($row['content'] ?? ''));
if ($name === '' && $content === '') {
continue;
}
$out[] = [
'name' => $name,
'role' => sanitize_text_field((string) ($row['role'] ?? '')),
'avatar' => esc_url_raw(trim((string) ($row['avatar'] ?? ''))),
'content' => $content,
'rating' => max(0, min(5, (int) ($row['rating'] ?? 5))),
'url' => esc_url_raw(trim((string) ($row['url'] ?? ''))),
];
if (count($out) >= 20) {
break;
}
}
return $out;
}
public function form($instance): void {
$json = is_string($instance['reviews'] ?? null)
? (string) $instance['reviews']
: wp_json_encode($this->normalize($instance['reviews'] ?? []), 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((string) ($instance['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('reviews')); ?>" placeholder='[{"name":"张三","role":"站长","rating":5,"content":"很好用","avatar":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('PC 每屏', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" step="0.1" name="<?php echo esc_attr($this->get_field_name('desktop_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['desktop_per_view'] ?? '3')); ?>">
</label>
<label><?php esc_html_e('移动每屏', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" step="0.1" name="<?php echo esc_attr($this->get_field_name('mobile_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['mobile_per_view'] ?? '1.2')); ?>">
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'reviews' => $this->normalize($new_instance['reviews'] ?? []),
'desktop_per_view' => max(1, min(6, (float) ($new_instance['desktop_per_view'] ?? 3))),
'mobile_per_view' => max(1, min(3, (float) ($new_instance['mobile_per_view'] ?? 1.2))),
];
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
/**
* Stats number cards (zibll zib_widget_stats lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetStats extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_stats',
esc_html__('AKSRT 数据统计', 'aksrt'),
[
'description' => esc_html__('数字统计卡片网格(站点数据展示)。', 'aksrt'),
'classname' => 'widget_aksrt_stats',
]
);
}
public function widget($args, $instance): void {
$stats = $this->normalize_stats($instance['stats'] ?? []);
if (!$stats) {
return;
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 4)));
$m_cols = max(1, min(3, (int) ($instance['mobile_columns'] ?? 2)));
$intro_title = trim((string) ($instance['intro_title'] ?? ''));
$intro_sub = trim((string) ($instance['intro_subtitle'] ?? ''));
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-stats-pc:%d;--aksrt-stats-m:%d;', $cols, $m_cols);
echo '<div class="aksrt-stats" style="' . esc_attr($style) . '">';
if ($intro_title !== '' || $intro_sub !== '') {
echo '<div class="aksrt-stats__intro">';
if ($intro_title !== '') {
echo '<h2 class="aksrt-stats__intro-title">' . esc_html($intro_title) . '</h2>';
}
if ($intro_sub !== '') {
echo '<p class="aksrt-stats__intro-sub">' . esc_html($intro_sub) . '</p>';
}
echo '</div>';
}
echo '<div class="aksrt-stats__grid">';
$palette = ['pink', 'purple', 'orange', 'blue', 'green'];
$i = 0;
foreach ($stats as $s) {
$color = sanitize_key((string) ($s['color'] ?? 'auto'));
if ($color === '' || $color === 'auto') {
$color = $palette[$i % count($palette)];
}
$i++;
echo '<div class="aksrt-stats__item color-' . esc_attr($color) . '">';
if ($s['icon'] !== '') {
echo '<div class="aksrt-stats__icon"><i class="' . esc_attr($s['icon']) . '" aria-hidden="true"></i></div>';
}
echo '<div class="aksrt-stats__value">';
if ($s['prefix'] !== '') {
echo '<span class="aksrt-stats__prefix">' . esc_html($s['prefix']) . '</span>';
}
echo '<span class="aksrt-stats__number" data-target="' . esc_attr($s['number']) . '">' . esc_html($s['number']) . '</span>';
if ($s['unit'] !== '') {
echo '<span class="aksrt-stats__unit">' . esc_html($s['unit']) . '</span>';
}
echo '</div>';
if ($s['label'] !== '') {
echo '<div class="aksrt-stats__label">' . esc_html($s['label']) . '</div>';
}
echo '</div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array{number:string,label:string,unit:string,prefix:string,icon:string,color:string}>
*/
private function normalize_stats($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;
}
$number = sanitize_text_field((string) ($row['number'] ?? ''));
if ($number === '') {
continue;
}
$out[] = [
'number' => $number,
'label' => sanitize_text_field((string) ($row['label'] ?? '')),
'unit' => sanitize_text_field((string) ($row['unit'] ?? '')),
'prefix' => sanitize_text_field((string) ($row['prefix'] ?? '')),
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'color' => sanitize_key((string) ($row['color'] ?? 'auto')),
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
public function form($instance): void {
$stats = is_string($instance['stats'] ?? null)
? (string) $instance['stats']
: wp_json_encode($this->normalize_stats($instance['stats'] ?? []), 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((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数据 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('stats')); ?>" placeholder='[{"number":"1280","label":"用户","unit":"+","icon":"fas fa-users","color":"blue"}]'><?php echo esc_textarea((string) $stats); ?></textarea>
</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('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 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('mobile_columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mobile_columns'] ?? 2))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('介绍标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_title')); ?>" value="<?php echo esc_attr((string) ($instance['intro_title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('介绍副标题', 'aksrt'); ?>
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['intro_subtitle'] ?? '')); ?></textarea>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'stats' => $this->normalize_stats($new_instance['stats'] ?? []),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 4))),
'mobile_columns' => max(1, min(3, (int) ($new_instance['mobile_columns'] ?? 2))),
'intro_title' => sanitize_text_field((string) ($new_instance['intro_title'] ?? '')),
'intro_subtitle' => sanitize_text_field((string) ($new_instance['intro_subtitle'] ?? '')),
];
}
}
+166
View File
@@ -0,0 +1,166 @@
<?php
/**
* Taxonomy term cards (zibll zib_widget_ui_term_card lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetTermCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_term_cards',
esc_html__('AKSRT 分类/标签卡片', 'aksrt'),
[
'description' => esc_html__('以卡片展示分类或标签。', 'aksrt'),
'classname' => 'widget_aksrt_term_cards',
]
);
}
public function widget($args, $instance): void {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$term_args = [
'taxonomy' => $taxonomy,
'number' => $number,
'hide_empty' => $hide_empty,
];
if ($include) {
$term_args['include'] = $include;
$term_args['orderby'] = 'include';
} else {
$term_args['orderby'] = in_array($orderby, ['name', 'count', 'id'], true) ? $orderby : 'count';
$term_args['order'] = $orderby === 'name' ? 'ASC' : 'DESC';
}
$terms = get_terms($term_args);
if (is_wp_error($terms) || !$terms) {
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
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 2)));
echo '<div class="aksrt-term-cards" style="--aksrt-tc-cols:' . esc_attr((string) $cols) . '">';
foreach ($terms as $term) {
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$thumb = '';
if (function_exists('get_term_meta')) {
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = wp_get_attachment_image_url($img_id, 'medium') ?: '';
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
}
}
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '">';
if ($thumb !== '') {
echo '<div class="aksrt-term-card__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>';
} else {
echo '<div class="aksrt-term-card__cover is-empty"></div>';
}
echo '<div class="aksrt-term-card__body">';
echo '<div class="aksrt-term-card__name">' . esc_html($term->name) . '</div>';
echo '<div class="aksrt-term-card__count">' . esc_html(sprintf(_n('%d 篇', '%d 篇', (int) $term->count, 'aksrt'), (int) $term->count)) . '</div>';
echo '</div></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 [];
}
$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');
?>
<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'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
<?php foreach ($taxs as $tax) : ?>
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'category'), $tax->name); ?>><?php echo esc_html($tax->labels->name . ' (' . $tax->name . ')'); ?></option>
<?php endforeach; ?>
</select>
</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('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
</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('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="count" <?php selected(($instance['orderby'] ?? 'count'), 'count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="id" <?php selected(($instance['orderby'] ?? ''), 'id'); ?>><?php esc_html_e('ID', '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('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?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'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
];
}
}
+138
View File
@@ -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'] ?? '')) ?: '',
];
}
}
+199
View File
@@ -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,
];
}
}
+13
View File
@@ -436,6 +436,19 @@ $initial_content = $draft_data ? $draft_data['content'] : '';
</div>
</div>
<?php if (is_active_sidebar('bbs_new_posts_sidebar_top') || is_active_sidebar('bbs_new_posts_sidebar_bottom')) : ?>
<div class="aksrt-forum-widget-zone">
<?php
if (is_active_sidebar('bbs_new_posts_sidebar_top')) {
dynamic_sidebar('bbs_new_posts_sidebar_top');
}
if (is_active_sidebar('bbs_new_posts_sidebar_bottom')) {
dynamic_sidebar('bbs_new_posts_sidebar_bottom');
}
?>
</div>
<?php endif; ?>
</div><!-- /.aksrt-forum-compose__sidebar -->
</form>
</div>
+7 -1
View File
@@ -56,8 +56,9 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
?>
<main id="main-content" class="site-main content-shell content-shell--stream content-shell--forum" tabindex="-1">
<?php if (is_active_sidebar('bbs_plate_top_fluid')) { echo '<div class="aksrt-fluid-widget aksrt-fluid-widget--forum">'; dynamic_sidebar('bbs_plate_top_fluid'); echo '</div>'; } ?>
<div class="content-wrap container">
<div class="content-layout content-layout--no-sidebar">
<div class="content-layout content-layout--with-sidebar">
<div class="content-layout__primary">
<?php if (function_exists('aksrt_breadcrumbs')) { aksrt_breadcrumbs(); } ?>
@@ -318,6 +319,11 @@ $max_pages = (int) ($view['pagination']['max_pages'] ?? 1);
<?php endif; ?>
</div>
<?php if (is_active_sidebar('bbs_plate_sidebar')) : ?>
<aside class="content-layout__sidebar aksrt-forum-widget-zone">
<?php dynamic_sidebar('bbs_plate_sidebar'); ?>
</aside>
<?php endif; ?>
</div>
</div>
</main>
+5
View File
@@ -412,6 +412,11 @@ $comments_enabled = !empty($permissions['can_reply']);
</div>
</div>
</section>
<?php if (is_active_sidebar('bbs_posts_sidebar')) : ?>
<div class="aksrt-forum-widget-zone">
<?php dynamic_sidebar('bbs_posts_sidebar'); ?>
</div>
<?php endif; ?>
</aside>
<div class="aksrt-forum-report-modal" id="aksrt-report-modal" hidden>
+5
View File
@@ -348,6 +348,11 @@ if (empty($public_home_tabs)) {
</ul>
</div>
<?php endif; ?>
<?php if (is_active_sidebar('bbs_home_sidebar')) : ?>
<div class="aksrt-forum-widget-zone">
<?php dynamic_sidebar('bbs_home_sidebar'); ?>
</div>
<?php endif; ?>
</aside>
</div>
</div>