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