Merge pull request 'feat(widgets): complete phase 3 atomic integration' (#5) from worktree/widgets-phase-3 into main

Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2026-08-17 10:35:39 +00:00
89 changed files with 843 additions and 12886 deletions
+4 -4
View File
@@ -27,10 +27,10 @@
## Phase 3 — 原子切换、挂载区域与导入导出
- [ ] 替换注册器并删除旧小工具类、旧公共层、旧路由和重复资源。
- [ ] 重写 40 个静态挂载区域、动态页面区域及所有模板消费点。
- [ ] 实现 `aksrt.widgets.v1` 导入导出和 5 套知识付费模板。
- [ ] 核对默认 WordPress 小工具注销策略与区块编辑器/自定义器行为。
- [x] 替换注册器并删除旧小工具类、旧公共层、旧路由和重复资源。
- [x] 重写 40 个静态挂载区域、动态页面区域及所有模板消费点。
- [x] 实现 `aksrt.widgets.v1` 导入导出和 5 套知识付费模板。
- [x] 核对默认 WordPress 小工具注销策略与区块编辑器/自定义器行为。
## Phase 4 — 自动化与视觉终验
-277
View File
@@ -1,277 +0,0 @@
/**
* Batch3 widgets: iframe, combo info, text title, and user lists.
* @package AKSRT
*/
/* ---- 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;
}
/* ---- 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;
}
-48
View File
@@ -1,48 +0,0 @@
/**
* Batch3c: highlights.
*/
.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; }
-29
View File
@@ -1,29 +0,0 @@
/**
* Batch3d: term cards.
*/
.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; }
-78
View File
@@ -1,78 +0,0 @@
/**
* Batch3e: author card.
*/
.aksrt-author-card {
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
margin-bottom: 0;
text-align: center;
}
.aksrt-author-card__cover {
height: 139px;
min-height: 139px;
max-height: 139px;
background: linear-gradient(135deg, #93c5fd, #c4b5fd) center/cover no-repeat;
}
.aksrt-author-card__body {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 14px 16px;
}
.aksrt-author-card__body .user-content {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.aksrt-author-card__avatar {
display: flex;
flex: 0 0 auto;
margin: -38px auto 0;
border-radius: 50%;
overflow: hidden;
border: 3px solid var(--surface-1, #fff);
width: 76px;
height: 76px;
}
.aksrt-author-card__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-author-card__name {
display: block;
margin-top: 8px;
font-weight: 700;
font-size: 16px;
color: inherit;
text-decoration: none;
}
.aksrt-author-card__bio {
margin-top: 6px;
font-size: 12px;
color: var(--muted-2-color, #888);
line-height: 1.5;
}
.aksrt-author-card__stats {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 12px;
font-size: 12px;
color: var(--muted-2-color, #888);
}
.aksrt-author-card__stats b {
display: block;
font-size: 16px;
color: var(--color-ink, #111);
}
.aksrt-author-card__btn {
display: inline-block;
margin-top: 12px;
padding: 6px 16px;
border-radius: 999px;
background: var(--color-primary, #3b82f6);
color: #fff !important;
text-decoration: none;
font-size: 13px;
}
-278
View File
@@ -1,278 +0,0 @@
/**
* Batch3f: oneline posts + tilt cases.
*/
.aksrt-oneline { margin-bottom: 12px; }
.aksrt-oneline__track {
display: grid;
grid-auto-flow: column;
grid-auto-columns: minmax(180px, 220px);
gap: 12px;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 6px;
-webkit-overflow-scrolling: touch;
}
.aksrt-oneline.style-mini .aksrt-oneline__track {
grid-auto-columns: minmax(140px, 160px);
}
.aksrt-oneline__item {
display: flex;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
text-decoration: none;
color: inherit;
scroll-snap-align: start;
min-width: 0;
}
.aksrt-oneline.style-list .aksrt-oneline__item {
flex-direction: row;
align-items: center;
gap: 10px;
padding: 8px;
}
.aksrt-oneline__thumb {
height: 110px;
background: var(--surface-2, #f3f4f6) center/cover no-repeat;
overflow: hidden;
}
.aksrt-oneline.style-mini .aksrt-oneline__thumb { height: 90px; }
.aksrt-oneline.style-list .aksrt-oneline__thumb {
width: 64px;
height: 64px;
border-radius: 8px;
flex-shrink: 0;
}
.aksrt-oneline__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-oneline__thumb.is-empty { background: linear-gradient(135deg, #e2e8f0, #f8fafc); }
.aksrt-oneline__body { padding: 10px 12px 12px; min-width: 0; }
.aksrt-oneline.style-list .aksrt-oneline__body { padding: 0; }
.aksrt-oneline__title {
font-size: 13px;
font-weight: 600;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.aksrt-oneline__meta { margin-top: 4px; font-size: 11px; color: var(--muted-2-color, #999); }
.aksrt-tilt {
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: 28px;
align-items: center;
margin-bottom: 18px;
}
@media (max-width: 991.98px) {
.aksrt-tilt { grid-template-columns: 1fr; }
}
.aksrt-tilt__badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
padding: 4px 10px;
border-radius: 999px;
background: #eff6ff;
color: #2563eb;
margin-bottom: 12px;
}
.aksrt-tilt__title {
font-size: 30px;
line-height: 1.25;
margin: 0 0 12px;
}
.aksrt-tilt__hl {
font-style: normal;
background: linear-gradient(90deg, #3b82f6, #a855f7);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.aksrt-tilt__subtitle {
font-size: 14px;
color: var(--muted-2-color, #666);
line-height: 1.7;
margin: 0 0 14px;
}
.aksrt-tilt__features {
list-style: none;
margin: 0 0 16px;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 8px 14px;
font-size: 13px;
}
.aksrt-tilt__features i { color: var(--color-primary, #3b82f6); margin-right: 4px; }
.aksrt-tilt__actions { display: flex; flex-wrap: wrap; gap: 10px; }
.aksrt-tilt__btn {
display: inline-flex;
align-items: center;
padding: 8px 16px;
border-radius: 999px;
background: var(--color-primary, #3b82f6);
color: #fff !important;
text-decoration: none;
font-size: 13px;
font-weight: 600;
}
.aksrt-tilt__showcase {
position: relative;
min-height: 260px;
}
.aksrt-tilt__img {
position: absolute;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 16px 40px rgba(0,0,0,.12);
background: var(--surface-2, #f3f4f6);
}
.aksrt-tilt__img.is-main {
position: relative;
width: 100%;
aspect-ratio: 4/3;
}
.aksrt-tilt__img:not(.is-main) {
width: 42%;
aspect-ratio: 4/3;
right: -4%;
bottom: -8%;
z-index: 2;
}
.aksrt-tilt__img:nth-child(3) {
left: -4%;
top: -6%;
right: auto;
bottom: auto;
width: 38%;
z-index: 1;
}
.aksrt-tilt__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-tilt__img.is-empty { background: linear-gradient(135deg, #dbeafe, #ede9fe); }
/* Check-in details modal */
.aksrt-checkin-modal { padding: 4px 2px 8px; }
.aksrt-checkin-modal__head {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
margin-bottom: 14px;
}
.aksrt-checkin-modal__head .aksrt-modal-runtime__close {
margin-left: auto;
border: 0;
background: transparent;
font-size: 22px;
line-height: 1;
cursor: pointer;
opacity: .55;
}
.aksrt-checkin-modal__user {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.aksrt-checkin-modal__avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.aksrt-checkin-modal__name { font-weight: 700; }
.aksrt-checkin-modal__sub { font-size: 12px; color: var(--muted-2-color, #888); }
.aksrt-checkin-modal__wallet { margin-bottom: 12px; }
.aksrt-checkin-modal__wallet-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-radius: 10px;
background: var(--surface-2, #f3f4f6);
text-decoration: none;
color: inherit;
}
.aksrt-checkin-modal__card {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px;
border-radius: 12px;
border: 1px solid var(--color-line, #e5e7eb);
margin-bottom: 14px;
}
.aksrt-checkin-modal__card-title { font-size: 13px; margin-bottom: 6px; }
.aksrt-checkin-modal__badge {
display: inline-block;
font-size: 12px;
padding: 2px 8px;
border-radius: 999px;
background: #ecfdf5;
color: #059669;
}
.aksrt-checkin-modal__done { text-align: right; color: #2563eb; font-size: 13px; }
.aksrt-checkin-modal__btn { white-space: nowrap; }
.aksrt-checkin-modal__streak-head {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
margin-bottom: 8px;
}
.aksrt-checkin-modal__ladder {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 6px;
margin-bottom: 14px;
}
.aksrt-checkin-modal__day {
text-align: center;
border-radius: 8px;
padding: 8px 2px;
background: var(--surface-2, #f8fafc);
border: 1px solid transparent;
font-size: 11px;
}
.aksrt-checkin-modal__day.is-done {
background: #eff6ff;
border-color: #bfdbfe;
color: #1d4ed8;
}
.aksrt-checkin-modal__day-n { font-weight: 700; margin-bottom: 2px; }
.aksrt-checkin-modal__cal-title {
font-size: 13px;
margin-bottom: 8px;
}
.aksrt-signin-calendar__grid {
display: grid;
grid-template-columns: repeat(10, minmax(0, 1fr));
gap: 4px;
}
.aksrt-signin-calendar__day {
aspect-ratio: 1;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
background: var(--surface-2, #f3f4f6);
color: #999;
}
.aksrt-signin-calendar__day.is-signed {
background: #dbeafe;
color: #1d4ed8;
font-weight: 700;
}
.aksrt-signin-calendar__day.is-today {
outline: 1px solid var(--color-primary, #3b82f6);
}
.aksrt-header-checkin-details { text-decoration: none; }
File diff suppressed because it is too large Load Diff
-93
View File
@@ -1,93 +0,0 @@
/**
* Widget slider (AKSRT 幻灯片).
* @package AKSRT
*/
.aksrt-widget-slider {
position: relative;
overflow: hidden;
border-radius: 8px;
background: var(--surface-2, #f3f4f6);
}
.aksrt-widget-slider__track {
position: relative;
width: 100%;
height: var(--aksrt-slider-h, 220px);
}
.aksrt-widget-slider__slide {
position: absolute;
inset: 0;
opacity: 0;
pointer-events: none;
transition: opacity .35s ease;
}
.aksrt-widget-slider__slide.is-active {
opacity: 1;
pointer-events: auto;
z-index: 1;
}
.aksrt-widget-slider__slide img,
.aksrt-widget-slider__link {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
.aksrt-widget-slider__caption {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 10px 12px;
background: linear-gradient(transparent, rgba(0,0,0,.55));
color: #fff;
display: flex;
flex-direction: column;
gap: 2px;
font-size: 13px;
}
.aksrt-widget-slider__caption strong {
font-size: 14px;
}
.aksrt-widget-slider__prev,
.aksrt-widget-slider__next {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 2;
width: 28px;
height: 28px;
border: 0;
border-radius: 50%;
background: rgba(0,0,0,.35);
color: #fff;
cursor: pointer;
line-height: 28px;
text-align: center;
padding: 0;
}
.aksrt-widget-slider__prev { left: 8px; }
.aksrt-widget-slider__next { right: 8px; }
.aksrt-widget-slider__dots {
position: absolute;
left: 0;
right: 0;
bottom: 8px;
z-index: 2;
display: flex;
justify-content: center;
gap: 6px;
pointer-events: none;
}
.aksrt-widget-slider__dot {
pointer-events: auto;
width: 7px;
height: 7px;
border-radius: 50%;
border: 0;
padding: 0;
background: rgba(255,255,255,.45);
cursor: pointer;
}
.aksrt-widget-slider__dot.is-active {
background: #fff;
}
-11
View File
@@ -1,11 +0,0 @@
@import url("widgets/part-1.css");
@import url("widgets/part-2.css");
@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");
@import url("widget-batch3e.css");
@import url("widget-batch3f.css");
@import url("widget-batch5.css");
-299
View File
@@ -1,299 +0,0 @@
/* ========== 侧栏小工具(模仿 zibll .zib-widget) ========== */
.content-layout__aside .widget,
.widget-card,
.zib-widget {
clear: both;
background: var(--surface-1);
padding: var(--card-padding);
box-shadow: var(--shadow-sm);
border-radius: var(--radius);
margin-bottom: 20px;
border: 1px solid var(--color-line);
}
/* Custom HTML widgets: de-card (no background, no padding — matches Zibll) */
.widget_custom_html,
.zib-widget.widget_custom_html {
background: transparent;
padding: 0;
box-shadow: none;
}
/* Zibll zib-widget-sm / noshadow modifier */
.zib-widget-sm,
.zib-widget.noshadow {
box-shadow: none !important;
}
.widget-title {
font-size: 15px;
font-weight: 700;
margin: 0 0 10px;
padding-bottom: 8px;
position: relative;
color: var(--color-ink);
border-bottom: none;
text-transform: none;
letter-spacing: normal;
}
/* 标题下划线装饰(模仿 zibll) */
.widget-title::before {
content: '';
position: absolute;
left: 0;
bottom: 3px;
width: 40px;
height: 3px;
background: var(--color-primary);
border-radius: 5px;
box-shadow: 1px 1px 3px -1px var(--color-primary);
transition: transform 0.4s ease;
transform-origin: left;
}
/* 暗色模式 */
.theme-dark .content-layout__aside .widget,
html.dark .content-layout__aside .widget,
.theme-dark .widget-card,
html.dark .widget-card,
.theme-dark .zib-widget,
html.dark .zib-widget {
background: var(--surface-1);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.author-card {
text-align: center;
padding: 28px 20px;
border-radius: var(--radius-lg);
background: linear-gradient(145deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
color: #fff;
box-shadow: var(--shadow-md);
border: none;
}
.author-card img {
width: 100px;
height: 100px;
border-radius: var(--radius-avatar);
border: 3px solid rgba(255, 255, 255, 0.35);
margin-bottom: 14px;
object-fit: cover;
}
.author-card h3 {
margin: 0 0 8px;
color: #fff;
font-size: 1.125rem;
}
.author-card p {
margin: 0 0 16px;
font-size: 0.875rem;
opacity: 0.92;
line-height: 1.55;
}
.author-card .social-row,
.author-card .profile-social-links {
justify-content: center;
}
.profile-social-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.profile-social-links a,
.social-icon-link {
width: 36px;
height: 36px;
border-radius: var(--radius-avatar);
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
transition: background 0.2s;
}
.profile-social-links a i,
.social-icon-link i {
font-size: 18px;
line-height: 1;
display: block;
}
.profile-social-links a:hover,
.social-icon-link:hover {
background: rgba(255, 255, 255, 0.35);
color: #fff;
}
.widget_search .search-form {
display: flex;
position: relative;
}
.widget_search label {
position: absolute;
left: 16px;
top: 50%;
transform: translateY(-50%);
color: var(--color-ink-4);
}
.widget_search .search-field {
width: 100%;
height: 44px;
padding: 0 16px 0 44px;
border-radius: var(--radius-btn);
border: 1px solid var(--color-line);
background: var(--surface-3);
color: var(--color-ink);
font-size: 14px;
}
.widget_search .search-field:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-50);
}
.widget_search .search-submit {
position: absolute;
right: 4px;
top: 4px;
height: 36px;
padding: 0 14px;
border-radius: 6px;
border: none;
background: var(--color-primary);
color: #fff;
font-weight: 600;
cursor: pointer;
}
.widget-list {
list-style: none;
margin: 0;
padding: 0;
}
.widget-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--color-line);
font-size: 14px;
color: var(--color-ink-3);
}
.widget-list li:last-child {
border-bottom: none;
}
.widget-list li a {
flex: 1;
transition: color 0.2s, padding 0.2s;
}
.widget-list li a:hover {
color: var(--color-primary);
padding-left: 6px;
}
.widget-list li span {
color: var(--color-ink-4);
font-variant-numeric: tabular-nums;
}
.widget_recent_entries li,
.recent-posts-row {
display: flex;
gap: 12px;
align-items: flex-start;
padding: 10px 0;
border-bottom: 1px solid var(--color-line);
}
.widget_recent_entries li:last-child,
.recent-posts-row:last-child {
border-bottom: none;
}
.recent-posts-row img {
width: 60px;
height: 60px;
border-radius: var(--radius-btn);
object-fit: cover;
flex-shrink: 0;
}
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.tag-cloud a {
padding: 6px 12px;
border-radius: var(--radius-pill);
border: 1px solid var(--color-line);
font-weight: 500;
color: var(--color-ink-3);
transition: background 0.2s, border-color 0.2s, color 0.2s;
}
.tag-cloud a:hover {
border-color: var(--color-primary);
background: var(--color-primary);
color: #fff;
}
.content-layout__aside .widget:last-child {
margin-bottom: 0;
}
/* ============================================================
* 热榜文章小工具 — v4.0 Zibll-Inspired
* ============================================================ */
.widget-hot-posts {
display: flex;
flex-direction: column;
gap: 0;
}
.hot-post-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid var(--color-line);
}
.hot-post-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.hot-post-item:first-child {
padding-top: 0;
}
.hot-post-item__rank {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
min-width: 22px;
border-radius: 4px;
font-size: 12px;
font-weight: 700;
background: var(--surface-3);
color: var(--color-ink-4);
-312
View File
@@ -1,312 +0,0 @@
flex-shrink: 0;
margin-top: 2px;
}
/* 前三名高亮 */
.hot-post-item.rank-top .hot-post-item__rank {
background: var(--color-primary);
color: #fff;
}
.hot-post-item__info {
flex: 1;
min-width: 0;
}
.hot-post-item__title {
font-size: 13px;
font-weight: 500;
line-height: 1.4;
color: var(--color-ink);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
transition: color 0.15s ease;
}
.hot-post-item__title:hover {
color: var(--color-primary);
}
.hot-post-item__meta {
display: flex;
gap: 10px;
font-size: 11px;
color: var(--color-ink-4);
margin-top: 4px;
}
.hot-post-item__meta span {
display: inline-flex;
align-items: center;
gap: 3px;
}
.hot-post-item__meta i {
font-size: 10px;
}
/* ============================================================
* 多栏目 Tab 切换小工具 — v4.0 Zibll-Inspired
* ============================================================ */
.widget-tab-posts__nav {
display: flex;
list-style: none;
margin: 0 0 12px;
padding: 0;
gap: 2px;
border-bottom: 1px solid var(--color-line);
}
.widget-tab-posts__nav li {
margin: 0;
}
.widget-tab-posts__tab {
display: inline-block;
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
color: var(--color-ink-4);
background: none;
border: none;
border-bottom: 2px solid transparent;
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
margin-bottom: -1px;
}
.widget-tab-posts__tab:hover {
color: var(--color-ink);
}
.widget-tab-posts__tab.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
font-weight: 600;
}
.widget-tab-posts__panel {
display: none;
}
.widget-tab-posts__panel.active {
display: block;
}
.widget-tab-posts__list {
list-style: none;
margin: 0;
padding: 0;
}
.widget-tab-posts__item {
display: flex;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid var(--color-line);
}
.widget-tab-posts__item:last-child {
border-bottom: none;
}
.widget-tab-posts__thumb {
width: 56px;
height: 42px;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
}
.widget-tab-posts__thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.widget-tab-posts__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.widget-tab-posts__title {
font-size: 13px;
font-weight: 500;
line-height: 1.4;
color: var(--color-ink);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
transition: color 0.15s ease;
}
.widget-tab-posts__title:hover {
color: var(--color-primary);
}
.widget-tab-posts__meta {
font-size: 11px;
color: var(--color-ink-4);
margin-top: 2px;
}
.aksrt-homepage__aside .widget-tab-posts__list--mini,
.aksrt-homepage__aside .aksrt-home-widget__sidebar-list {
display: block;
}
.aksrt-homepage__aside .posts-mini {
display: flex;
align-items: flex-start;
width: 100% !important;
gap: 12px;
padding: 10px 0;
min-width: 0;
}
.aksrt-homepage__aside .widget-tab-posts__list--mini .posts-mini + .posts-mini,
.aksrt-homepage__aside .aksrt-home-widget__sidebar-list .posts-mini + .posts-mini {
border-top: 1px solid var(--color-line);
}
.aksrt-homepage__aside .widget-tab-posts__list--mini .posts-mini:first-of-type,
.aksrt-homepage__aside .aksrt-home-widget__sidebar-list .posts-mini:first-of-type {
padding-top: 0;
}
.aksrt-homepage__aside .widget-tab-posts__list--mini .posts-mini:last-of-type,
.aksrt-homepage__aside .aksrt-home-widget__sidebar-list .posts-mini:last-of-type {
padding-bottom: 0;
}
.aksrt-homepage__aside .posts-mini-con {
flex: auto;
min-width: 0;
overflow: hidden;
}
.aksrt-homepage__aside .posts-mini .item-thumbnail {
display: block;
position: relative;
flex: 0 0 96px;
width: 96px;
min-width: 96px;
height: 0;
padding-bottom: var(--posts-list-scale, 70%);
border-radius: var(--radius, 8px);
overflow: hidden;
line-height: 0;
}
.aksrt-homepage__aside .posts-mini .item-thumbnail img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-homepage__aside .posts-mini .item-heading {
margin: 0;
font-size: 13px;
font-weight: 500;
line-height: 1.45;
max-height: 2.9em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.aksrt-homepage__aside .posts-mini .item-heading a {
color: var(--color-ink);
text-decoration: none;
transition: color 0.15s ease;
}
.aksrt-homepage__aside .posts-mini .item-heading a:hover {
color: var(--color-primary, var(--color-primary));
}
.aksrt-homepage__aside .posts-mini .item-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
margin-top: 6px;
font-size: 12px;
color: var(--color-ink-4);
}
.aksrt-homepage__aside .posts-mini .item-meta .meta-dot {
width: 3px;
height: 3px;
min-width: 3px;
border-radius: 50%;
background: currentColor;
opacity: 0.55;
}
.aksrt-homepage__aside .posts-mini .item-meta .meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.aksrt-homepage__aside .posts-mini .item-meta i {
font-size: 10px;
}
.aksrt-homepage__aside .widget-hot-posts--mini .hot-post-item {
align-items: stretch;
gap: 12px;
}
.aksrt-homepage__aside .widget-hot-posts--mini .hot-post-item .posts-mini {
flex: 1;
align-items: flex-start;
min-width: 0;
width: auto !important;
padding: 0;
}
.aksrt-homepage__aside .widget-hot-posts--mini .hot-post-item .posts-mini-con {
display: flex;
flex: 1 1 auto;
flex-direction: column;
justify-content: center;
min-width: 0;
}
.aksrt-homepage__aside .widget-hot-posts--mini .hot-post-item__rank {
margin-top: 2px;
align-self: flex-start;
}
.widget-empty {
text-align: center;
font-size: 13px;
color: var(--color-ink-4);
padding: 16px 0;
}
/* ============================================================
.aksrt-null--widget {
padding: 12px 0 !important;
}
.aksrt-null--widget .aksrt-null__img {
margin: 0 auto;
display: block;
}
.aksrt-null .separator {
border: 0;
}
-332
View File
@@ -1,332 +0,0 @@
* Homepage content widgets
* ============================================================ */
.aksrt-home-widget {
display: block;
}
.aksrt-home-widget__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 14px;
}
.aksrt-home-widget__header--tabs {
align-items: flex-end;
flex-wrap: wrap;
}
.aksrt-home-widget__title {
position: relative;
margin: 0;
padding-left: 16px;
color: var(--color-ink);
font: 700 15px/1.4 var(--font-ui, var(--font-ui));
}
.aksrt-home-widget__title::before {
content: "";
position: absolute;
top: 10%;
bottom: 10%;
left: 2px;
width: 4px;
border-radius: 5px;
background: var(--color-primary);
box-shadow: 1px 1px 3px -1px var(--color-primary);
}
.aksrt-home-widget__more {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 30px;
padding: 0 12px;
border-radius: 20px;
background: var(--surface-3);
color: var(--color-ink-3);
font-size: 12px;
font-weight: 600;
transition: background 0.2s ease, color 0.2s ease;
}
.aksrt-home-widget__more:hover {
background: var(--color-primary);
color: #fff;
}
.aksrt-home-widget__footer {
display: flex;
justify-content: center;
margin-top: 16px;
}
.aksrt-home-widget__tab-nav.widget-tab-posts__nav {
margin: 0;
padding: 0;
gap: 4px;
border-bottom: 0;
flex: 1 1 auto;
justify-content: flex-end;
flex-wrap: wrap;
}
.aksrt-home-widget__tab-nav .widget-tab-posts__tab {
padding: 5px 12px;
margin-bottom: 0;
border: 0;
border-radius: 20px;
background: var(--surface-3);
color: var(--color-ink-3);
}
.aksrt-home-widget__tab-nav .widget-tab-posts__tab:hover {
color: var(--color-primary);
}
.aksrt-home-widget__tab-nav .widget-tab-posts__tab.active {
background: var(--color-primary);
color: #fff;
}
.aksrt-home-widget__post-grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.aksrt-home-widget__post-grid--list {
gap: 0;
}
@media (min-width: 640px) {
.aksrt-home-widget__post-grid--card,
.aksrt-home-widget__post-grid--grid-cms {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 992px) {
.aksrt-home-widget__post-grid--card,
.aksrt-home-widget__post-grid--grid-cms {
grid-template-columns: repeat(var(--aksrt-widget-columns, 3), minmax(0, 1fr));
}
}
.aksrt-home-widget__post-grid .posts-item.card {
height: 100%;
border: 1px solid var(--color-line);
box-shadow: none;
}
.aksrt-home-widget__post-grid .posts-item.card:hover {
transform: none;
box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08));
}
/* Category block uses the unified renderer's .posts-row wrapper. Keep its card
* layout horizontal and wrap according to the configured column count. */
.zib-widget.widget_aksrt_category_block .posts-row.post-grid--card,
.zib-widget[id^="aksrt_category_block-"] .posts-row.post-grid--card {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
margin: 0;
font-size: initial;
}
.zib-widget.widget_aksrt_category_block .posts-row.post-grid--card > .posts-item.card,
.zib-widget[id^="aksrt_category_block-"] .posts-row.post-grid--card > .posts-item.card {
width: auto;
display: block;
margin: 0;
}
@media (min-width: 640px) {
.zib-widget.widget_aksrt_category_block .posts-row.post-grid--card,
.zib-widget[id^="aksrt_category_block-"] .posts-row.post-grid--card {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 992px) {
.zib-widget.widget_aksrt_category_block .posts-row.post-grid--card,
.zib-widget[id^="aksrt_category_block-"] .posts-row.post-grid--card {
grid-template-columns: repeat(var(--aksrt-widget-columns, 3), minmax(0, 1fr));
}
}
.aksrt-home-widget__post-grid .posts-item.list {
border-bottom: 1px solid var(--color-line);
}
.aksrt-home-widget__post-grid .posts-item.list:last-child {
border-bottom: 0;
}
.aksrt-home-widget__post-grid .item-heading {
font-size: 14px;
}
.aksrt-home-widget__post-grid .item-tags {
gap: 0;
margin-bottom: 6px;
}
.aksrt-home-widget__post-grid .item-tags .badge {
margin-right: 5px;
padding: 2px 5px;
font-size: 11px;
line-height: 1.4;
}
.aksrt-home-widget__post-grid .item-meta {
font-size: 12px;
}
.aksrt-home-widget__sidebar-list {
margin: 0;
padding: 0;
}
.aksrt-home-widget__sidebar-list .widget-tab-posts__item:first-child {
padding-top: 0;
}
.aksrt-category-recommend__grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 768px) {
.aksrt-category-recommend__grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 992px) {
.aksrt-category-recommend__grid {
grid-template-columns: repeat(var(--aksrt-recommend-columns, 2), minmax(0, 1fr));
}
}
.aksrt-category-recommend__card {
display: flex;
flex-direction: column;
min-width: 0;
background: var(--surface-page);
border: 1px solid var(--color-line);
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.aksrt-category-recommend__card:hover {
transform: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.aksrt-category-recommend__hero {
position: relative;
aspect-ratio: 16 / 9;
overflow: hidden;
background: var(--surface-3);
}
.aksrt-category-recommend__hero > a {
position: absolute;
inset: 0;
display: block;
}
.aksrt-category-recommend__cover {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.aksrt-category-recommend__card:hover .aksrt-category-recommend__cover {
transform: scale(1.04);
}
.aksrt-category-recommend__badge,
.aksrt-category-recommend__views {
position: absolute;
top: 10px;
z-index: 2;
display: inline-flex;
align-items: center;
gap: 4px;
min-height: 24px;
padding: 0 9px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
color: #fff;
background: rgba(17, 24, 39, 0.72);
backdrop-filter: blur(8px);
}
.aksrt-category-recommend__badge {
left: 10px;
}
.aksrt-category-recommend__views {
right: 10px;
}
.aksrt-category-recommend__body {
display: flex;
flex: 1 1 auto;
flex-direction: column;
gap: 12px;
padding: 14px;
}
.aksrt-category-recommend__meta {
display: flex;
flex-direction: column;
gap: 6px;
}
.aksrt-category-recommend__name {
margin: 0;
font-size: 15px;
line-height: 1.35;
}
.aksrt-category-recommend__name a {
color: var(--color-ink);
}
.aksrt-category-recommend__name a:hover {
color: var(--color-primary);
}
.aksrt-category-recommend__desc {
margin: 0;
color: var(--color-ink-4);
font-size: 13px;
line-height: 1.55;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.aksrt-category-recommend__posts {
list-style: none;
margin: 0;
padding: 0;
border-top: 1px solid var(--color-line);
}
.aksrt-category-recommend__post {
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
-95
View File
@@ -1,95 +0,0 @@
padding: 9px 0;
border-bottom: 1px dashed var(--color-line);
}
.aksrt-category-recommend__post:last-child {
border-bottom: 0;
}
.aksrt-category-recommend__post-link {
flex: 1 1 auto;
min-width: 0;
font-size: 13px;
line-height: 1.45;
color: var(--color-ink);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.aksrt-category-recommend__post-link:hover {
color: var(--color-primary);
}
.aksrt-category-recommend__date {
flex: 0 0 auto;
color: var(--color-ink-4);
font-size: 11px;
white-space: nowrap;
}
@media (max-width: 767.98px) {
.aksrt-home-widget__header,
.aksrt-home-widget__header--tabs {
align-items: flex-start;
flex-direction: column;
}
.aksrt-home-widget__tab-nav.widget-tab-posts__nav {
justify-content: flex-start;
}
}
/* ============================================================
* 流体挂件区(Fluid Widget Areas)
* 模仿 zibll 的 all_top_fluid / single_top_fluid 等全宽挂载位
* ============================================================ */
.aksrt-fluid-widget {
width: 100%;
max-width: var(--container-wide, 1440px);
margin: 0 auto;
padding: 0 16px;
}
@media (min-width: 768px) {
.aksrt-fluid-widget {
padding: 0 24px;
}
}
.aksrt-fluid-widget .widget {
margin-bottom: 0;
}
.aksrt-fluid-widget--all-top {
padding-top: 16px;
}
.aksrt-fluid-widget--all-bottom {
padding-bottom: 16px;
}
.aksrt-fluid-widget--home-top {
margin-bottom: 24px;
}
.aksrt-fluid-widget--single-top {
margin-bottom: 24px;
}
.aksrt-fluid-widget--single-bottom {
margin-top: 24px;
}
.aksrt-fluid-widget--archive-bottom {
margin-top: 24px;
}
/* 流体挂件区内无边框无背景(全宽展示) */
.aksrt-fluid-widget .widget_custom_html {
background: transparent;
border: none;
box-shadow: none;
padding: 0;
}
-337
View File
@@ -1,337 +0,0 @@
/**
* AKSRT Widget Set
*
* Handles widget-local interactions and floating QR widgets.
*
* @package AKSRT
*/
(function () {
'use strict';
function restHeaders() {
var headers = { Accept: 'application/json' };
if (window.aksrtData && aksrtData.restNonce) {
headers['X-WP-Nonce'] = aksrtData.restNonce;
}
return headers;
}
function initWidgetTabs() {
document.querySelectorAll('.widget-tab-posts[data-aksrt-tab-posts]').forEach(function (widget) {
if (widget.dataset.aksrtWidgetTabsBound === '1') return;
widget.dataset.aksrtWidgetTabsBound = '1';
var tabs = widget.querySelectorAll('.widget-tab-posts__tab');
var panels = widget.querySelectorAll('.widget-tab-posts__panel');
if (!tabs.length || !panels.length) return;
var rest = widget.getAttribute('data-rest') || '';
var defaultCount = widget.getAttribute('data-count') || '5';
function loadPanel(panel, tab) {
if (!panel || panel.getAttribute('data-loaded') === '1' || !rest) return;
var orderby = tab.getAttribute('data-orderby') || 'date';
var cat = tab.getAttribute('data-cat') || '0';
var tabCount = tab.getAttribute('data-tab-count') || '0';
var style = widget.getAttribute('data-style') || 'mini';
var paginate = widget.getAttribute('data-paginate') || 'none';
var qs = new URLSearchParams({
count: defaultCount,
orderby: orderby,
cat: cat,
tab_count: tabCount,
style: style,
paginate: paginate,
limit_day: tab.getAttribute('data-limit-day') || '0',
pay_type: tab.getAttribute('data-pay-type') || '',
});
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString();
panel.classList.add('is-ajax-loading');
fetch(url, { credentials: 'same-origin', headers: restHeaders() })
.then(function (r) { return r.json(); })
.then(function (j) {
var d = (j && j.data) || j || {};
var html = d.html || '';
var pag = d.paginate_html || d.paginateHtml || '';
panel.innerHTML = (html || '<p class="widget-empty">暂无文章</p>') + (pag || '');
panel.setAttribute('data-loaded', '1');
panel.classList.remove('is-ajax-loading');
})
.catch(function () {
panel.classList.remove('is-ajax-loading');
});
}
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var target = tab.getAttribute('data-tab');
tabs.forEach(function (item) {
var active = item === tab;
item.classList.toggle('active', active);
item.classList.toggle('is-active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
panels.forEach(function (panel) {
var active = panel.getAttribute('data-tab-panel') === target;
panel.classList.toggle('active', active);
panel.classList.toggle('is-active', active);
if (active) {
panel.removeAttribute('hidden');
loadPanel(panel, tab);
} else {
panel.setAttribute('hidden', '');
}
});
});
});
});
}
// Homepage category tabs (was inline in CategoryTabsWidget).
function initCategoryTabs() {
document.querySelectorAll('[data-aksrt-home-cat-tabs]').forEach(function (section) {
if (section.dataset.aksrtCatTabsBound === '1') return;
section.dataset.aksrtCatTabsBound = '1';
var rest = section.getAttribute('data-rest') || '';
var count = section.getAttribute('data-count') || '6';
var layout = section.getAttribute('data-layout') || 'card';
var columns = section.getAttribute('data-columns') || '3';
var context = section.getAttribute('data-context') || 'homepage';
var showMore = section.getAttribute('data-show-more') === '1';
var tabs = [].slice.call(section.querySelectorAll('.widget-tab-posts__tab'));
var panels = [].slice.call(section.querySelectorAll('.widget-tab-posts__panel'));
if (!tabs.length || !panels.length || !rest) return;
function load(panel) {
if (!panel || panel.getAttribute('data-loaded') === '1') return;
var cat = panel.getAttribute('data-cat') || '0';
var more = panel.getAttribute('data-more') || '';
var qs = new URLSearchParams({
cat: cat,
count: String(count),
layout: layout,
columns: String(columns),
context: context,
});
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString();
panel.classList.add('is-ajax-loading');
fetch(url, { credentials: 'same-origin', headers: restHeaders() })
.then(function (r) { return r.json(); })
.then(function (j) {
var d = (j && j.data) || j || {};
var html = d.html || '';
if (showMore && more) {
html += '<div class="aksrt-home-widget__footer"><a class="aksrt-home-widget__more" href="' + more + '">查看更多</a></div>';
}
panel.innerHTML = html || '<div class="ajax-item widget-empty muted-2-color">暂无文章</div>';
panel.setAttribute('data-loaded', '1');
panel.classList.remove('is-ajax-loading');
})
.catch(function () {
panel.classList.remove('is-ajax-loading');
});
}
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var target = tab.getAttribute('data-tab');
tabs.forEach(function (item) {
var active = item === tab;
item.classList.toggle('active', active);
item.setAttribute('aria-selected', active ? 'true' : 'false');
});
panels.forEach(function (panel) {
var active = panel.getAttribute('data-tab-panel') === target;
panel.classList.toggle('active', active);
if (active) {
panel.removeAttribute('hidden');
load(panel);
} else {
panel.setAttribute('hidden', '');
}
});
});
});
});
}
// Product multi-tab.
function initProductTabs() {
document.querySelectorAll('[data-aksrt-product-tab]').forEach(function (root) {
if (root.dataset.aksrtProductTabBound === '1') return;
root.dataset.aksrtProductTabBound = '1';
var rest = root.getAttribute('data-rest') || '';
var per = root.getAttribute('data-per-page') || '8';
var style = root.getAttribute('data-style') || 'default';
var paginate = root.getAttribute('data-paginate') || 'ajax';
function skeleton(n) {
n = Math.max(1, Math.min(12, parseInt(n, 10) || 4));
var html = '<div class="aksrt-product-tab__skeleton">';
for (var i = 0; i < n; i++) {
html += '<div class="placeholder-row product-card-placeholder"><div class="placeholder thumb"></div><div class="placeholder k1"></div><div class="placeholder s1"></div></div>';
}
return html + '</div>';
}
function loadPanel(panel, btn) {
if (!panel || panel.getAttribute('data-loaded') === '1' || !rest) return;
var grid = panel.querySelector('[data-aksrt-ajaxpager]');
if (!grid) return;
var qs = new URLSearchParams({
page: '1',
per_page: per,
style: style,
paginate: paginate,
orderby: btn.getAttribute('data-orderby') || 'date',
order: 'DESC',
cat_ids: btn.getAttribute('data-cat-ids') || '',
tag_ids: btn.getAttribute('data-tag-ids') || '',
discount_ids: btn.getAttribute('data-discount-ids') || '',
});
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + qs.toString();
grid.classList.add('is-ajax-loading');
if (!grid.querySelector('.aksrt-product-tab__skeleton') && !grid.querySelector('.ajax-item')) {
grid.insertAdjacentHTML('afterbegin', skeleton(per));
}
fetch(url, { credentials: 'same-origin', headers: restHeaders() })
.then(function (r) { return r.json(); })
.then(function (j) {
var d = (j && j.data && (j.data.data || j.data)) || j || {};
var html = d.html || '';
var pag = d.paginate_html || d.paginateHtml || '';
var loader = grid.querySelector('[data-aksrt-ajax-loader]');
grid.innerHTML = '';
if (html) grid.insertAdjacentHTML('beforeend', html);
else grid.insertAdjacentHTML('beforeend', '<p class="widget-empty ajax-item">暂无商品</p>');
if (pag) grid.insertAdjacentHTML('beforeend', pag);
if (loader) grid.appendChild(loader);
panel.setAttribute('data-loaded', '1');
grid.classList.remove('is-ajax-loading');
})
.catch(function () {
grid.classList.remove('is-ajax-loading');
});
}
root.querySelectorAll('.aksrt-product-tab__header').forEach(function (btn) {
btn.addEventListener('click', function () {
root.querySelectorAll('.aksrt-product-tab__header').forEach(function (b) {
b.classList.remove('is-active');
});
root.querySelectorAll('.aksrt-product-tab__panel').forEach(function (p) {
p.style.display = 'none';
p.classList.remove('is-active');
});
btn.classList.add('is-active');
var idx = btn.getAttribute('data-tab-index');
var panel = root.querySelector('.aksrt-product-tab__panel[data-tab-index="' + idx + '"]');
if (panel) {
panel.style.display = '';
panel.classList.add('is-active');
loadPanel(panel, btn);
}
});
});
});
}
// Main posts load_mode=ajax first paint.
function initMainPostsLazy() {
document.querySelectorAll('.aksrt-main-posts[data-load-mode="ajax"]').forEach(function (root) {
if (root.dataset.aksrtMainLazyBound === '1') return;
root.dataset.aksrtMainLazyBound = '1';
if (root.getAttribute('data-loaded') === '1') return;
var rest = root.getAttribute('data-aksrt-ajax-url') || root.getAttribute('data-rest') || '';
if (!rest) return;
var qs = root.getAttribute('data-query') || '';
var url = rest + (rest.indexOf('?') > -1 ? '&' : '?') + (qs || 'page=1');
root.classList.add('is-ajax-loading');
fetch(url, { credentials: 'same-origin', headers: restHeaders() })
.then(function (r) { return r.json(); })
.then(function (j) {
var d = (j && j.data) || j || {};
var html = d.html || '';
var pag = d.paginate_html || d.paginateHtml || '';
var loader = root.querySelector('[data-aksrt-ajax-loader]');
// Remove skeleton rows.
root.querySelectorAll('.aksrt-main-posts__skeleton, .posts-mini.placeholder-row, .placeholder-row').forEach(function (n) {
n.remove();
});
if (html) {
root.insertAdjacentHTML('afterbegin', html);
} else {
root.insertAdjacentHTML('afterbegin', '<p class="widget-empty ajax-item">暂无文章</p>');
}
// Replace/append paginate.
var oldPag = root.querySelector('.ajax-pag, .pagenav.ajax-pag');
if (oldPag) oldPag.remove();
if (pag) {
if (loader) loader.insertAdjacentHTML('beforebegin', pag);
else root.insertAdjacentHTML('beforeend', pag);
}
root.setAttribute('data-loaded', '1');
root.classList.remove('is-ajax-loading');
})
.catch(function () {
root.classList.remove('is-ajax-loading');
});
});
}
function initFloatingQRCodes() {
var wrap = document.querySelector('.float-right');
if (!wrap) return;
wrap.querySelectorAll('.service-wechat .hover-show-con p, .service-qq .hover-show-con p').forEach(function (node) {
node.remove();
});
if (typeof window.kjua !== 'function') return;
wrap.querySelectorAll('.float-btn-qrcode').forEach(function (node) {
if (node.dataset.qrReady === '1') return;
// footer.php uses data-qrcode-url; keep data-qr-text as fallback alias
var qrText =
node.getAttribute('data-qrcode-url') ||
node.getAttribute('data-qr-text') ||
window.location.href;
var qrSize = parseInt(node.getAttribute('data-qr-size') || '120', 10);
if (!qrText || !qrSize) return;
try {
node.innerHTML = '';
node.appendChild(window.kjua({
render: 'svg',
text: qrText,
size: qrSize,
ecLevel: 'H',
fill: '#15171A',
back: '#FFFFFF',
quiet: 0,
crisp: true
}));
node.style.width = qrSize + 'px';
node.style.height = qrSize + 'px';
node.dataset.qrReady = '1';
} catch (error) {
node.dataset.qrReady = '0';
}
});
}
function init() {
initWidgetTabs();
initCategoryTabs();
initProductTabs();
initMainPostsLazy();
initFloatingQRCodes();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
+33
View File
@@ -159,6 +159,39 @@
.aksrt-widget-form-lazy__status { margin-top: 0; color: #646970; }
.aksrt-widget-placement-hint {
display: flex;
gap: 6px;
align-items: flex-start;
margin: 10px 0 0;
padding: 8px;
border-left: 3px solid #72aee6;
background: #f0f6fc;
color: #50575e;
}
.aksrt-widget-placement-hint.is-warning {
border-color: #dba617;
background: #fcf9e8;
color: #614200;
}
.aksrt-widget-transfer { margin: 16px 20px 16px 0; }
.aksrt-widget-transfer__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; }
.aksrt-widget-transfer form { display: grid; align-content: start; gap: 9px; padding: 12px; border: 1px solid #dcdcde; border-radius: 6px; }
.aksrt-widget-transfer form h3 { margin: 0 0 3px; }
.aksrt-widget-transfer select,
.aksrt-widget-transfer input[type="file"] { width: 100%; }
.aksrt-widget-transfer__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.aksrt-widget-template-preview-output { grid-column: 1 / -1; max-height: 520px; overflow: auto; padding: 14px; border: 1px solid #dcdcde; background: #fff; }
.aksrt-widget-template-preview__area { margin-bottom: 18px; }
.aksrt-widget-template-preview__module { margin: 10px 0; padding: 12px; border: 1px dashed #c3c4c7; border-radius: 6px; }
@media (max-width: 1100px) {
.aksrt-widget-transfer__grid { grid-template-columns: 1fr; }
.aksrt-widget-template-preview-output { grid-column: auto; }
}
@media (prefers-reduced-motion: reduce) {
.aksrt-widget-switch span,
.aksrt-widget-switch span::after { transition: none; }
+41 -2
View File
@@ -16,8 +16,29 @@
if (output) this.addEventListener('input', () => { output.textContent = this.value; });
});
evaluateDependencies($root);
updateWidgetTitle($root);
updatePlacementWarning($root);
}
function updateWidgetTitle(root) {
const $root = $(root);
const title = String($root.find('.aksrt-widget-field[data-field-id="title"] input').first().val() || '').trim();
if (!title) return;
$root.closest('.widget').find('.widget-title h3, .widget-title-action + h3').first().contents().filter(function () {
return this.nodeType === Node.TEXT_NODE;
}).first().replaceWith(`${title} `);
}
function updatePlacementWarning(root) {
const $widget = $(root).closest('.widget');
const $hint = $widget.find('.aksrt-widget-placement-hint').first();
if (!$hint.length) return;
const size = String($hint.data('widget-size') || 'any');
const area = String($widget.closest('.widgets-sortables').attr('id') || '');
const isSidebar = /sidebar/.test(area);
$hint.toggleClass('is-warning', (size === 'wide' && isSidebar) || (size === 'sidebar' && !isSidebar && area !== ''));
}
function loadForm(container) {
const $container = $(container);
if ($container.data('loading') || $container.data('loaded')) return;
@@ -82,7 +103,11 @@
$(document)
.on('click', '.aksrt-widget-form-lazy__load', function () { loadForm($(this).closest('.aksrt-widget-form-lazy')); })
.on('click', '.widget.open .aksrt-widget-form-lazy', function () { loadForm(this); })
.on('change input', '.aksrt-widget-form input, .aksrt-widget-form select, .aksrt-widget-form textarea', function () { evaluateDependencies($(this).closest('.aksrt-widget-form')); })
.on('change input', '.aksrt-widget-form input, .aksrt-widget-form select, .aksrt-widget-form textarea', function () {
const $form = $(this).closest('.aksrt-widget-form');
evaluateDependencies($form);
updateWidgetTitle($form);
})
.on('click', '.aksrt-widget-repeater__add', function () {
const $repeater = $(this).closest('.aksrt-widget-repeater');
const max = Number($repeater.data('max-items') || 50);
@@ -116,10 +141,24 @@
const $wrap = $(this).closest('.aksrt-widget-upload');
$wrap.find('input[type="url"]').val('').trigger('change');
$wrap.find('img').remove();
});
})
.on('click', '.aksrt-widget-template-preview', function () {
const $form = $(this).closest('form');
const $output = $('.aksrt-widget-template-preview-output').first();
$output.prop('hidden', false).html('<p>正在生成预览…</p>');
$.post(config.ajaxUrl, {
action: 'aksrt_widgets_template_preview',
nonce: config.transferNonce,
template: $form.find('[name="template"]').val(),
}).done((response) => {
if (!response?.success) throw new Error('Invalid response');
$output.html(response.data.html || '<p>模板为空。</p>');
}).fail(() => $output.html('<p class="notice notice-error">预览加载失败。</p>'));
});
$(document).on('widget-added widget-updated', function (_event, widget) {
$(widget).find('.aksrt-widget-form-lazy').each(function () { loadForm(this); });
updatePlacementWarning(widget);
});
$(function () {
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
# Widget third-party assets
Only libraries requested by the 37-widget registry are shipped. The five player/tilt files are pinned byte-for-byte from the frozen reference directory `C:\Users\Xing\Desktop\zibll\js\libs`; Swiper was already bundled by AKSRT and is shared with the new widget runtime.
| Asset | Upstream | License | SHA-256 |
|---|---|---|---|
| `DPlayer.min.js` | [DPlayer](https://github.com/DIYgod/DPlayer) | MIT | `361689807fc1e856f868df91036e41195d2c255619bd1c5526849c5ad7491241` |
| `hls.min.js` | [hls.js](https://github.com/video-dev/hls.js) | Apache-2.0 | `e876282a26116e323c8ed3e86bed3838a80dbbe3a2faa09f834bee86963ff760` |
| `flv.min.js` | [flv.js](https://github.com/bilibili/flv.js) | Apache-2.0 | `0668d36757cdc17804d5c0bd6fe8956d76370e73a733bf5bbc0eddbfbca2c6cc` |
| `dash.all.min.js` | [dash.js](https://github.com/Dash-Industry-Forum/dash.js) | BSD-3-Clause | `3e753969c16a2a78f420a25d24869f14721eb1d284418f9f22d35a31c3693cd9` |
| `vanilla-tilt.min.js` | [vanilla-tilt.js](https://github.com/micku7zu/vanilla-tilt.js) | MIT | `de6fa8d3f40dbae2726a72f7d1ae46bb0588267af05355bef1d128f737f4a282` |
| `../../js/swiper-bundle.min.js` | [Swiper 11.2.10](https://github.com/nolimits4web/swiper) | MIT | `985f1224c0eeec99d367a9db35e58e44b21e7eb9ce45831b15b4c108e41fd97f` |
| `../../css/frontend/swiper-bundle.min.css` | [Swiper 11.2.10](https://github.com/nolimits4web/swiper) | MIT | `74ca6aae5468dbc924790c3b4d219a089b90a34bad53a0f7ca3a73e73b6f5ab8` |
The hashes are the release contract. Do not replace these files through a CDN or silently update them.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -12
View File
@@ -167,17 +167,13 @@ if ($_footer_conter === '') {
}
?>
<?php if (is_active_sidebar('single_bottom_fluid') && is_singular()) : ?>
<?php if (is_active_sidebar('single_bottom_fluid') && is_singular('post')) : ?>
<div class="fluid-widget-wrap">
<?php dynamic_sidebar('single_bottom_fluid'); ?>
</div>
<?php endif; ?>
<?php if (is_active_sidebar('archive_bottom_fluid') && (is_archive() || is_search())) : ?>
<div class="fluid-widget-wrap">
<?php dynamic_sidebar('archive_bottom_fluid'); ?>
</div>
<?php endif; ?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('all_bottom_fluid', 'fluid-widget-wrap'); ?>
<footer class="footer" data-aksrt-shell="site-footer" role="contentinfo">
<?php if (is_active_sidebar('all_footer')) : ?>
@@ -531,12 +527,6 @@ if (is_readable($aksrt_search_panel)) {
}
?>
<?php if (is_active_sidebar('all_bottom_fluid')) : ?>
<div class="fluid-widget-wrap">
<?php dynamic_sidebar('all_bottom_fluid'); ?>
</div>
<?php endif; ?>
<?php
// 弹窗登录/注册模板(仅未登录用户)
if (!is_user_logged_in() && file_exists(AKSRT_DIR . '/templates/parts/modal-auth.php')) {
+1 -4
View File
@@ -134,6 +134,7 @@ if (get_option('aksrt_flushed_version') !== AKSRT_VERSION) {
// 初始化所有单例实例
// ============================================================
\AKSRT\Core\Setup::get_instance()->init();
\AKSRT\WidgetSystem\Runtime::init();
\AKSRT\Core\Scripts::get_instance()->init();
\AKSRT\Admin\Admin::get_instance()->init();
if (class_exists('\AKSRT\Admin\NavMenuOptions')) {
@@ -234,10 +235,6 @@ if (class_exists('\AKSRT\Core\DocsPresenter')) {
if (class_exists('\AKSRT\Core\HomeModules')) {
\AKSRT\Core\HomeModules::get_instance()->init();
}
// Public widget list REST (posts / tab-posts ajax)
if (class_exists('\AKSRT\Core\WidgetAjax')) {
\AKSRT\Core\WidgetAjax::get_instance()->init();
}
if (class_exists('\AKSRT\Content\Visibility')) {
\AKSRT\Content\Visibility::get_instance()->init();
}
+2
View File
@@ -110,4 +110,6 @@ if (function_exists('aksrt_header')) {
if (function_exists('aksrt_header_slide')) {
aksrt_header_slide();
}
\AKSRT\WidgetSystem\WidgetAreas::render('all_top_fluid', 'fluid-widget-wrap');
?>
-1
View File
@@ -76,7 +76,6 @@ require_once AKSRT_DIR . '/inc/modules/captcha.php';
require_once AKSRT_DIR . '/inc/modules/user-hub.php';
require_once AKSRT_DIR . '/inc/modules/user-notifications.php';
require_once AKSRT_DIR . '/inc/modules/creator-revenue.php';
require_once AKSRT_DIR . '/inc/modules/widgets-register.php';
// Stage 3: core hooks
require_once AKSRT_DIR . '/inc/core-hooks.php';
+3 -3
View File
@@ -63,7 +63,7 @@ if (!function_exists('aksrt_single_render_aside')) :
// from an otherwise two-column article page.
$has_article_widgets = is_active_sidebar('all_sidebar_top')
|| is_active_sidebar('sidebar-article')
|| is_active_sidebar('single_sidebar')
|| is_active_sidebar('all_sidebar_bottom');
?>
<div class="aksrt-article-sidebar" role="complementary" aria-label="<?php esc_attr_e('侧栏', 'aksrt'); ?>">
@@ -85,8 +85,8 @@ if (!function_exists('aksrt_single_render_aside')) :
<?php if (is_active_sidebar('all_sidebar_top')) : ?>
<?php dynamic_sidebar('all_sidebar_top'); ?>
<?php endif; ?>
<?php if (is_active_sidebar('sidebar-article')) : ?>
<?php dynamic_sidebar('sidebar-article'); ?>
<?php if (is_active_sidebar('single_sidebar')) : ?>
<?php dynamic_sidebar('single_sidebar'); ?>
<?php endif; ?>
<?php if (is_active_sidebar('all_sidebar_bottom')) : ?>
<?php dynamic_sidebar('all_sidebar_bottom'); ?>
+1 -1
View File
@@ -104,7 +104,7 @@ if (!function_exists('aksrt_single_build_context')) :
? aksrt_get_post_show_layout($post_id)
: '';
$has_article_widgets = is_active_sidebar('all_sidebar_top')
|| is_active_sidebar('sidebar-article')
|| is_active_sidebar('single_sidebar')
|| is_active_sidebar('all_sidebar_bottom');
// Do not use wp_is_mobile() as a layout gate. It is User-Agent based and
// can disagree with the actual viewport (for example, desktop-mode mobile
+2 -2
View File
@@ -23,7 +23,7 @@ if (!function_exists('aksrt_resolve_sidebar_id')):
*/
function aksrt_resolve_sidebar_id(): string {
if (is_single()) {
return 'sidebar-article';
return 'single_sidebar';
}
if (is_category()) {
@@ -39,7 +39,7 @@ function aksrt_resolve_sidebar_id(): string {
}
if (is_author()) {
return 'author-sidebar';
return '';
}
if (is_page()) {
+2 -2
View File
@@ -29,7 +29,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
// Fluid zones live outside <main>, once only (removed from header.php).
aksrt_homepage_render_widget_zone(
['all_top_fluid', 'home_top_fluid'],
['home_top_fluid'],
'fluid-widget-wrap aksrt-homepage__fluid-zone aksrt-homepage__fluid-zone--top'
);
?>
@@ -142,7 +142,7 @@ if (!function_exists('aksrt_homepage_render_layout')) {
</main>
<?php
aksrt_homepage_render_widget_zone(
['home_bottom_fluid', 'all_bottom_fluid'],
['home_bottom_fluid'],
'fluid-widget-wrap aksrt-homepage__fluid-zone aksrt-homepage__fluid-zone--bottom'
);
}
-148
View File
@@ -1,148 +0,0 @@
<?php
/**
* Widget placement and area filtering.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
function aksrt_widget_sidebar_area_ids(): array {
return ['home_sidebar', 'sidebar-article', 'all_sidebar_top', 'all_sidebar_bottom', 'cat_sidebar', 'tag_sidebar', 'search_sidebar'];
}
function aksrt_widget_archive_area_ids(): array {
return ['cat_top_fluid', 'cat_top_content', 'cat_bottom_content', 'cat_bottom_fluid', 'cat_sidebar', 'tag_top_fluid', 'tag_top_content', 'tag_bottom_content', 'tag_bottom_fluid', 'tag_sidebar', 'search_top_fluid', 'search_top_content', 'search_bottom_content', 'search_bottom_fluid', 'search_sidebar'];
}
function aksrt_widget_homepage_area_ids(): array {
return ['home_top_fluid', 'home_top_content', 'home_bottom_content', 'home_bottom_fluid'];
}
function aksrt_widget_single_area_ids(): array {
return ['single_top_fluid', 'single_top_content', 'single_bottom_content', 'single_bottom_fluid'];
}
function aksrt_widget_base_from_id(string $widget_id): string {
return preg_replace('/-\d+$/', '', $widget_id) ?: $widget_id;
}
function aksrt_widget_allowed_sidebars(string $widget_base_id): array {
$sidebar_only = ['aksrt_categories', 'aksrt_tag_cloud', 'aksrt_hot_posts'];
$homepage_only = ['aksrt_category_recommend'];
$hybrid = ['aksrt_tab_posts', 'aksrt_category_block', 'aksrt_category_tabs'];
if (in_array($widget_base_id, $sidebar_only, true)) {
return aksrt_widget_sidebar_area_ids();
}
if (in_array($widget_base_id, $homepage_only, true)) {
return aksrt_widget_homepage_area_ids();
}
if (in_array($widget_base_id, $hybrid, true)) {
return array_values(array_unique(array_merge(aksrt_widget_sidebar_area_ids(), aksrt_widget_homepage_area_ids())));
}
if ($widget_base_id === 'aksrt_post_pay_box') {
return ['sidebar-article'];
}
return [];
}
function aksrt_widget_is_allowed_in_sidebar(string $widget_base_id, string $sidebar_id): bool {
if ($widget_base_id === '' || $sidebar_id === '') {
return true;
}
if ($sidebar_id === 'wp_inactive_widgets' || strpos($sidebar_id, 'orphaned_widgets') === 0) {
return true;
}
$allowed = aksrt_widget_allowed_sidebars($widget_base_id);
return empty($allowed) || in_array($sidebar_id, $allowed, true);
}
function aksrt_widget_get_area_context(array $args): string {
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
if (in_array($sidebar_id, aksrt_widget_sidebar_area_ids(), true)) {
return 'sidebar';
}
if (in_array($sidebar_id, aksrt_widget_homepage_area_ids(), true)) {
return 'homepage';
}
if (in_array($sidebar_id, aksrt_widget_archive_area_ids(), true)) {
return 'archive';
}
if (in_array($sidebar_id, aksrt_widget_single_area_ids(), true)) {
return 'single';
}
return 'other';
}
function aksrt_widget_render_location_help(string $scope): void {
$messages = [
'sidebar' => __('仅限侧栏区域:首页侧栏、文章页侧边栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'),
'homepage' => __('仅限首页挂载区域:首页顶部/底部全宽、首页内容上方/下方。', 'aksrt'),
'hybrid' => __('支持侧栏、首页和归档页挂载区域。', 'aksrt'),
'archive' => __('仅限归档页挂载区域:分类页、标签页、搜索页的顶部/底部/侧栏。', 'aksrt'),
];
if (empty($messages[$scope])) {
return;
}
printf('<p style="margin-top:10px;font-size:12px;color:#666;">%s</p>', esc_html($messages[$scope]));
}
function aksrt_filter_widget_display_by_area($instance, WP_Widget $widget, array $args) {
if ($instance === false) {
return false;
}
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
$widget_base_id = isset($widget->id_base) ? (string) $widget->id_base : '';
return aksrt_widget_is_allowed_in_sidebar($widget_base_id, $sidebar_id) ? $instance : false;
}
add_filter('widget_display_callback', 'aksrt_filter_widget_display_by_area', 10, 3);
function aksrt_filter_widget_sidebar_assignments($new_value, $old_value) {
if (!is_array($new_value)) {
return $new_value;
}
$removed = [];
foreach ($new_value as $sidebar_id => $widget_ids) {
if (!is_array($widget_ids) || $sidebar_id === 'wp_inactive_widgets') {
continue;
}
$filtered = [];
foreach ($widget_ids as $widget_id) {
if (!is_string($widget_id) || $widget_id === '') {
continue;
}
$widget_base_id = aksrt_widget_base_from_id($widget_id);
if (aksrt_widget_is_allowed_in_sidebar($widget_base_id, (string) $sidebar_id)) {
$filtered[] = $widget_id;
} else {
$removed[] = $widget_id;
}
}
$new_value[$sidebar_id] = array_values($filtered);
}
if (empty($removed)) {
return $new_value;
}
if (!isset($new_value['wp_inactive_widgets']) || !is_array($new_value['wp_inactive_widgets'])) {
$new_value['wp_inactive_widgets'] = [];
}
$assigned = [];
foreach ($new_value as $widget_ids) {
if (!is_array($widget_ids)) {
continue;
}
foreach ($widget_ids as $widget_id) {
if (is_string($widget_id) && $widget_id !== '') {
$assigned[$widget_id] = true;
}
}
}
foreach (array_values(array_unique($removed)) as $widget_id) {
if (!isset($assigned[$widget_id]) && !in_array($widget_id, $new_value['wp_inactive_widgets'], true)) {
$new_value['wp_inactive_widgets'][] = $widget_id;
}
}
return $new_value;
}
add_filter('pre_update_option_sidebars_widgets', 'aksrt_filter_widget_sidebar_assignments', 10, 2);
-29
View File
@@ -1,29 +0,0 @@
<?php
/** Shared forward widget contract. */
defined('ABSPATH') || exit;
function aksrt_widget_common_defaults(): array {
return class_exists('\\AKSRT\\Widgets\\CommonWidgetFields') ? \AKSRT\Widgets\CommonWidgetFields::defaults() : ['subtitle'=>'','title_link'=>'','display_pc'=>1,'display_mobile'=>1,'sticky'=>0,'animation'=>'none','background'=>'surface','dark_background'=>'surface'];
}
function aksrt_widget_common_normalize(array $instance): array {
if (class_exists('\\AKSRT\\Widgets\\CommonWidgetFields')) return \AKSRT\Widgets\CommonWidgetFields::normalize($instance);
$d = aksrt_widget_common_defaults();
$out = $instance;
$out['subtitle'] = sanitize_text_field((string)($instance['subtitle'] ?? $d['subtitle']));
$out['title_link'] = esc_url_raw((string)($instance['title_link'] ?? ''));
foreach (['display_pc','display_mobile','sticky'] as $key) $out[$key] = !empty($instance[$key]) ? 1 : 0;
$out['animation'] = in_array(($a = sanitize_key((string)($instance['animation'] ?? 'none'))), ['none','fade','slide'], true) ? $a : 'none';
$out['background'] = in_array(($b = sanitize_key((string)($instance['background'] ?? 'surface'))), ['surface','transparent','primary'], true) ? $b : 'surface';
$out['dark_background'] = in_array(($db = sanitize_key((string)($instance['dark_background'] ?? 'surface'))), ['surface','transparent','primary'], true) ? $db : 'surface';
return $out;
}
function aksrt_widget_common_form(WP_Widget $widget, array $instance): void {
$v = aksrt_widget_common_normalize($instance);
$field = static function(string $key) use ($widget): string { return esc_attr($widget->get_field_name($key)); };
echo '<p><label for="' . esc_attr($widget->get_field_id('subtitle')) . '">' . esc_html__('副标题', 'aksrt') . '</label><input class="widefat" id="' . esc_attr($widget->get_field_id('subtitle')) . '" name="' . $field('subtitle') . '" type="text" value="' . esc_attr($v['subtitle']) . '"></p>';
echo '<p><label for="' . esc_attr($widget->get_field_id('title_link')) . '">' . esc_html__('标题链接', 'aksrt') . '</label><input class="widefat" id="' . esc_attr($widget->get_field_id('title_link')) . '" name="' . $field('title_link') . '" type="url" value="' . esc_attr($v['title_link']) . '"></p>';
echo '<p><label><input type="checkbox" name="' . $field('display_pc') . '" value="1" ' . checked($v['display_pc'], 1, false) . '> ' . esc_html__('桌面端显示', 'aksrt') . '</label> <label><input type="checkbox" name="' . $field('display_mobile') . '" value="1" ' . checked($v['display_mobile'], 1, false) . '> ' . esc_html__('移动端显示', 'aksrt') . '</label> <label><input type="checkbox" name="' . $field('sticky') . '" value="1" ' . checked($v['sticky'], 1, false) . '> ' . esc_html__('侧栏吸顶', 'aksrt') . '</label></p>';
echo '<p><label>' . esc_html__('入场动画', 'aksrt') . '<select class="widefat" name="' . $field('animation') . '"><option value="none" ' . selected($v['animation'], 'none', false) . '>None</option><option value="fade" ' . selected($v['animation'], 'fade', false) . '>Fade</option><option value="slide" ' . selected($v['animation'], 'slide', false) . '>Slide</option></select></label></p>';
}
add_filter('widget_update_callback', static function($instance, $new_instance, $old_instance, $widget) { return is_array($instance) ? aksrt_widget_common_normalize($instance) : $instance; }, 10, 4);
add_action('in_widget_form', static function(WP_Widget $widget, &$return, array $instance): void { aksrt_widget_common_form($widget, $instance); }, 20, 3);
-144
View File
@@ -1,144 +0,0 @@
<?php
/**
* Widget registration and placement rules.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
require_once AKSRT_DIR . '/inc/modules/widget-areas.php';
require_once AKSRT_DIR . '/inc/modules/widget-common.php';
/**
* Register custom widgets.
*/
function aksrt_unregister_default_widgets(): void {
unregister_widget('WP_Widget_RSS');
unregister_widget('WP_Widget_Recent_Posts');
unregister_widget('WP_Widget_Recent_Comments');
unregister_widget('WP_Widget_Pages');
unregister_widget('WP_Widget_Search');
unregister_widget('WP_Widget_Calendar');
unregister_widget('WP_Nav_Menu_Widget');
}
add_action('widgets_init', 'aksrt_unregister_default_widgets', 1);
function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\CategoriesWidget');
register_widget('\AKSRT\Widgets\TagCloudWidget');
register_widget('\AKSRT\Widgets\HotPostsWidget');
register_widget('\AKSRT\Widgets\TabPostsWidget');
register_widget('\AKSRT\Widgets\CategoryBlockWidget');
register_widget('\AKSRT\Widgets\CategoryTabsWidget');
register_widget('\AKSRT\Widgets\CategoryRecommendWidget');
// 第一批通用 widget
register_widget('\AKSRT\Widgets\SearchWidget');
register_widget('\AKSRT\Widgets\NoticeWidget');
register_widget('\AKSRT\Widgets\RecentCommentsWidget');
register_widget('\AKSRT\Widgets\IconCardsWidget');
register_widget('\AKSRT\Widgets\ButtonsWidget');
// 公告跑马灯
register_widget('\AKSRT\Widgets\WidgetAnnouncement');
// 用户卡片
register_widget('\AKSRT\Widgets\WidgetUserCard');
// 图文封面卡片
register_widget('\AKSRT\Widgets\WidgetCoverCards');
// 视频播放器
register_widget('\AKSRT\Widgets\WidgetVideoPlayer');
// 一言轮播
register_widget('\AKSRT\Widgets\WidgetYiyan');
// 幻灯片
register_widget('\AKSRT\Widgets\WidgetSlider');
// 其余通用小工具
register_widget('\AKSRT\Widgets\WidgetIframe');
register_widget('\AKSRT\Widgets\WidgetComboInfo');
register_widget('\AKSRT\Widgets\WidgetTextTitle');
register_widget('\AKSRT\Widgets\WidgetTermCards');
register_widget('\AKSRT\Widgets\WidgetPostPayBox');
register_widget('\AKSRT\Widgets\WidgetAuthorCard');
register_widget('\AKSRT\Widgets\WidgetOnelinePosts');
register_widget('\AKSRT\Widgets\WidgetMainPosts');
register_widget('\AKSRT\Widgets\WidgetTermAggregation');
register_widget('\AKSRT\Widgets\WidgetIconCoverCards');
register_widget('\AKSRT\Widgets\PricingPlansWidget');
register_widget('\AKSRT\Widgets\TestimonialsWidget');
register_widget('\AKSRT\Widgets\FaqWidget');
register_widget('\AKSRT\Widgets\StatsWidget');
register_widget('\AKSRT\Widgets\HighlightsWidget');
register_widget('\AKSRT\Widgets\PartnersWidget');
}
add_action('widgets_init', 'aksrt_register_widgets');
/**
* Register a block editor collection for AKSRT widgets.
*/
add_action('enqueue_block_editor_assets', function () {
$screen = get_current_screen();
if (!$screen || $screen->id !== 'widgets') {
return;
}
wp_add_inline_script('wp-blocks', '
document.addEventListener("DOMContentLoaded", function() {
if (typeof wp !== "undefined" && wp.blocks && wp.blocks.registerBlockCollection) {
wp.blocks.registerBlockCollection("aksrt", {
title: "AKSRT 主题小工具",
icon: undefined
});
}
});
');
});
/**
* Assign AKSRT widgets to the AKSRT block category in the widgets editor.
*
* @param array<string, mixed> $metadata Block metadata.
* @return array<string, mixed>
*/
add_filter('block_type_metadata', function ($metadata) {
$aksrt_widgets = [
'aksrt_categories',
'aksrt_tag_cloud',
'aksrt_hot_posts','aksrt_tab_posts','aksrt_category_block','aksrt_category_tabs','aksrt_category_recommend',
'aksrt_search','aksrt_notice','aksrt_recent_comments','aksrt_icon_cards','aksrt_buttons','aksrt_announcement','aksrt_user_card','aksrt_cover_cards','aksrt_video_player','aksrt_yiyan','aksrt_slider','aksrt_iframe','aksrt_combo_info','aksrt_text_title','aksrt_term_cards','aksrt_post_pay_box','aksrt_author_card','aksrt_oneline_posts','aksrt_main_posts','aksrt_term_aggregation','aksrt_icon_cover_cards',
'aksrt_pricing_plans','aksrt_testimonials','aksrt_faq','aksrt_stats','aksrt_highlights','aksrt_partners',
];
if (!empty($metadata['name']) && in_array($metadata['name'], $aksrt_widgets, true)) {
$metadata['category'] = 'aksrt';
}
return $metadata;
});
/**
* Show widget placement hints in the admin form.
*/
add_action('in_widget_form', function (WP_Widget $widget): void {
$map = [
'aksrt_categories' => 'sidebar',
'aksrt_tag_cloud' => 'sidebar',
'aksrt_hot_posts' => 'sidebar',
'aksrt_tab_posts' => 'hybrid',
'aksrt_category_block' => 'hybrid',
'aksrt_category_tabs' => 'hybrid',
'aksrt_category_recommend' => 'homepage',
'aksrt_search'=>'sidebar','aksrt_notice'=>'sidebar','aksrt_recent_comments'=>'sidebar','aksrt_icon_cards'=>'hybrid','aksrt_buttons'=>'hybrid','aksrt_announcement'=>'homepage','aksrt_user_card'=>'sidebar','aksrt_cover_cards'=>'homepage','aksrt_video_player'=>'content','aksrt_yiyan'=>'sidebar','aksrt_slider'=>'homepage','aksrt_iframe'=>'content','aksrt_combo_info'=>'homepage','aksrt_text_title'=>'hybrid','aksrt_term_cards'=>'homepage','aksrt_post_pay_box'=>'content','aksrt_author_card'=>'content','aksrt_oneline_posts'=>'sidebar','aksrt_main_posts'=>'homepage','aksrt_term_aggregation'=>'homepage','aksrt_icon_cover_cards'=>'homepage',
'aksrt_pricing_plans'=>'homepage','aksrt_testimonials'=>'homepage','aksrt_faq'=>'content','aksrt_stats'=>'homepage','aksrt_highlights'=>'homepage','aksrt_partners'=>'homepage',
];
$scope = $map[$widget->id_base] ?? '';
if ($scope !== '') {
aksrt_widget_render_location_help($scope);
}
}, 10, 1);
+1 -2
View File
@@ -36,9 +36,8 @@ while (have_posts()) :
<div id="reading-progress-global" class="reading-progress-bar" role="progressbar" aria-label="<?php esc_attr_e('阅读进度', 'aksrt'); ?>" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>
<?php endif; ?>
<?php if (function_exists('dynamic_sidebar')) : ?>
<?php if (is_active_sidebar('single_top_fluid')) : ?>
<div class="fluid-widget-wrap">
<?php dynamic_sidebar('all_top_fluid'); ?>
<?php dynamic_sidebar('single_top_fluid'); ?>
</div>
<?php endif; ?>
-43
View File
@@ -112,49 +112,6 @@ class ImportExportController extends ControllerBase {
'permission_callback' => [$this, 'check_permission'],
]);
// ── Widget Template Management ──
// Export current widget configuration
register_rest_route(self::API_NAMESPACE, '/widgets/export', [
'methods' => 'GET',
'callback' => [$this, 'export_widgets'],
'permission_callback' => [$this, 'check_permission'],
]);
// Import widget configuration
register_rest_route(self::API_NAMESPACE, '/widgets/import', [
'methods' => 'POST',
'callback' => [$this, 'import_widgets'],
'permission_callback' => [$this, 'check_permission'],
]);
// List saved widget templates
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
'methods' => 'GET',
'callback' => [$this, 'list_widget_templates'],
'permission_callback' => [$this, 'check_permission'],
]);
// Save current layout as a named template
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
'methods' => 'POST',
'callback' => [$this, 'save_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
// Apply a saved template
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)/apply', [
'methods' => 'POST',
'callback' => [$this, 'apply_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
// Delete a saved template
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [$this, 'delete_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
}
/* ------------------------------------------------------------------ */
-171
View File
@@ -144,177 +144,6 @@ trait ImportExportControllerPart1 {
]);
}
/* ------------------------------------------------------------------ */
/* Widget Template Handlers */
/* ------------------------------------------------------------------ */
/**
* GET /widgets/export — Export current widget configuration.
*/
public function export_widgets(\WP_REST_Request $request): \WP_REST_Response {
$sidebars_widgets = get_option('sidebars_widgets', []);
$widget_options = $this->collect_widget_options();
return $this->success([
'data' => [
'version' => AKSRT_VERSION ?? '3.4.0',
'exported' => gmdate('Y-m-d H:i:s'),
'sidebars_widgets' => $sidebars_widgets,
'widget_options' => $widget_options,
],
]);
}
/**
* POST /widgets/import — Import widget configuration.
*/
public function import_widgets(\WP_REST_Request $request): \WP_REST_Response {
$params = $request->get_json_params() ?? [];
$sidebars = $params['sidebars_widgets'] ?? null;
$options = $params['widget_options'] ?? null;
if (!$sidebars || !is_array($sidebars)) {
return $this->error('缺少 sidebars_widgets 数据');
}
// Validate sidebar IDs exist
$valid_sidebars = array_keys(get_registered_sidebars());
$valid_sidebars[] = 'wp_inactive_widgets';
$imported = 0;
foreach ($sidebars as $sidebar_id => $widget_ids) {
if (!in_array($sidebar_id, $valid_sidebars, true) && $sidebar_id !== 'array_version') {
continue;
}
$imported++;
}
// Import widget options if provided
if ($options && is_array($options)) {
foreach ($options as $option_key => $option_value) {
if (preg_match('/^widget_aksrt_/', $option_key)) {
update_option($option_key, $option_value);
}
}
}
// Import sidebar assignments
update_option('sidebars_widgets', $sidebars);
return $this->success(['message' => "已导入 {$imported} 个侧栏配置。"]);
}
/**
* GET /widgets/templates — List saved widget templates.
*/
public function list_widget_templates(\WP_REST_Request $request): \WP_REST_Response {
$templates = get_option('aksrt_widget_templates', []);
return $this->success(['data' => ['templates' => $templates]]);
}
/**
* POST /widgets/templates — Save current layout as a named template.
*/
public function save_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$params = $request->get_json_params() ?? [];
$name = sanitize_text_field((string) ($params['name'] ?? ''));
if (!$name) {
return $this->error('请输入模板名称');
}
$templates = get_option('aksrt_widget_templates', []);
$template = [
'id' => count($templates) + 1,
'name' => $name,
'created_at' => gmdate('Y-m-d H:i:s'),
'sidebars_widgets' => get_option('sidebars_widgets', []),
'widget_options' => $this->collect_widget_options(),
];
$templates[] = $template;
update_option('aksrt_widget_templates', $templates);
return $this->success(['message' => '模板已保存', 'data' => $template]);
}
/**
* POST /widgets/templates/{id}/apply — Apply a saved template.
*/
public function apply_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$templates = get_option('aksrt_widget_templates', []);
$template = null;
foreach ($templates as $t) {
if ((int) ($t['id'] ?? 0) === $id) {
$template = $t;
break;
}
}
if (!$template) {
return $this->error('模板不存在', 404);
}
// Apply widget options
if (!empty($template['widget_options']) && is_array($template['widget_options'])) {
foreach ($template['widget_options'] as $key => $value) {
update_option($key, $value);
}
}
// Apply sidebar assignments
if (!empty($template['sidebars_widgets'])) {
update_option('sidebars_widgets', $template['sidebars_widgets']);
}
return $this->success(['message' => '模板已应用']);
}
/**
* DELETE /widgets/templates/{id} — Delete a saved template.
*/
public function delete_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$templates = get_option('aksrt_widget_templates', []);
$found = false;
$filtered = [];
foreach ($templates as $t) {
if ((int) ($t['id'] ?? 0) === $id) {
$found = true;
continue;
}
$filtered[] = $t;
}
if (!$found) {
return $this->error('模板不存在', 404);
}
update_option('aksrt_widget_templates', $filtered);
return $this->success(['message' => '模板已删除']);
}
/**
* Collect all widget options from the database.
*/
private function collect_widget_options(): array {
global $wpdb;
$rows = $wpdb->get_results(
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'widget_aksrt_%' ORDER BY option_name",
ARRAY_A
);
$options = [];
if ($rows) {
foreach ($rows as $row) {
$value = maybe_unserialize($row['option_value']);
$options[$row['option_name']] = $value;
}
}
return $options;
}
private function persist_setting_backup(array $backup): void {
$backups = get_option('aksrt_settings_backups', []);
+5 -6
View File
@@ -760,12 +760,11 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
}
// 单篇资源专属样式
if ($is_single_resource) {
$enqueue_module('single-post');
$enqueue_module('resource-meta');
$enqueue_module('comments');
$enqueue_module('widgets');
}
if ($is_single_resource) {
$enqueue_module('single-post');
$enqueue_module('resource-meta');
$enqueue_module('comments');
}
// 资源归档专属样式
if ($is_resource_archive) {
+2 -13
View File
@@ -71,13 +71,13 @@ trait ScriptsPart1c {
'aksrt-swiper',
$base_uri . '/assets/css/frontend/swiper-bundle.min.css',
[],
'11.0'
'11.2.10'
);
wp_enqueue_script(
'aksrt-swiper',
$base_uri . '/assets/js/swiper-bundle.min.js',
[],
'11.0',
'11.2.10',
true
);
}
@@ -299,17 +299,6 @@ trait ScriptsPart1c {
);
}
$widget_set_js = get_template_directory() . '/assets/js/widget-set.js';
if (file_exists($widget_set_js)) {
wp_enqueue_script(
'aksrt-widget-set',
$base_uri . '/assets/js/widget-set.js',
['aksrt-script', 'aksrt-qrcode'],
(string) filemtime($widget_set_js),
true
);
}
// ===== 全局 UI 增强(Toast、骨架屏、悬停动效、灯箱、滚动感知)=====
$enhancements_css = get_template_directory() . '/assets/css/frontend/enhancements.css';
if (file_exists($enhancements_css)) {
-2
View File
@@ -8,7 +8,6 @@ namespace AKSRT\Core;
defined('ABSPATH') || exit;
class Setup {
use SetupPart1;
use SetupPart2;
@@ -26,7 +25,6 @@ class Setup {
public function init(): void {
add_action('after_setup_theme', [$this, 'load_textdomain'], 1);
add_action('after_setup_theme', [$this, 'setup_theme']);
add_action('widgets_init', [$this, 'register_sidebars']);
add_filter('dynamic_sidebar_params', [$this, 'filter_sidebar_widget_params']);
add_action('init', [$this, 'register_nav_menus'], 0);
add_action('init', [$this, 'register_post_types'], 1);
-424
View File
@@ -1,424 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
trait SetupPart1 {
public function register_sidebars(): void {
$fluid_wrapper_before = '<div id="%1$s" class="zib-widget %2$s">';
$card_wrapper_before = '<div id="%1$s" class="zib-widget %2$s">';
$title_before = '<h3>';
$title_after = '</h3>';
// ===== 全站 / 首页挂载位(对齐 Zibll 首页骨架) =====
register_sidebar([
'id' => 'all_top_fluid',
'name' => __('全站 - 顶部全宽', 'aksrt'),
'description' => __('显示在所有页面内容区上方的全宽挂载位。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_bottom_fluid',
'name' => __('全站 - 底部全宽', 'aksrt'),
'description' => __('显示在所有页面页脚上方的全宽挂载位。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_sidebar_top',
'name' => __('全站 - 侧栏顶部', 'aksrt'),
'description' => __('显示在全站侧栏顶部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_sidebar_bottom',
'name' => __('全站 - 侧栏底部', 'aksrt'),
'description' => __('显示在全站侧栏底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_footer',
'name' => __('全站 - 页脚小工具', 'aksrt'),
'description' => __('显示在页脚内部的统一小工具区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'mobile_nav_fluid',
'name' => __('移动端 - 菜单底部', 'aksrt'),
'description' => __('显示在移动端抽屉菜单底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_top_fluid',
'name' => __('首页 - 顶部全宽', 'aksrt'),
'description' => __('显示在首页主内容上方的全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_top_content',
'name' => __('首页 - 内容上方', 'aksrt'),
'description' => __('显示在首页主内容区顶部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_bottom_content',
'name' => __('首页 - 内容下方', 'aksrt'),
'description' => __('显示在首页主内容区底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_bottom_fluid',
'name' => __('首页 - 底部全宽', 'aksrt'),
'description' => __('显示在首页页脚上方的全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_sidebar',
'name' => __('首页 - 侧边栏', 'aksrt'),
'description' => __('显示在首页右侧栏。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// 文章详情页侧栏
register_sidebar([
'id' => 'sidebar-article',
'name' => __('文章页 - 侧边栏', 'aksrt'),
'description' => __('显示在文章详情页右侧。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 文章页 Widget 区域(模板zibll 多位置) =====
register_sidebar([
'id' => 'single_top_fluid',
'name' => __('文章页 - 顶部全宽', 'aksrt'),
'description' => __('文章页顶部全宽区域(在封面/面包屑之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_top_content',
'name' => __('文章页 - 内容上方', 'aksrt'),
'description' => __('文章主体卡片上方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_bottom_content',
'name' => __('文章页 - 内容下方', 'aksrt'),
'description' => __('评论区下方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_bottom_fluid',
'name' => __('文章页 - 底部全宽', 'aksrt'),
'description' => __('文章页底部全宽区域(在页脚之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 分类页 Widget 区域 =====
register_sidebar([
'id' => 'cat_top_fluid',
'name' => __('分类页 - 顶部全宽', 'aksrt'),
'description' => __('分类归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_top_content',
'name' => __('分类页 - 内容上方', 'aksrt'),
'description' => __('分类归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_bottom_content',
'name' => __('分类页 - 内容下方', 'aksrt'),
'description' => __('分类归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_bottom_fluid',
'name' => __('分类页 - 底部全宽', 'aksrt'),
'description' => __('分类归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_sidebar',
'name' => __('分类页 - 侧栏', 'aksrt'),
'description' => __('分类归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 标签页 Widget 区域 =====
register_sidebar([
'id' => 'tag_top_fluid',
'name' => __('标签页 - 顶部全宽', 'aksrt'),
'description' => __('标签归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_top_content',
'name' => __('标签页 - 内容上方', 'aksrt'),
'description' => __('标签归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_bottom_content',
'name' => __('标签页 - 内容下方', 'aksrt'),
'description' => __('标签归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_bottom_fluid',
'name' => __('标签页 - 底部全宽', 'aksrt'),
'description' => __('标签归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_sidebar',
'name' => __('标签页 - 侧栏', 'aksrt'),
'description' => __('标签归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 搜索页 Widget 区域 =====
register_sidebar([
'id' => 'search_top_fluid',
'name' => __('搜索页 - 顶部全宽', 'aksrt'),
'description' => __('搜索结果页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_top_content',
'name' => __('搜索页 - 内容上方', 'aksrt'),
'description' => __('搜索结果页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_bottom_content',
'name' => __('搜索页 - 内容下方', 'aksrt'),
'description' => __('搜索结果页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_bottom_fluid',
'name' => __('搜索页 - 底部全宽', 'aksrt'),
'description' => __('搜索结果页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_sidebar',
'name' => __('搜索页 - 侧栏', 'aksrt'),
'description' => __('搜索结果页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'archive_bottom_fluid',
'name' => __('归档页 - 底部全宽', 'aksrt'),
'description' => __('分类/标签/归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'author-sidebar',
'name' => __('作者页 - 侧栏', 'aksrt'),
'description' => __('作者归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ── Per-page dynamic sidebars (zibll zib_page_register_sidebar parity) ──
// Pages that opt in via meta `widgets_register=1` + `widgets_register_container`
// (a serialized list of area keys) get page_{key}_{ID} sidebars registered, and
// the matching `page_sidebar_{ID}` is treated as active when any of its areas
// hold widgets. page.php reads page_top_fluid_/page_top_content_/etc.
$this->register_page_sidebars();
}
/**
* Register dynamic per-page widget areas (aligns Zibll zib_page_register_sidebar).
*
* Zibll stores, per page, a `widgets_register` flag and a `widgets_register_container`
* serialized array of area keys (sidebar/top_fluid/top_content/bottom_content/bottom_fluid).
* We replicate that: for every page opted in, register page_{key}_{ID} sidebars.
*/
public function register_page_sidebars(): void {
global $wpdb;
// Cache the opt-in page list (postmeta-driven), mirroring zib_get_widgets_register_pages.
$pages = wp_cache_get('aksrt_widgets_register_pages', 'aksrt_cache_group', true);
if (false === $pages) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
$rows = $wpdb->get_results(
"SELECT p.ID, p.post_title, pm.meta_value
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
WHERE p.post_type = 'page'
AND p.post_status = 'publish'
AND pm.meta_key = 'widgets_register_container'
AND pm.meta_value LIKE '%;}'"
);
$pages = is_array($rows) ? $rows : [];
wp_cache_set('aksrt_widgets_register_pages', $pages, 'aksrt_cache_group');
}
$area_labels = [
'sidebar' => __('侧边栏', 'aksrt'),
'top_fluid' => __('顶部全宽度', 'aksrt'),
'top_content' => __('主内容上面', 'aksrt'),
'bottom_content' => __('主内容下面', 'aksrt'),
'bottom_fluid' => __('底部全宽度', 'aksrt'),
];
$fluid_wrapper_before = '<div id="%1$s" class="zib-widget %2$s">';
$card_wrapper_before = '<div id="%1$s" class="zib-widget %2$s">';
$title_before = '<h3>';
$title_after = '</h3>';
foreach ($pages as $page) {
$meta_value = maybe_unserialize($page->meta_value);
if (!is_array($meta_value)) {
continue;
}
$name = function_exists('aksrt_str_cut') ? aksrt_str_cut($page->post_title, 0, 10, '...') : mb_substr((string) $page->post_title, 0, 10);
foreach ($meta_value as $key) {
if (!isset($area_labels[$key])) {
continue;
}
$id = 'page_' . $key . '_' . (int) $page->ID;
register_sidebar([
'name' => sprintf(__('[页面: %1$s] - %2$s', 'aksrt'), $name, $area_labels[$key]),
'id' => $id,
'description' => sprintf(__('显示在[页面:%1$s]的 %2$s 位置', 'aksrt'), $name, $area_labels[$key]),
'before_widget' => $key === 'sidebar' ? $card_wrapper_before : $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
}
}
}
}
-351
View File
@@ -1,351 +0,0 @@
<?php
/**
* Public widget list REST endpoints.
*
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
class WidgetAjax {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
$routes = [
'/widgets/posts' => 'rest_main_posts',
'/widgets/tab-posts' => 'rest_tab_posts',
'/widgets/category-tabs' => 'rest_category_tabs',
'/widgets/category-block' => 'rest_category_block',
'/widgets/category-recommend' => 'rest_category_recommend',
'/widgets/hot-posts' => 'rest_hot_posts',
'/widgets/oneline-posts' => 'rest_oneline_posts',
'/widgets/recent-comments' => 'rest_recent_comments',
'/widgets/tag-cloud' => 'rest_tag_cloud',
'/widgets/categories' => 'rest_categories',
'/widgets/term-cards' => 'rest_term_cards',
'/widgets/term-aggregation' => 'rest_term_aggregation',
];
foreach ($routes as $path => $cb) {
register_rest_route('aksrt/v1', $path, [
'methods' => 'GET',
'callback' => [$this, $cb],
'permission_callback' => '__return_true',
]);
}
}
public function rest_main_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(30, (int) ($request->get_param('count') ?? 6))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'list')),
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'show_meta' => $request->get_param('show_meta') === null ? 1 : (int) $request->get_param('show_meta'),
'show_number' => (int) ($request->get_param('show_number') ?? 0),
'paginate' => in_array(sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), ['none','ajax','number'], true) ? sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')) : 'ajax',
];
$page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1));
return $this->payload(\AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page), $page);
}
public function rest_tab_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TabPostsWidget')) {
return $this->fail();
}
$default_count = max(1, min(20, (int) ($request->get_param('count') ?? 5)));
$style = sanitize_key((string) ($request->get_param('style') ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($request->get_param('paginate') ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$page = max(1, (int) ($request->get_param('page') ?? 1));
$tab = [
'title' => '',
'category_id' => max(0, (int) ($request->get_param('cat') ?? $request->get_param('category_id') ?? 0)),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'count' => max(0, min(20, (int) ($request->get_param('tab_count') ?? 0))),
'enabled' => true,
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),];
$opts = [
'style' => $style,
'paginate' => $paginate,
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'show_meta' => $request->get_param('show_meta') === null ? 1 : (int) $request->get_param('show_meta'),
'show_number' => (int) ($request->get_param('show_number') ?? 0),
];
$payload = \AKSRT\Widgets\TabPostsWidget::render_tab_payload($tab, $default_count, $opts, $page);
$html = (string) ($payload['html'] ?? '');
if ($html === '') {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
} else {
$html = '<div class="widget-tab-posts__list widget-tab-posts__list--' . esc_attr($style) . ' aksrt-main-posts style-' . esc_attr($style) . '">' . $html . '</div>';
}
return new \WP_REST_Response([
'success' => true,
'html' => $html,
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
}
public function rest_category_tabs(\WP_REST_Request $request) {
$cat = max(0, (int) ($request->get_param('cat') ?? 0));
$count = max(1, min(12, (int) ($request->get_param('count') ?? 6)));
$layout = sanitize_key((string) ($request->get_param('layout') ?? 'card'));
$columns = max(1, min(4, (int) ($request->get_param('columns') ?? 3)));
$context = sanitize_key((string) ($request->get_param('context') ?? 'homepage'));
if ($cat <= 0) {
return new \WP_REST_Response(['success' => true, 'html' => ''], 200);
}
$post_types = function_exists('aksrt_home_widget_post_types') ? aksrt_home_widget_post_types() : ['post'];
$query = new \WP_Query([
'post_type' => $post_types,
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'cat' => $cat,
]);
ob_start();
if (function_exists('aksrt_home_widget_render_post_grid')) {
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
} elseif ($query->have_posts()) {
if (function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini">';
aksrt_render_mini_list($query, ['context' => 'widget', 'show_thumb' => true]);
echo '</div>';
} else {
echo '<ul class="widget-tab-posts__list">';
while ($query->have_posts()) {
$query->the_post();
echo '<li><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></li>';
}
echo '</ul>';
}
} else {
echo function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
wp_reset_postdata();
$html = (string) ob_get_clean();
if ($html === '') {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_hot_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\HotPostsWidget')) {
return $this->fail();
}
$paginate = sanitize_key((string) ($request->get_param('paginate') ?? 'ajax'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'ajax';
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'order_by' => sanitize_key((string) ($request->get_param('order_by') ?? 'views')),
'days' => max(0, (int) ($request->get_param('days') ?? 30)),
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'target_blank'=> (int) ($request->get_param('target_blank') ?? 0),
'paginate' => $paginate,
];
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\HotPostsWidget::query_payload($instance, $page), $page);
}
public function rest_oneline_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetOnelinePosts')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 8))),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'card')),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),
];
$html = \AKSRT\Widgets\WidgetOnelinePosts::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_recent_comments(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\RecentCommentsWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'show_avatar' => $request->get_param('show_avatar') === null ? 1 : (int) $request->get_param('show_avatar'),
'outpost' => sanitize_text_field((string) ($request->get_param('outpost') ?? '')),
'outer' => sanitize_text_field((string) ($request->get_param('outer') ?? '')),
];
$html = \AKSRT\Widgets\RecentCommentsWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_category_block(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryBlockWidget')) {
return $this->fail();
}
$instance = [
'category_id' => max(0, (int) ($request->get_param('cat') ?? $request->get_param('category_id') ?? 0)),
'count' => max(1, min(12, (int) ($request->get_param('count') ?? 6))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 3))),
'layout' => sanitize_key((string) ($request->get_param('layout') ?? 'card')),
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$context = sanitize_key((string) ($request->get_param('context') ?? 'homepage'));
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\CategoryBlockWidget::query_payload($instance, $page, $context), $page);
}
public function rest_category_recommend(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryRecommendWidget')) {
return $this->fail();
}
$raw_ids = $request->get_param('category_ids');
if (is_string($raw_ids)) {
$raw_ids = array_filter(array_map('intval', preg_split('/[\s,]+/', $raw_ids) ?: []));
}
$instance = [
'category_ids' => is_array($raw_ids) ? $raw_ids : [],
'posts_per_category' => max(1, min(8, (int) ($request->get_param('posts_per_category') ?? 5))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 2))),
'show_views' => (int) ($request->get_param('show_views') ?? 0),
'show_description' => $request->get_param('show_description') === null ? 1 : (int) $request->get_param('show_description'),
'show_dates' => $request->get_param('show_dates') === null ? 1 : (int) $request->get_param('show_dates'),
'show_more' => $request->get_param('show_more') === null ? 1 : (int) $request->get_param('show_more'),
];
$html = \AKSRT\Widgets\CategoryRecommendWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_tag_cloud(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TagCloudWidget')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'post_tag')),
'count' => max(1, min(80, (int) ($request->get_param('count') ?? 15))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'color' => sanitize_html_class((string) ($request->get_param('color') ?? 'rand')),
'show_count' => (int) ($request->get_param('show_count') ?? 0),
'blank' => (int) ($request->get_param('blank') ?? 0),
'fixed_width' => (int) ($request->get_param('fixed_width') ?? 0),
'hide_empty' => (int) ($request->get_param('hide_empty') ?? 0),
];
$html = \AKSRT\Widgets\TagCloudWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_categories(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoriesWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(50, (int) ($request->get_param('count') ?? 8))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')),
'parent' => max(0, (int) ($request->get_param('parent') ?? 0)),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'exclude' => sanitize_text_field((string) ($request->get_param('exclude') ?? '')),
'show_count' => $request->get_param('show_count') === null ? 1 : (int) $request->get_param('show_count'),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'hierarchical' => (int) ($request->get_param('hierarchical') ?? 0),
];
$html = \AKSRT\Widgets\CategoriesWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_cards(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermCards')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'number' => max(1, min(30, (int) ($request->get_param('number') ?? 8))),
'pc_row' => max(1, min(12, (int) ($request->get_param('pc_row') ?? $request->get_param('columns') ?? 2))),
'm_row' => max(1, min(12, (int) ($request->get_param('m_row') ?? 1))),
'columns' => max(1, min(12, (int) ($request->get_param('columns') ?? $request->get_param('pc_row') ?? 2))),
'type' => sanitize_key((string) ($request->get_param('type') ?? 'style-1')),
'height_scale' => max(20, min(300, (int) ($request->get_param('height_scale') ?? 60))),
'mask_opacity' => max(0, min(90, (int) ($request->get_param('mask_opacity') ?? 10))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'target_blank' => (int) ($request->get_param('target_blank') ?? 0),
];
$html = \AKSRT\Widgets\WidgetTermCards::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_aggregation(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermAggregation')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'term_ids' => sanitize_text_field((string) ($request->get_param('term_ids') ?? '')),
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 4))),
'columns' => max(1, min(3, (int) ($request->get_param('columns') ?? 1))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'auto_terms' => max(1, min(4, (int) ($request->get_param('auto_terms') ?? 2))),
'target_blank' => (int) ($request->get_param('target_blank') ?? 0),
];
$html = \AKSRT\Widgets\WidgetTermAggregation::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
private function fail(): \WP_REST_Response {
return new \WP_REST_Response(['success' => false, 'code' => 'widget_unavailable', 'message' => __('小工具服务暂不可用。', 'aksrt'), 'data' => ['items' => [], 'page' => 1, 'max_pages' => 1, 'total' => 0]], 500);
}
private function payload(array $payload, int $page): \WP_REST_Response {
return new \WP_REST_Response([
'success' => true,
'code' => 'ok',
'html' => (string) ($payload['html'] ?? ''),
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
}
}
+4 -1
View File
@@ -19,18 +19,21 @@ final class AdminFormController {
public static function enqueue(): void {
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
if (!$screen || !in_array($screen->id, ['widgets', 'customize'], true)) {
$is_customizer = current_filter() === 'customize_controls_enqueue_scripts';
if (!$is_customizer && (!$screen || !in_array($screen->id, ['widgets', 'customize'], true))) {
return;
}
wp_enqueue_media();
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('jquery-ui-sortable');
wp_enqueue_style('aksrt-widget-admin', AKSRT_URI . '/assets/widgets/admin.css', ['wp-color-picker'], AKSRT_VERSION);
wp_enqueue_style('aksrt-widget-preview', AKSRT_URI . '/assets/widgets/frontend.css', [], AKSRT_VERSION);
wp_enqueue_script('aksrt-widget-admin', AKSRT_URI . '/assets/widgets/admin.js', ['jquery', 'jquery-ui-sortable', 'wp-color-picker'], AKSRT_VERSION, true);
wp_localize_script('aksrt-widget-admin', 'aksrtWidgetAdmin', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'action' => self::AJAX_ACTION,
'nonce' => wp_create_nonce(self::NONCE_ACTION),
'transferNonce' => wp_create_nonce('aksrt_widgets_transfer'),
'i18n' => [
'loading' => __('正在加载模块配置…', 'aksrt'),
'error' => __('加载失败,请重试。', 'aksrt'),
+17 -9
View File
@@ -9,6 +9,15 @@ final class AssetManager {
private static bool $registered = false;
private static bool $configured = false;
/** Enqueue before wp_head whenever at least one new widget is assigned. */
public static function enqueue_active(): void {
foreach (WidgetRegistry::all() as $definition) {
if (is_active_widget(false, false, $definition->id_base(), true)) {
self::enqueue_for($definition);
}
}
}
public static function enqueue_for(WidgetDefinition $definition): void {
self::register();
foreach ($definition->assets() as $asset) {
@@ -42,17 +51,17 @@ final class AssetManager {
wp_register_style('aksrt-widgets', $uri . '/assets/widgets/frontend.css', [], is_file($css) ? (string) filemtime($css) : null);
wp_register_script('aksrt-widgets', $uri . '/assets/widgets/frontend.js', [], is_file($js) ? (string) filemtime($js) : null, true);
if (!wp_style_is('aksrt-swiper', 'registered')) {
wp_register_style('aksrt-swiper', $uri . '/assets/css/frontend/swiper-bundle.min.css', [], '11.0');
wp_register_style('aksrt-swiper', $uri . '/assets/css/frontend/swiper-bundle.min.css', [], '11.2.10');
}
if (!wp_script_is('aksrt-swiper', 'registered')) {
wp_register_script('aksrt-swiper', $uri . '/assets/js/swiper-bundle.min.js', [], '11.0', true);
wp_register_script('aksrt-swiper', $uri . '/assets/js/swiper-bundle.min.js', [], '11.2.10', true);
}
wp_register_style('aksrt-widget-dplayer', 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.css', [], '1.27.1');
wp_register_script('aksrt-widget-hls', 'https://cdn.jsdelivr.net/npm/hls.js@1.5.18/dist/hls.min.js', [], '1.5.18', true);
wp_register_script('aksrt-widget-flv', 'https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js', [], '1.6.2', true);
wp_register_script('aksrt-widget-dash', 'https://cdn.jsdelivr.net/npm/dashjs@4.7.4/dist/dash.all.min.js', [], '4.7.4', true);
wp_register_script('aksrt-widget-dplayer', 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js', ['aksrt-widget-hls', 'aksrt-widget-flv', 'aksrt-widget-dash'], '1.27.1', true);
wp_register_script('aksrt-widget-tilt', 'https://cdn.jsdelivr.net/npm/vanilla-tilt@1.8.1/dist/vanilla-tilt.min.js', [], '1.8.1', true);
$third_party = $uri . '/assets/widgets/third-party/';
wp_register_script('aksrt-widget-hls', $third_party . 'hls.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-flv', $third_party . 'flv.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-dash', $third_party . 'dash.all.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-dplayer', $third_party . 'DPlayer.min.js', ['aksrt-widget-hls', 'aksrt-widget-flv', 'aksrt-widget-dash'], 'reference-pinned', true);
wp_register_script('aksrt-widget-tilt', $third_party . 'vanilla-tilt.min.js', [], 'reference-pinned', true);
self::$registered = true;
}
@@ -61,7 +70,6 @@ final class AssetManager {
wp_enqueue_style('aksrt-swiper');
wp_enqueue_script('aksrt-swiper');
} elseif ($asset === 'dplayer') {
wp_enqueue_style('aksrt-widget-dplayer');
wp_enqueue_script('aksrt-widget-dplayer');
} elseif (in_array($asset, ['hls', 'flv', 'dash'], true)) {
wp_enqueue_script('aksrt-widget-' . $asset);
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/**
* Single bootstrap for the schema-driven widget runtime.
*/
final class Runtime {
private static bool $registered = false;
public static function init(): void {
if (self::$registered) {
return;
}
self::$registered = true;
add_action('widgets_init', [self::class, 'unregister_core_widgets'], 1);
add_action('widgets_init', [WidgetAreas::class, 'register'], 5);
add_action('widgets_init', [self::class, 'register_widgets'], 10);
add_action('save_post_page', [WidgetAreas::class, 'flush_page_cache']);
add_action('rest_api_init', static function (): void {
(new RestController())->register_routes();
});
add_action('wp_enqueue_scripts', [AssetManager::class, 'enqueue_active'], 20);
add_action('in_widget_form', [self::class, 'render_placement_hint'], 20, 1);
AdminFormController::register_hooks();
TransferController::register_hooks();
}
public static function unregister_core_widgets(): void {
foreach ([
'WP_Widget_RSS',
'WP_Widget_Recent_Posts',
'WP_Widget_Recent_Comments',
'WP_Widget_Pages',
'WP_Widget_Search',
'WP_Widget_Calendar',
] as $class) {
unregister_widget($class);
}
}
public static function register_widgets(): void {
foreach (WidgetRegistry::all() as $definition) {
register_widget(new SchemaWidget($definition));
}
}
public static function render_placement_hint(\WP_Widget $widget): void {
if (!$widget instanceof SchemaWidget) {
return;
}
$definition = $widget->definition();
$messages = [
'wide' => __('宽版模块:建议放入全宽或主内容区域;放入侧栏时会显示尺寸警告。', 'aksrt'),
'sidebar' => __('侧栏模块:建议放入侧栏区域;放入全宽区域时会显示尺寸警告。', 'aksrt'),
'any' => __('自适应模块:可放入主内容、全宽或侧栏区域。', 'aksrt'),
];
printf(
'<p class="aksrt-widget-placement-hint" data-widget-size="%1$s"><span class="dashicons dashicons-info-outline" aria-hidden="true"></span>%2$s</p>',
esc_attr($definition->size()),
esc_html($messages[$definition->size()] ?? $messages['any'])
);
}
}
+3
View File
@@ -56,6 +56,9 @@ final class SchemaWidget extends \WP_Widget {
if (wp_doing_ajax() && isset($_REQUEST['action']) && $_REQUEST['action'] === AdminFormController::AJAX_ACTION) {
return false;
}
if (function_exists('is_customize_preview') && is_customize_preview()) {
return true;
}
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
return $screen && in_array($screen->id, ['widgets', 'customize'], true);
}
+153
View File
@@ -0,0 +1,153 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Admin-only import, export, template application and preview. */
final class TransferController {
private const NONCE = 'aksrt_widgets_transfer';
public static function register_hooks(): void {
add_action('admin_notices', [self::class, 'render_panel']);
add_action('admin_post_aksrt_widgets_export', [self::class, 'handle_export']);
add_action('admin_post_aksrt_widgets_import', [self::class, 'handle_import']);
add_action('admin_post_aksrt_widgets_template', [self::class, 'handle_template']);
add_action('wp_ajax_aksrt_widgets_template_preview', [self::class, 'ajax_preview']);
}
public static function render_panel(): void {
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
if (!$screen || $screen->id !== 'widgets' || !current_user_can('edit_theme_options')) {
return;
}
$sidebars = $GLOBALS['wp_registered_sidebars'] ?? [];
$templates = WidgetTransfer::templates();
$notice = isset($_GET['aksrt_widgets_notice']) ? sanitize_key(wp_unslash($_GET['aksrt_widgets_notice'])) : '';
if ($notice !== '') {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('小工具配置已更新。', 'aksrt') . '</p></div>';
}
?>
<div class="aksrt-widget-transfer postbox">
<div class="postbox-header"><h2><?php esc_html_e('AKSRT 小工具模板与导入导出', 'aksrt'); ?></h2></div>
<div class="inside aksrt-widget-transfer__grid">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('导出', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_export">
<?php wp_nonce_field(self::NONCE); ?>
<?php self::sidebar_select($sidebars, 'sidebar', true); ?>
<?php submit_button(__('下载 aksrt.widgets.v1', 'aksrt'), 'secondary', 'submit', false); ?>
</form>
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('导入', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_import">
<?php wp_nonce_field(self::NONCE); ?>
<input type="file" name="widgets_file" accept="application/json,.json" required>
<?php self::mode_select(); ?>
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('沿用文件中的区域', 'aksrt')); ?>
<?php submit_button(__('导入配置', 'aksrt'), 'secondary', 'submit', false); ?>
</form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('内置模板', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_template">
<?php wp_nonce_field(self::NONCE); ?>
<select name="template" class="aksrt-widget-template-select">
<?php foreach ($templates as $key => $template) : ?>
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html((string) $template['label']); ?></option>
<?php endforeach; ?>
</select>
<?php self::mode_select(); ?>
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('使用模板默认区域', 'aksrt')); ?>
<div class="aksrt-widget-transfer__actions">
<button type="button" class="button aksrt-widget-template-preview"><?php esc_html_e('预览模块', 'aksrt'); ?></button>
<?php submit_button(__('应用模板', 'aksrt'), 'primary', 'submit', false); ?>
</div>
</form>
<div class="aksrt-widget-template-preview-output" hidden></div>
</div>
</div>
<?php
}
public static function handle_export(): void {
self::authorize();
$sidebar = isset($_POST['sidebar']) ? sanitize_key(wp_unslash($_POST['sidebar'])) : '';
$payload = WidgetTransfer::export($sidebar);
nocache_headers();
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="aksrt-widgets-' . gmdate('Ymd-His') . '.json"');
echo wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
public static function handle_import(): void {
self::authorize();
$file = $_FILES['widgets_file'] ?? null;
if (!is_array($file) || !empty($file['error']) || empty($file['tmp_name']) || (int) ($file['size'] ?? 0) > 2 * MB_IN_BYTES) {
wp_die(esc_html__('导入文件无效或超过 2MB。', 'aksrt'));
}
$payload = json_decode((string) file_get_contents((string) $file['tmp_name']), true);
if (!is_array($payload)) {
wp_die(esc_html__('导入文件不是有效 JSON。', 'aksrt'));
}
self::run_import($payload);
}
public static function handle_template(): void {
self::authorize();
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
try {
$payload = WidgetTransfer::template_payload($template);
} catch (\InvalidArgumentException $error) {
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
}
self::run_import($payload);
}
public static function ajax_preview(): void {
check_ajax_referer(self::NONCE, 'nonce');
if (!current_user_can('edit_theme_options')) {
wp_send_json_error(['message' => __('权限不足。', 'aksrt')], 403);
}
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
try {
wp_send_json_success(['html' => WidgetTransfer::preview($template)]);
} catch (\InvalidArgumentException $error) {
wp_send_json_error(['message' => $error->getMessage()], 400);
}
}
private static function authorize(): void {
if (!current_user_can('edit_theme_options')) {
wp_die(esc_html__('权限不足。', 'aksrt'), '', ['response' => 403]);
}
check_admin_referer(self::NONCE);
}
private static function run_import(array $payload): void {
$mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : 'append';
$target = isset($_POST['target_sidebar']) ? sanitize_key(wp_unslash($_POST['target_sidebar'])) : '';
try {
WidgetTransfer::import($payload, $mode, $target);
} catch (\InvalidArgumentException $error) {
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
}
wp_safe_redirect(add_query_arg('aksrt_widgets_notice', 'updated', admin_url('widgets.php')));
exit;
}
private static function sidebar_select(array $sidebars, string $name, bool $allow_empty, string $empty_label = ''): void {
echo '<label><span class="screen-reader-text">' . esc_html__('小工具区域', 'aksrt') . '</span><select name="' . esc_attr($name) . '">';
if ($allow_empty) {
echo '<option value="">' . esc_html($empty_label !== '' ? $empty_label : __('全部区域', 'aksrt')) . '</option>';
}
foreach ($sidebars as $id => $sidebar) {
echo '<option value="' . esc_attr((string) $id) . '">' . esc_html((string) ($sidebar['name'] ?? $id)) . '</option>';
}
echo '</select></label>';
}
private static function mode_select(): void {
echo '<select name="mode"><option value="append">' . esc_html__('追加', 'aksrt') . '</option><option value="replace">' . esc_html__('替换区域', 'aksrt') . '</option></select>';
}
}
+146
View File
@@ -0,0 +1,146 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Canonical 40 static areas plus five opt-in areas for each page. */
final class WidgetAreas {
private const PAGE_CACHE_KEY = 'aksrt_widget_pages_v1';
/** @return array<string, array{0:string,1:string,2:string}> */
public static function definitions(): array {
$areas = [
'all_top_fluid' => ['所有页面 - 顶部全宽', '显示在所有知识内容页面顶部。', 'fluid'],
'all_bottom_fluid' => ['所有页面 - 底部全宽', '显示在所有知识内容页面底部。', 'fluid'],
'all_sidebar_top' => ['所有页面 - 侧栏顶部', '显示在页面侧栏最上方。', 'sidebar'],
'all_sidebar_bottom' => ['所有页面 - 侧栏底部', '显示在页面侧栏最下方。', 'sidebar'],
];
$page_names = [
'home' => '首页',
'single' => '文章页',
'cat' => '分类页',
'tag' => '标签页',
'search' => '搜索页',
];
$positions = [
'top_fluid' => ['顶部全宽', 'fluid'],
'top_content' => ['主内容上面', 'content'],
'bottom_content' => ['主内容下面', 'content'],
'bottom_fluid' => ['底部全宽', 'fluid'],
'sidebar' => ['侧边栏', 'sidebar'],
];
foreach ($page_names as $prefix => $page_name) {
foreach ($positions as $suffix => [$position, $kind]) {
$areas[$prefix . '_' . $suffix] = [
$page_name . ' - ' . $position,
sprintf('显示在%s的%s位置。', $page_name, $position),
$kind,
];
}
}
foreach (['author' => '作者页', 'msg' => '消息中心'] as $prefix => $page_name) {
$areas[$prefix . '_top_fluid'] = [$page_name . ' - 顶部全宽', '显示在' . $page_name . '内容顶部。', 'fluid'];
$areas[$prefix . '_bottom_fluid'] = [$page_name . ' - 底部全宽', '显示在' . $page_name . '内容底部。', 'fluid'];
}
$areas['user_top_fluid'] = ['用户中心 - 顶部全宽', '显示在用户中心内容顶部。', 'fluid'];
$areas['user_top_content'] = ['用户中心 - 内容区上面', '显示在用户中心主内容上面。', 'content'];
$areas['user_bottom_fluid'] = ['用户中心 - 底部全宽', '显示在用户中心内容底部。', 'fluid'];
$areas['newposts_sidebar_top'] = ['前台投稿 - 侧栏顶部', '显示在前台投稿侧栏顶部,并在移动端保留。', 'sidebar'];
$areas['newposts_sidebar_bottom'] = ['前台投稿 - 侧栏底部', '显示在前台投稿侧栏底部,并在移动端保留。', 'sidebar'];
$areas['all_footer'] = ['页脚内部', '显示在站点页脚内部。', 'content'];
$areas['mobile_nav_fluid'] = ['移动菜单 - 底部', '显示在移动端抽屉菜单底部。', 'sidebar'];
return $areas;
}
/** @return string[] */
public static function static_ids(): array {
return array_keys(self::definitions());
}
public static function register(): void {
foreach (self::definitions() as $id => [$name, $description, $kind]) {
self::register_area($id, $name, $description, $kind);
}
self::register_page_areas();
}
public static function render(string $id, string $class = ''): bool {
if (!is_active_sidebar($id)) {
return false;
}
$class = trim('aksrt-widget-area aksrt-widget-area--' . sanitize_html_class($id) . ' ' . $class);
echo '<div class="' . esc_attr($class) . '" data-widget-area="' . esc_attr($id) . '">';
dynamic_sidebar($id);
echo '</div>';
return true;
}
public static function flush_page_cache(): void {
wp_cache_delete(self::PAGE_CACHE_KEY, 'aksrt');
}
private static function register_page_areas(): void {
$page_ids = wp_cache_get(self::PAGE_CACHE_KEY, 'aksrt');
if (!is_array($page_ids)) {
$page_ids = get_posts([
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'orderby' => 'date',
'order' => 'ASC',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'meta_query' => [
[
'key' => 'widgets_register',
'value' => '1',
],
],
]);
$page_ids = array_map('absint', $page_ids);
wp_cache_set(self::PAGE_CACHE_KEY, $page_ids, 'aksrt');
}
$positions = [
'sidebar' => ['侧边栏', 'sidebar'],
'top_fluid' => ['顶部全宽', 'fluid'],
'top_content' => ['主内容上面', 'content'],
'bottom_content' => ['主内容下面', 'content'],
'bottom_fluid' => ['底部全宽', 'fluid'],
];
foreach ($page_ids as $page_id) {
$enabled = get_post_meta($page_id, 'widgets_register_container', true);
if (!is_array($enabled)) {
continue;
}
$title = wp_html_excerpt(get_the_title($page_id), 12, '…');
foreach (array_values(array_unique(array_map('sanitize_key', $enabled))) as $position) {
if (!isset($positions[$position])) {
continue;
}
[$label, $kind] = $positions[$position];
self::register_area(
'page_' . $position . '_' . $page_id,
sprintf('[页面:%1$s] - %2$s', $title, $label),
sprintf('显示在页面“%1$s”的%2$s位置。', $title, $label),
$kind
);
}
}
}
private static function register_area(string $id, string $name, string $description, string $kind): void {
register_sidebar([
'id' => $id,
'name' => __($name, 'aksrt'),
'description' => __($description, 'aksrt'),
'before_widget' => '<section id="%1$s" class="aksrt-widget-shell aksrt-widget-shell--' . esc_attr($kind) . ' %2$s">',
'after_widget' => '</section>',
'before_title' => '<h3 class="aksrt-widget-shell__title">',
'after_title' => '</h3>',
]);
}
}
+214
View File
@@ -0,0 +1,214 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Import/export contract for schema widgets. */
final class WidgetTransfer {
public const FORMAT = 'aksrt.widgets.v1';
/** @return array<string, array<string,mixed>> */
public static function templates(): array {
return [
'article_home' => [
'label' => __('文章首页', 'aksrt'),
'description' => __('公告、幻灯片、多栏目文章与热榜侧栏。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'notice', 'settings' => ['title' => '站点公告']],
['widget' => 'slider', 'settings' => ['title' => '精选内容']],
['widget' => 'tab_posts', 'settings' => ['title' => '热门栏目']],
],
'home_sidebar' => [['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']]],
],
],
'article_list' => [
'label' => __('文章列表', 'aksrt'),
'description' => __('标题、分类卡片与可分页文章列表。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'text_title', 'settings' => ['heading' => '最新知识内容', 'description' => '持续更新的课程、文章与资料']],
['widget' => 'term_cards', 'settings' => ['title' => '内容分类']],
['widget' => 'posts', 'settings' => ['title' => '最新发布', 'paginate' => 'number']],
],
],
],
'links_navigation' => [
'label' => __('链接导航', 'aksrt'),
'description' => __('搜索、分类入口与链接列表。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'icon_cover_cards', 'settings' => ['title' => '常用入口']],
['widget' => 'links', 'settings' => ['title' => '链接导航']],
],
'home_sidebar' => [['widget' => 'search', 'settings' => ['title' => '快速查找']]],
],
],
'article_sidebar' => [
'label' => __('文章侧栏', 'aksrt'),
'description' => __('目录、作者、付费授权与热榜文章。', 'aksrt'),
'sidebars' => [
'single_sidebar' => [
['widget' => 'posts_toc', 'settings' => ['title' => '文章目录']],
['widget' => 'author_card', 'settings' => ['title' => '作者']],
['widget' => 'post_pay', 'settings' => []],
['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']],
],
],
],
'knowledge_landing' => [
'label' => __('知识付费落地页', 'aksrt'),
'description' => __('亮点、方案、评价与常见问题。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'feature_intro', 'settings' => ['title' => '系统化学习路径']],
['widget' => 'highlights', 'settings' => ['title' => '内容亮点']],
['widget' => 'pricing_plans', 'settings' => ['title' => '选择学习方案']],
['widget' => 'reviews', 'settings' => ['title' => '学员评价']],
['widget' => 'faq', 'settings' => ['title' => '常见问题']],
],
],
],
];
}
public static function template_payload(string $template): array {
$templates = self::templates();
if (!isset($templates[$template])) {
throw new \InvalidArgumentException(__('未知的小工具模板。', 'aksrt'));
}
return [
'format' => self::FORMAT,
'template' => $template,
'sidebars' => $templates[$template]['sidebars'],
];
}
public static function export(string $sidebar = ''): array {
$assignments = wp_get_sidebars_widgets();
$sidebars = [];
foreach ($assignments as $sidebar_id => $widget_ids) {
if ($sidebar !== '' && $sidebar_id !== $sidebar) {
continue;
}
if (!is_array($widget_ids)) {
continue;
}
$items = [];
foreach ($widget_ids as $widget_id) {
$item = self::export_widget((string) $widget_id);
if ($item !== null) {
$items[] = $item;
}
}
if ($items || $sidebar !== '') {
$sidebars[$sidebar_id] = $items;
}
}
return [
'format' => self::FORMAT,
'exported_at' => gmdate('c'),
'sidebars' => $sidebars,
];
}
/** @return array{sidebars:int,widgets:int} */
public static function import(array $payload, string $mode = 'append', string $target_sidebar = ''): array {
if (($payload['format'] ?? '') !== self::FORMAT || !isset($payload['sidebars']) || !is_array($payload['sidebars'])) {
throw new \InvalidArgumentException(__('仅接受 aksrt.widgets.v1 格式。', 'aksrt'));
}
$mode = $mode === 'replace' ? 'replace' : 'append';
$registered = array_keys($GLOBALS['wp_registered_sidebars'] ?? []);
$assignments = wp_get_sidebars_widgets();
$sources = array_slice($payload['sidebars'], 0, 100, true);
if ($target_sidebar !== '') {
$combined = [];
foreach ($sources as $items) {
if (is_array($items)) {
$combined = array_merge($combined, $items);
}
}
$sources = [$target_sidebar => $combined];
}
$changed = 0;
$created = 0;
foreach ($sources as $source_sidebar => $items) {
$sidebar_id = $target_sidebar !== '' ? sanitize_key($target_sidebar) : sanitize_key((string) $source_sidebar);
if (!in_array($sidebar_id, $registered, true) || !is_array($items)) {
continue;
}
$current = isset($assignments[$sidebar_id]) && is_array($assignments[$sidebar_id]) ? $assignments[$sidebar_id] : [];
if ($mode === 'replace') {
$current = [];
}
foreach (array_slice($items, 0, 100) as $item) {
if (!is_array($item)) {
continue;
}
$widget_id = self::create_widget($item);
if ($widget_id !== '') {
$current[] = $widget_id;
$created++;
}
}
$assignments[$sidebar_id] = array_values(array_unique($current));
$changed++;
}
wp_set_sidebars_widgets($assignments);
return ['sidebars' => $changed, 'widgets' => $created];
}
public static function preview(string $template): string {
$payload = self::template_payload($template);
$html = '';
foreach ($payload['sidebars'] as $sidebar_id => $items) {
$html .= '<section class="aksrt-widget-template-preview__area"><h4>' . esc_html((string) $sidebar_id) . '</h4>';
foreach ($items as $item) {
$definition = WidgetRegistry::get((string) ($item['widget'] ?? ''));
if ($definition === null) {
continue;
}
$settings = $definition->normalize((array) ($item['settings'] ?? []));
$html .= '<article class="aksrt-widget-template-preview__module aksrt-widget aksrt-widget--' . esc_attr($definition->slug()) . '"><strong>' . esc_html($definition->name()) . '</strong><div class="aksrt-widget__content">';
$html .= RendererRegistry::render($definition->renderer(), $settings, ['widget_id' => 'preview-0']);
$html .= '</div></article>';
}
$html .= '</section>';
}
return $html;
}
private static function export_widget(string $widget_id): ?array {
if (!preg_match('/^(aksrt_widget_[a-z0-9_]+)-([1-9][0-9]*)$/', $widget_id, $matches)) {
return null;
}
$definition = WidgetRegistry::get_by_id_base($matches[1]);
if ($definition === null) {
return null;
}
$settings = get_option('widget_' . $definition->id_base(), []);
$number = (int) $matches[2];
if (!is_array($settings) || !isset($settings[$number]) || !is_array($settings[$number])) {
return null;
}
return ['widget' => $definition->slug(), 'settings' => $definition->normalize($settings[$number])];
}
private static function create_widget(array $item): string {
$definition = WidgetRegistry::get(sanitize_key((string) ($item['widget'] ?? '')));
if ($definition === null) {
return '';
}
$option_name = 'widget_' . $definition->id_base();
$settings = get_option($option_name, []);
$settings = is_array($settings) ? $settings : [];
$numbers = array_filter(array_map('intval', array_keys($settings)), static fn(int $number): bool => $number > 0);
$number = $numbers ? max($numbers) + 1 : 1;
$settings[$number] = $definition->normalize((array) ($item['settings'] ?? []));
$settings['_multiwidget'] = 1;
update_option($option_name, $settings);
return $definition->id_base() . '-' . $number;
}
}
-245
View File
@@ -1,245 +0,0 @@
<?php
/**
* Custom buttons group (zibll zib_widget_ui_buttons).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class ButtonsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [
'description' => esc_html__('显示一组自定义按钮,支持外链跳转与色系。', 'aksrt'),
'classname' => 'widget_aksrt_buttons',
]);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
// Also accept zibll-style `items`.
if (!$buttons && isset($instance['items']) && is_array($instance['items'])) {
$buttons = $instance['items'];
}
if (!$buttons) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一个按钮', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一个按钮', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
if (!in_array($align, ['left', 'center', 'right'], true)) {
$align = 'center';
}
$radius = !empty($instance['radius']);
$hollow = !empty($instance['hollow']);
$go_link = !empty($instance['go_link']);
$pc_gap = max(0, min(40, (int) ($instance['pc_gap'] ?? $instance['gap'] ?? 12)));
$m_gap = max(0, min(30, (int) ($instance['m_gap'] ?? 6)));
$pc_pad = sanitize_key((string) ($instance['pc_padding'] ?? 'large'));
$m_pad = sanitize_key((string) ($instance['m_padding'] ?? 'medium'));
if (!in_array($pc_pad, ['small', 'medium', 'large'], true)) {
$pc_pad = 'large';
}
if (!in_array($m_pad, ['small', 'medium', 'large'], true)) {
$m_pad = 'medium';
}
$pc_width = max(0, min(400, (int) ($instance['pc_width'] ?? 0)));
$m_width = max(0, min(400, (int) ($instance['m_width'] ?? 0)));
$but_class = 'but';
if ($radius) {
$but_class .= ' radius';
}
if ($hollow) {
$but_class .= ' hollow';
}
$buts = '';
foreach ($buttons as $btn) {
if (!is_array($btn)) {
continue;
}
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
if ($text === '') {
continue;
}
$url = '';
$target = '';
if (is_array($btn['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($btn['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($btn['link']['target'] ?? ''));
} else {
$url = esc_url_raw(trim((string) ($btn['url'] ?? '')));
$target = !empty($btn['new_window']) || !empty($btn['target']) ? '_blank' : '';
}
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
if ($url === '') {
$url = 'javascript:void(0)';
}
$color = sanitize_html_class((string) ($btn['color'] ?? $btn['style'] ?? ''));
// Map legacy style keys to color classes.
$style_map = [
'primary' => 'c-blue',
'secondary' => 'c-blue-2',
'outline' => '',
'text' => '',
'pink' => 'c-red',
'blue' => 'c-blue',
'green' => 'c-green',
'yellow' => 'c-yellow',
'purple' => 'c-purple',
];
if (isset($style_map[$color])) {
$color = $style_map[$color];
}
$item_class = trim($but_class . ' ' . $color);
$icon_left = sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? ''));
$icon_right = sanitize_text_field((string) ($btn['icon_right'] ?? ''));
$left_html = $icon_left !== '' ? '<i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i> ' : '';
$right_html = $icon_right !== '' ? ' <span class="ml6"><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>' : '';
$tgt = $target !== '' && strpos($url, 'javascript:') !== 0
? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"'
: '';
$buts .= '<div><a class="' . esc_attr($item_class) . '" href="' . esc_url($url) . '"' . $tgt . '>'
. $left_html . esc_html($text) . $right_html . '</a>';
$buts .= '</div>';
}
if ($buts === '') {
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
}
$style_attr = '';
if ($pc_width > 0) {
$style_attr .= '--pc-width:' . $pc_width . 'px;';
}
if ($m_width > 0) {
$style_attr .= '--m-width:' . $m_width . 'px;';
}
$style_attr .= '--pc-gap:' . $pc_gap . 'px;--m-gap:' . $m_gap . 'px;--btn-gap:' . $pc_gap . 'px;';
echo '<div class="widget-buttons mb20 pc-size-' . esc_attr($pc_pad) . ' m-size-' . esc_attr($m_pad) . '">';
echo '<div class="widget-buttons-wrap flex hh align-' . esc_attr($align) . '" style="' . esc_attr($style_attr) . '">';
echo $buts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
while (count($buttons) < 4) {
$buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'icon_right' => '', 'new_window' => false];
}
?>
<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 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><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?php esc_html_e('圆角胶囊', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hollow')); ?>" value="1" <?php checked(!empty($instance['hollow'])); ?>> <?php esc_html_e('镂空', 'aksrt'); ?></label>
<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 foreach ($buttons as $i => $btn) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][text]")); ?>" value="<?php echo esc_attr((string) ($btn['text'] ?? '')); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($btn['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($btn['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('左图标 class', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon_right]")); ?>" value="<?php echo esc_attr((string) ($btn['icon_right'] ?? '')); ?>" placeholder="<?php esc_attr_e('右图标 class', 'aksrt'); ?>">
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][style]")); ?>">
<?php foreach (['primary' => '主要', 'secondary' => '次要', 'outline' => '线框', 'text' => '文字', 'pink' => '粉', 'blue' => '蓝', 'green' => '绿', 'yellow' => '黄', 'purple' => '紫'] as $k => $lab) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($btn['style'] ?? 'primary'), $k); ?>><?php echo esc_html($lab); ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][new_window]")); ?>" value="1" <?php checked(!empty($btn['new_window'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label>
</div>
<?php endforeach;
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$buttons = [];
if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) {
foreach ($new_instance['buttons'] as $btn) {
if (!is_array($btn)) {
continue;
}
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
if ($text === '') {
continue;
}
$buttons[] = [
'text' => $text,
'url' => esc_url_raw(trim((string) ($btn['url'] ?? ''))),
'style' => sanitize_key((string) ($btn['style'] ?? 'primary')),
'icon' => sanitize_text_field((string) ($btn['icon'] ?? '')),
'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')),
'new_window' => !empty($btn['new_window']),
];
if (count($buttons) >= 8) {
break;
}
}
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
'radius' => !empty($new_instance['radius']) ? 1 : 0,
'hollow' => !empty($new_instance['hollow']) ? 1 : 0,
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 12))),
'pc_gap' => max(0, min(60, (int) ($new_instance['pc_gap'] ?? $new_instance['gap'] ?? 12))),
'm_gap' => max(0, min(60, (int) ($new_instance['m_gap'] ?? 6))),
'pc_padding' => in_array(($new_instance['pc_padding'] ?? 'large'), ['small', 'medium', 'large'], true) ? $new_instance['pc_padding'] : 'large',
'm_padding' => in_array(($new_instance['m_padding'] ?? 'medium'), ['small', 'medium', 'large'], true) ? $new_instance['m_padding'] : 'medium',
'pc_width' => max(0, min(500, (int) ($new_instance['pc_width'] ?? 0))),
'm_width' => max(0, min(500, (int) ($new_instance['m_width'] ?? 0))),
'buttons' => $buttons,
]; return $business;
}
}
-263
View File
@@ -1,263 +0,0 @@
<?php
/**
* Categories list (zibll widget categories depth).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class CategoriesWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_categories',
esc_html__('AKSRT 分类目录', 'aksrt'),
[
'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
'customize_selective_refresh' => true,
'classname' => 'widget_aksrt_categories',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(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));
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$count = max(1, min(50, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
$orderby = 'count';
}
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$hierarchical = !empty($instance['hierarchical']);
$parent = max(0, (int) ($instance['parent'] ?? 0));
$show_count = !isset($instance['show_count']) || !empty($instance['show_count']);
$show_desc = !empty($instance['show_desc']);
$show_thumb = !empty($instance['show_thumb']);
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$term_args = [
'taxonomy' => 'category',
'number' => $hierarchical ? 0 : $count,
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'hierarchical' => $hierarchical,
];
if ($parent > 0) {
$term_args['parent'] = $parent;
} elseif ($hierarchical) {
$term_args['parent'] = 0;
}
if ($include) {
$term_args['include'] = $include;
$term_args['orderby'] = 'include';
unset($term_args['number']);
}
if ($exclude) {
$term_args['exclude'] = $exclude;
}
$cats = get_categories($term_args);
if (!$cats) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
ob_start();
$cls = 'widget-list aksrt-cat-list ajax-item' . ($hierarchical ? ' is-tree' : '') . ($show_thumb ? ' has-thumb' : '');
echo '<ul class="' . esc_attr($cls) . '">';
$n = 0;
foreach ($cats as $cat) {
if (!$hierarchical && $n >= $count) {
break;
}
echo self::render_cat_item($cat, $show_count, $show_desc, $show_thumb, $hierarchical ? 0 : -1, $count, $n, $hide_empty, $orderby, $order);
$n++;
}
echo '</ul>';
return (string) ob_get_clean();
}
/**
* @param \WP_Term $cat
*/
private static function render_cat_item($cat, bool $show_count, bool $show_desc, bool $show_thumb, int $depth, int $limit, int &$n, bool $hide_empty, string $orderby, string $order): string {
if ($n > 0 && $limit > 0 && $n >= $limit && $depth >= 0) {
return '';
}
$html = '<li class="aksrt-cat-list__item depth-' . max(0, $depth) . '">';
$html .= '<a class="aksrt-cat-list__link" href="' . esc_url(get_category_link($cat->term_id)) . '">';
if ($show_thumb) {
$thumb = '';
$img_id = (int) get_term_meta($cat->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($cat->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = (string) (wp_get_attachment_image_url($img_id, 'thumbnail') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($cat->term_id, '_aksrt_cover_url', true);
}
if ($thumb !== '') {
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
$html .= '<span class="aksrt-cat-list__thumb"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="32" height="32" /></span>';
} else {
$html .= '<span class="aksrt-cat-list__thumb is-empty"><i class="fa fa-folder-o" aria-hidden="true"></i></span>';
}
}
$html .= '<span class="aksrt-cat-list__text">';
$html .= '<span class="aksrt-cat-list__name">' . esc_html($cat->name) . '</span>';
if ($show_desc && $cat->description !== '') {
$desc = wp_trim_words(wp_strip_all_tags((string) $cat->description), 12, '…');
$html .= '<span class="aksrt-cat-list__desc muted-2-color em09">' . esc_html($desc) . '</span>';
}
$html .= '</span>';
if ($show_count) {
$html .= '<span class="count">' . (int) $cat->count . '</span>';
}
$html .= '</a>';
if ($depth >= 0) {
$children = get_categories([
'taxonomy' => 'category',
'parent' => (int) $cat->term_id,
'hide_empty' => $hide_empty,
'orderby' => $orderby,
'order' => $order,
]);
if ($children) {
$html .= '<ul class="aksrt-cat-list__children">';
foreach ($children as $child) {
$n++;
if ($limit > 0 && $n > $limit) {
break;
}
$html .= self::render_cat_item($child, $show_count, $show_desc, $show_thumb, $depth + 1, $limit, $n, $hide_empty, $orderby, $order);
}
$html .= '</ul>';
}
}
$html .= '</li>';
return $html;
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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="50" 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="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>
<option value="slug" <?php selected(($instance['orderby'] ?? ''), 'slug'); ?>><?php esc_html_e('别名', 'aksrt'); ?></option>
</select>
</label>
<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>
</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('parent')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['parent'] ?? 0))); ?>">
</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><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!isset($instance['show_count']) || !empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_desc')); ?>" value="1" <?php checked(!empty($instance['show_desc'])); ?>> <?php esc_html_e('显示描述', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
<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><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hierarchical')); ?>" value="1" <?php checked(!empty($instance['hierarchical'])); ?>> <?php esc_html_e('层级树', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'count'));
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
$orderby = 'count';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 8))),
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'parent' => max(0, (int) ($new_instance['parent'] ?? 0)),
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
'show_desc' => !empty($new_instance['show_desc']) ? 1 : 0,
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'hierarchical' => !empty($new_instance['hierarchical']) ? 1 : 0,
];
}
}
-518
View File
@@ -1,518 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage content widgets aligned to the fixed Zibll-like layout.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_home_widget_allowed_layouts')) {
/**
* Allowed post-card layouts for homepage content widgets.
*
* @return array<string, string>
*/
function aksrt_home_widget_allowed_layouts(): array {
return [
'card' => __('卡片', 'aksrt'),
'list' => __('列表', 'aksrt'),
'grid-cms' => __('资源网格', 'aksrt'),
];
}
}
if (!function_exists('aksrt_home_widget_post_types')) {
/**
* Post types used by homepage widgets.
*
* @return array<int, string>
*/
function aksrt_home_widget_post_types(): array {
return ['post'];
}
}
if (!function_exists('aksrt_home_widget_available_categories')) {
/**
* Return categories for widget admin forms.
*
* @return array<int, \WP_Term>
*/
function aksrt_home_widget_available_categories(): array {
$categories = get_categories([
'taxonomy' => 'category',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
]);
return is_array($categories) ? $categories : [];
}
}
if (!function_exists('aksrt_home_widget_fallback_category_ids')) {
/**
* Pick top categories as fallback when widget instance has no selection.
*
* @param int $limit Maximum category ids to return.
* @return array<int, int>
*/
function aksrt_home_widget_fallback_category_ids(int $limit = 1): array {
$limit = max(1, min(8, $limit));
$terms = get_terms([
'taxonomy' => 'category',
'orderby' => 'count',
'order' => 'DESC',
'number' => $limit,
'hide_empty' => true,
'fields' => 'ids',
]);
if (is_wp_error($terms) || !is_array($terms)) {
return [];
}
return array_values(array_filter(array_map('absint', $terms)));
}
}
if (!function_exists('aksrt_home_widget_normalize_category_ids')) {
/**
* Normalize selected category ids.
*
* @param mixed $raw_ids Raw ids from widget instance.
* @param int $limit Maximum ids to keep.
* @return array<int, int>
*/
function aksrt_home_widget_normalize_category_ids($raw_ids, int $limit = 4): array {
$ids = array_map('absint', (array) $raw_ids);
$ids = array_values(array_filter($ids, static fn($id) => $id > 0));
if ($limit > 0) {
$ids = array_slice($ids, 0, $limit);
}
return $ids;
}
}
if (!function_exists('aksrt_home_widget_resolve_layout')) {
/**
* Sanitize requested widget post layout.
*
* @param mixed $layout Layout value.
* @param string $default Default layout.
* @return string
*/
function aksrt_home_widget_resolve_layout($layout, string $default = 'card'): string {
$allowed = array_keys(aksrt_home_widget_allowed_layouts());
$layout = is_string($layout) ? $layout : $default;
return in_array($layout, $allowed, true) ? $layout : $default;
}
}
if (!function_exists('aksrt_home_widget_resolve_context')) {
/**
* Resolve widget area context from sidebar args.
*/
function aksrt_home_widget_resolve_context(array $args): string {
if (function_exists('aksrt_widget_get_area_context')) {
return aksrt_widget_get_area_context($args);
}
return 'homepage';
}
}
if (!function_exists('aksrt_home_widget_effective_layout')) {
/**
* Compact rich homepage widgets when placed into a sidebar area.
*/
function aksrt_home_widget_effective_layout(string $layout, string $context): string {
if ($context === 'sidebar' && $layout !== 'list') {
return 'list';
}
return $layout;
}
}
if (!function_exists('aksrt_home_widget_effective_columns')) {
/**
* Sidebar widgets should stay single-column.
*/
function aksrt_home_widget_effective_columns(int $columns, string $context): int {
if ($context === 'sidebar') {
return 1;
}
return max(1, min(4, $columns));
}
}
if (!function_exists('aksrt_home_widget_render_header')) {
/**
* Render a compact Zibll-like section header.
*
* @param string $title Section title.
* @param string $more_link Optional more link.
* @param string $more_text Optional more text.
* @return void
*/
function aksrt_home_widget_render_header(string $title, string $more_link = '', string $more_text = ''): void {
?>
<header class="aksrt-home-widget__header">
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
<?php if ($more_link !== '') : ?>
<a class="aksrt-home-widget__more" href="<?php echo esc_url($more_link); ?>">
<?php echo esc_html($more_text !== '' ? $more_text : __('查看更多', 'aksrt')); ?>
</a>
<?php endif; ?>
</header>
<?php
}
}
if (!function_exists('aksrt_home_widget_render_post_grid')) {
/**
* Render posts for homepage widgets.
*
* Delegates to the unified shared renderer (aksrt_render_post_list).
*
* @param \WP_Query $query Posts query.
* @param string $layout Card layout.
* @param int $columns Desktop columns.
* @param string $context Sidebar or homepage context.
* @return void
*/
function aksrt_home_widget_render_post_grid(\WP_Query $query, string $layout = 'card', int $columns = 3, string $context = 'homepage'): void {
if (!$query->have_posts()) {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无内容', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无内容', 'aksrt') . '</p>';
wp_reset_postdata();
return;
}
if ($context === 'sidebar') {
$use_homepage_mini = function_exists('aksrt_is_fixed_homepage_request')
&& aksrt_is_fixed_homepage_request()
&& function_exists('aksrt_render_mini_list')
&& (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::homepage_sidebar_mini_enabled());
if ($use_homepage_mini && function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
echo '</div>';
return;
}
// Fallback: shared mini list (lazy thumbs + meta).
if (function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
echo '</div>';
return;
}
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
while ($query->have_posts()) {
$query->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
echo '<li class="widget-tab-posts__item">';
if ($thumb) {
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__thumb"><img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" /></a>';
}
echo '<div class="widget-tab-posts__info">';
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__title">' . esc_html(get_the_title()) . '</a>';
echo '<span class="widget-tab-posts__meta">' . esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')) . '</span>';
echo '</div></li>';
}
echo '</ul>';
wp_reset_postdata();
return;
}
// Homepage / content area: delegate to shared renderer
$layout = aksrt_home_widget_resolve_layout($layout, 'card');
$columns = max(1, min(4, $columns));
if (function_exists('aksrt_render_post_list')) {
aksrt_render_post_list($query, [
'mode' => $layout === 'grid-cms' ? 'grid-cms' : $layout,
'context' => 'widget',
'columns' => $columns,
], true);
} else {
// Fallback: use template part
$previous_layout = get_query_var('aksrt_post_card_layout', '');
$previous_scene = get_query_var('aksrt_post_card_scene', '');
set_query_var('aksrt_post_card_layout', $layout);
set_query_var('aksrt_post_card_scene', 'home');
$classes = [
'aksrt-home-widget__post-grid',
'aksrt-home-widget__post-grid--' . $layout,
'post-grid',
'post-grid--' . $layout,
];
printf(
'<div class="%1$s" style="--aksrt-widget-columns:%2$d;">',
esc_attr(implode(' ', $classes)),
(int) $columns
);
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
}
echo '</div>';
set_query_var('aksrt_post_card_layout', $previous_layout);
set_query_var('aksrt_post_card_scene', $previous_scene);
}
wp_reset_postdata();
}
}
if (!function_exists('aksrt_home_widget_get_lazyload_args')) {
/**
* Lazyload settings reused by category recommend widget.
*
* @return array{enabled: bool, placeholder_url: string}
*/
function aksrt_home_widget_get_lazyload_args(): array {
$config = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_lazyload_config()
: ['enabled' => false, 'placeholder_url' => ''];
return [
'enabled' => ! empty($config['enabled']),
'placeholder_url' => ! empty($config['placeholder_url'])
? (string) $config['placeholder_url']
: get_template_directory_uri() . '/assets/img/placeholder.svg',
];
}
}
/**
* Homepage category block widget.
*/
class CategoryBlockWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_block',
esc_html__('AKSRT 分类块', 'aksrt'),
[
'classname' => 'widget_aksrt_category_block',
'description' => esc_html__('指定分类的最新内容区块,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1, string $context = 'homepage'): array {
$page = max(1, $page);
$category_id = self::resolve_category_id_static($instance);
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
$layout = aksrt_home_widget_effective_layout(
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
$context
);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
$paginate = 'none';
}
$query_args = [
'post_type' => aksrt_home_widget_post_types(),
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => $page,
'ignore_sticky_posts' => true,
];
if ($paginate === 'none') {
$query_args['no_found_rows'] = true;
}
if ($category_id > 0) {
$query_args['cat'] = $category_id;
}
$query = new \WP_Query($query_args);
ob_start();
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
$html = (string) ob_get_clean();
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无内容', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无内容', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $query->max_num_pages);
$total = (int) $query->found_posts;
$has_more = in_array($paginate, ['ajax', 'number'], true) && $page < $max_pages;
$base_qs = [
'count' => $count,
'cat' => $category_id,
'columns' => $columns,
'layout' => $layout,
'context' => $context,
'paginate' => $paginate,
];
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/category-block'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
public function widget($args, $instance): void {
$context = aksrt_home_widget_resolve_context($args);
$category_id = self::resolve_category_id_static($instance);
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
$show_more = ! empty($instance['show_more']);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
$paginate = 'none';
}
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1, $context);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
aksrt_home_widget_render_header(
(string) $title,
($show_more && $term && ! is_wp_error($term)) ? (string) get_category_link($category_id) : '',
__('查看分类', 'aksrt')
);
$rest = rest_url('aksrt/v1/widgets/category-block');
echo '<div class="ajaxpager aksrt-home-widget__ajax" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$category_id = (int) ($instance['category_id'] ?? 0);
$count = (int) ($instance['count'] ?? 6);
$columns = (int) ($instance['columns'] ?? 3);
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
$show_more = ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_id')); ?>"><?php esc_html_e('分类:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_id')); ?>" name="<?php echo esc_attr($this->get_field_name('category_id')); ?>">
<option value="0"><?php esc_html_e('自动选择最热门分类', 'aksrt'); ?></option>
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected($category_id, (int) $category->term_id); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('文章数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示分类链接', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_id' => absint($new_instance['category_id'] ?? 0),
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'none')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'none')) : 'none',
]; return $business;
}
public static function resolve_category_id_static(array $instance): int {
$category_id = absint($instance['category_id'] ?? 0);
if ($category_id > 0) {
return $category_id;
}
$fallback = aksrt_home_widget_fallback_category_ids(1);
return $fallback[0] ?? 0;
}
private function resolve_category_id(array $instance): int {
return self::resolve_category_id_static($instance);
}
}
-264
View File
@@ -1,264 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage category recommend cards widget.
*/
class CategoryRecommendWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_recommend',
esc_html__('AKSRT 分类推荐卡片', 'aksrt'),
[
'classname' => 'widget_aksrt_category_recommend',
'description' => esc_html__('分类推荐卡片区块,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function render_html(array $instance): string {
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
$posts_per_cat = max(1, min(8, (int) ($instance['posts_per_category'] ?? 5)));
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
$show_views = ! empty($instance['show_views']);
$show_description = ! empty($instance['show_description']);
$show_dates = ! empty($instance['show_dates']);
$show_more = ! empty($instance['show_more']);
if (empty($category_ids)) {
$category_ids = aksrt_home_widget_fallback_category_ids(4);
}
$cards = self::build_cards_static($category_ids, $posts_per_cat, $show_views, $show_description);
if (empty($cards)) {
return function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
}
$lazy = aksrt_home_widget_get_lazyload_args();
ob_start();
printf('<div class="aksrt-category-recommend__grid ajax-item" style="--aksrt-recommend-columns:%d;">', (int) $columns);
foreach ($cards as $card) {
$cover = (string) ($card['cover'] ?? '');
$link = (string) ($card['link'] ?? '');
$name = (string) ($card['name'] ?? '');
$badge = sprintf(_n('%d 篇文章', '%d 篇文章', (int) $card['count'], 'aksrt'), (int) $card['count']);
$more = '';
if ($show_views && (int) $card['views'] > 0) {
$v = function_exists('aksrt_format_number') ? aksrt_format_number((int) $card['views']) : number_format_i18n((int) $card['views']);
$more = '<span class="aksrt-category-recommend__views"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html((string) $v) . '</span>';
}
echo '<article class="aksrt-category-recommend__card" data-aksrt-animate="fade-up">';
if (function_exists('aksrt_graphic_card') && $cover !== '') {
echo aksrt_graphic_card([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'type' => 'style-2',
'class' => 'aksrt-category-recommend__hero mb10',
'img' => $cover,
'alt' => $name,
'link' => ['url' => $link],
'text1' => $name,
'text2' => $show_description ? (string) $card['description'] : '',
'text3' => $badge,
'more' => $more,
'lazy' => true,
]);
} else {
echo '<div class="aksrt-category-recommend__hero">';
echo '<a href="' . esc_url($link) . '">';
if ($lazy['enabled']) {
echo '<span class="aksrt-skeleton"></span>';
echo '<img class="aksrt-category-recommend__cover aksrt-lazy" data-src="' . esc_url($cover) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="' . esc_attr($name) . '">';
} else {
echo '<img class="aksrt-category-recommend__cover" src="' . esc_url($cover) . '" alt="' . esc_attr($name) . '">';
}
echo '</a>';
echo '<span class="aksrt-category-recommend__badge">' . esc_html($badge) . '</span>';
echo $more; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '<div class="aksrt-category-recommend__meta">';
echo '<h4 class="aksrt-category-recommend__name"><a href="' . esc_url($link) . '">' . esc_html($name) . '</a></h4>';
if ($show_description && $card['description'] !== '') {
echo '<p class="aksrt-category-recommend__desc">' . esc_html((string) $card['description']) . '</p>';
}
echo '</div>';
}
echo '<div class="aksrt-category-recommend__body">';
// Prefer shared mini list when post IDs available.
if (!empty($card['post_ids']) && function_exists('aksrt_render_mini_list')) {
$q = new \WP_Query([
'post_type' => 'any',
'post__in' => array_map('intval', (array) $card['post_ids']),
'orderby' => 'post__in',
'posts_per_page' => count((array) $card['post_ids']),
'ignore_sticky_posts' => true,
'no_found_rows' => true,
]);
echo '<div class="aksrt-category-recommend__posts posts-mini-wrap">';
aksrt_render_mini_list($q, ['context' => 'widget', 'show_thumb' => true, 'show_meta' => $show_dates]);
echo '</div>';
wp_reset_postdata();
} else {
echo '<ul class="aksrt-category-recommend__posts">';
foreach ($card['posts'] as $post_item) {
echo '<li class="aksrt-category-recommend__post">';
echo '<a class="aksrt-category-recommend__post-link" href="' . esc_url($post_item['link']) . '">' . esc_html($post_item['title']) . '</a>';
if ($show_dates && $post_item['date'] !== '') {
echo '<span class="aksrt-category-recommend__date">' . esc_html($post_item['date']) . '</span>';
}
echo '</li>';
}
echo '</ul>';
}
if ($show_more) {
echo '<div class="aksrt-home-widget__footer">';
echo '<a class="aksrt-home-widget__more" href="' . esc_url($link) . '">' . esc_html__('查看更多', 'aksrt') . '</a>';
echo '</div>';
}
echo '</div></article>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
$html = self::render_html($instance);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
aksrt_home_widget_render_header($title);
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? __('分类推荐', 'aksrt'));
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
$posts_per_cat = (int) ($instance['posts_per_category'] ?? 5);
$columns = (int) ($instance['columns'] ?? 2);
$show_views = ! empty($instance['show_views']);
$show_description = ! array_key_exists('show_description', $instance) || ! empty($instance['show_description']);
$show_dates = ! array_key_exists('show_dates', $instance) || ! empty($instance['show_dates']);
$show_more = ! array_key_exists('show_more', $instance) || ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(最多 4 个):', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>"><?php esc_html_e('每个分类展示文章数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>" name="<?php echo esc_attr($this->get_field_name('posts_per_category')); ?>" type="number" min="1" max="8" value="<?php echo (int) $posts_per_cat; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_views); ?> id="<?php echo esc_attr($this->get_field_id('show_views')); ?>" name="<?php echo esc_attr($this->get_field_name('show_views')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_views')); ?>"><?php esc_html_e('显示分类总浏览量', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_description); ?> id="<?php echo esc_attr($this->get_field_id('show_description')); ?>" name="<?php echo esc_attr($this->get_field_name('show_description')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_description')); ?>"><?php esc_html_e('显示分类描述', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_dates); ?> id="<?php echo esc_attr($this->get_field_id('show_dates')); ?>" name="<?php echo esc_attr($this->get_field_name('show_dates')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_dates')); ?>"><?php esc_html_e('显示文章时间', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('homepage'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 4),
'posts_per_category' => max(1, min(8, absint($new_instance['posts_per_category'] ?? 5))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 2))),
'show_views' => ! empty($new_instance['show_views']) ? 1 : 0,
'show_description' => ! empty($new_instance['show_description']) ? 1 : 0,
'show_dates' => ! empty($new_instance['show_dates']) ? 1 : 0,
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
]; return $business;
}
/**
* Build category recommend cards.
*
* @param array<int, int> $category_ids Category ids.
* @param int $posts_per_cat Post count per category.
* @param bool $show_views Whether to calculate views.
* @param bool $show_description Whether to fetch description.
* @return array<int, array<string, mixed>>
*/
public static function build_cards_static(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
$cards = [];
foreach ($category_ids as $category_id) {
$term = get_term($category_id, 'category');
if (! $term || is_wp_error($term)) {
continue;
}
$posts = get_posts([
'post_type' => aksrt_home_widget_post_types(),
'cat' => $category_id,
'posts_per_page' => $posts_per_cat,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
]);
$post_items = [];
foreach ($posts as $post) {
$post_items[] = [
'title' => get_the_title($post->ID),
'link' => get_permalink($post->ID),
'date' => function_exists('aksrt_time_ago') ? aksrt_time_ago($post->post_date) : get_the_date('Y-m-d', $post->ID),
];
}
$cards[] = [
'name' => $term->name,
'link' => get_category_link($category_id),
'cover' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_cover_url($category_id) : (get_template_directory_uri() . '/assets/img/placeholder.svg'),
'description' => $show_description && class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_recommend_desc($category_id) : ($show_description ? (string) $term->description : ''),
'count' => (int) $term->count,
'views' => $show_views && function_exists('aksrt_get_category_total_views') ? aksrt_get_category_total_views($category_id) : 0,
'posts' => $post_items,
];
}
return $cards;
}
}
-203
View File
@@ -1,203 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage category tabs widget.
*/
class CategoryTabsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_tabs',
esc_html__('AKSRT 分类 Tabs', 'aksrt'),
[
'classname' => 'widget_aksrt_category_tabs',
'description' => esc_html__('多分类切换内容流,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public function widget($args, $instance): void {
$context = aksrt_home_widget_resolve_context($args);
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('热门分类', 'aksrt');
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
$layout = aksrt_home_widget_effective_layout(
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
$context
);
$show_more = ! empty($instance['show_more']);
$tabs = $this->resolve_tabs($instance);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--tabs widget-tab-posts" data-aksrt-home-cat-tabs data-aksrt-tabs="1" data-rest="' . esc_url(rest_url('aksrt/v1/widgets/category-tabs')) . '" data-count="' . esc_attr((string) $count) . '" data-layout="' . esc_attr($layout) . '" data-columns="' . esc_attr((string) $columns) . '" data-context="' . esc_attr($context) . '" data-show-more="' . (!empty($show_more) ? '1' : '0') . '">';
if (empty($tabs)) {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无可用分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无可用分类', 'aksrt') . '</p>';
echo '</section>';
echo $args['after_widget'];
return;
}
?>
<header class="aksrt-home-widget__header aksrt-home-widget__header--tabs">
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
<ul class="widget-tab-posts__nav aksrt-home-widget__tab-nav" role="tablist">
<?php foreach ($tabs as $index => $tab) : ?>
<li role="presentation">
<button
type="button"
role="tab"
class="widget-tab-posts__tab<?php echo 0 === $index ? ' active' : ''; ?>"
data-tab="cat-<?php echo (int) $tab['id']; ?>"
aria-selected="<?php echo 0 === $index ? 'true' : 'false'; ?>"
>
<?php echo esc_html($tab['name']); ?>
</button>
</li>
<?php endforeach; ?>
</ul>
</header>
<?php
foreach ($tabs as $index => $tab) {
?>
<div class="widget-tab-posts__panel<?php echo 0 === $index ? ' active' : ''; ?>"
data-tab-panel="cat-<?php echo (int) $tab['id']; ?>"
data-loaded="<?php echo 0 === $index ? '1' : '0'; ?>"
data-cat="<?php echo (int) $tab['id']; ?>"
data-more="<?php echo esc_url($tab['link']); ?>"
role="tabpanel"<?php echo 0 === $index ? '' : ' hidden'; ?>>
<?php if (0 === $index) :
$query = new \WP_Query([
'post_type' => aksrt_home_widget_post_types(),
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'cat' => (int) $tab['id'],
]);
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
wp_reset_postdata();
if ($show_more) : ?>
<div class="aksrt-home-widget__footer">
<a class="aksrt-home-widget__more" href="<?php echo esc_url($tab['link']); ?>">
<?php esc_html_e('查看更多', 'aksrt'); ?>
</a>
</div>
<?php endif;
else : ?>
<div class="post_ajax_loader" data-aksrt-ajax-loader>
<?php for ($si = 0; $si < $count; $si++) : ?>
<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div></div>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<?php
}
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
$count = (int) ($instance['count'] ?? 6);
$columns = (int) ($instance['columns'] ?? 3);
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
$show_more = ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(可多选):', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('每个 Tab 的文章数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 6),
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
]; return $business;
}
/**
* Resolve configured category tabs.
*
* @param array $instance Widget instance.
* @return array<int, array{id:int,name:string,link:string}>
*/
private function resolve_tabs(array $instance): array {
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
if (empty($category_ids)) {
$category_ids = aksrt_home_widget_fallback_category_ids(4);
}
$tabs = [];
foreach ($category_ids as $category_id) {
$term = get_term($category_id, 'category');
if (! $term || is_wp_error($term)) {
continue;
}
$tabs[] = [
'id' => (int) $category_id,
'name' => $term->name,
'link' => get_category_link($category_id),
];
}
return $tabs;
}
}
-11
View File
@@ -1,11 +0,0 @@
<?php
/** Canonical shared widget field contract. */
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
final class CommonWidgetFields {
public static function defaults(): array { return ['subtitle'=>'','title_link'=>'','display_pc'=>1,'display_mobile'=>1,'sticky'=>0,'animation'=>'none','background'=>'surface','dark_background'=>'surface']; }
public static function normalize(array $instance): array {
$d=self::defaults(); $out=$instance; $out['subtitle']=sanitize_text_field((string)($instance['subtitle']??$d['subtitle'])); $out['title_link']=esc_url_raw((string)($instance['title_link']??'')); foreach(['display_pc','display_mobile','sticky'] as $k)$out[$k]=!empty($instance[$k])?1:0; $a=sanitize_key((string)($instance['animation']??'none')); $out['animation']=in_array($a,['none','fade','slide'],true)?$a:'none'; $b=sanitize_key((string)($instance['background']??'surface')); $out['background']=in_array($b,['surface','transparent','primary'],true)?$b:'surface'; $db=sanitize_key((string)($instance['dark_background']??'surface')); $out['dark_background']=in_array($db,['surface','transparent','primary'],true)?$db:'surface'; return $out;
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-276
View File
@@ -1,276 +0,0 @@
<?php
/**
* Hot posts ranking (zibll hot posts + ajax).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class HotPostsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_hot_posts',
esc_html__('AKSRT 热榜文章', 'aksrt'),
[
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
$days = max(0, (int) ($instance['days'] ?? 30));
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($days > 0 && $days < 365) {
$query_args['date_query'] = [['after' => $days . ' days ago']];
}
switch ($order_by) {
case 'comments':
$query_args['orderby'] = 'comment_count';
$query_args['order'] = 'DESC';
break;
case 'likes':
$query_args['meta_key'] = '_aksrt_like_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'favorites':
$query_args['meta_key'] = '_aksrt_favorite_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'downloads':
$query_args['meta_key'] = '_aksrt_res_download_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
default:
$query_args['meta_key'] = 'aksrt_post_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
}
return $query_args;
}
public static function render_items_html(\WP_Query $query, array $instance, int $start = 0): string {
if (!$query->have_posts()) {
return '';
}
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$top_tones = ['jb-red', 'jb-yellow', 'b-gray'];
ob_start();
$index = $start;
while ($query->have_posts()) {
$query->the_post();
$index++;
$pid = (int) get_the_ID();
if ($order_by === 'comments') {
$metric = (int) get_comments_number($pid);
$metric_icon = 'fa-comment-o';
$metric_label = __('条评论', 'aksrt');
} elseif ($order_by === 'likes') {
$metric = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
$metric_icon = 'fa-heart';
$metric_label = __('人点赞', 'aksrt');
} elseif ($order_by === 'favorites') {
$metric = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
$metric_icon = 'fa-star';
$metric_label = __('人收藏', 'aksrt');
} elseif ($order_by === 'downloads') {
$metric = (int) get_post_meta($pid, '_aksrt_res_download_count', true);
$metric_icon = 'fa-download';
$metric_label = __('次下载', 'aksrt');
} else {
$metric = (int) get_post_meta($pid, 'aksrt_post_views', true);
$metric_icon = 'fa-eye';
$metric_label = __('次阅读', 'aksrt');
}
$metric_txt = function_exists('aksrt_format_number') ? aksrt_format_number($metric) : (string) $metric;
$tone = $top_tones[$index - 1] ?? 'b-gray';
$top_badge = '<badge class="img-badge left hot ' . ($index === 1 ? 'em12 ' : '') . esc_attr($tone) . '"><i>TOP' . (int) $index . '</i></badge>';
$permalink = get_permalink($pid);
$title = get_the_title($pid);
$time_ago = human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt');
$tgt = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
// Graphic hero for #1 (zibll zib_hot_posts).
if ($index === 1 && $start === 0) {
$thumb = get_the_post_thumbnail_url($pid, 'large');
if (!$thumb) {
$thumb = get_the_post_thumbnail_url($pid, 'medium');
}
echo '<div class="hot-posts-graphic relative ajax-item is-top1">';
echo '<a href="' . esc_url($permalink) . '"' . $tgt . '>';
echo '<div class="graphic hover-zoom-img" style="padding-bottom:60%;">';
if ($thumb) {
echo '<img class="fit-cover radius8 aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
} else {
echo '<span class="hot-posts-graphic__empty"></span>';
}
echo '<div class="absolute linear-mask"></div>';
echo '<div class="abs-center left-bottom box-body">';
echo '<div class="mb6"><span class="badg b-theme badg-sm"><i class="fa ' . esc_attr($metric_icon) . '" aria-hidden="true"></i> ' . esc_html($metric_txt . $metric_label) . '</span></div>';
echo '<div class="hot-posts-graphic__title">' . esc_html(wp_html_excerpt($title, 48, '…')) . '</div>';
echo '</div></div></a>';
echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
continue;
}
$rank_class = $index <= 3 ? ' rank-top' : '';
$thumb = $show_thumb ? get_the_post_thumbnail_url($pid, 'thumbnail') : '';
?>
<div class="hot-post-item flex mt15 relative hover-zoom-img ajax-item <?php echo esc_attr($rank_class); ?>">
<?php echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ($show_thumb) : ?>
<a class="hot-post-item__thumb graphic<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php echo esc_url($permalink); ?>"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php if ($thumb) : ?><img class="fit-cover radius8 aksrt-lazy lazyload" data-src="<?php echo esc_url($thumb); ?>" src="<?php echo esc_url($placeholder); ?>" alt="" loading="lazy" /><?php endif; ?>
</a>
<?php endif; ?>
<div class="hot-post-item__info term-title ml10 flex xx flex1 jsb">
<div class="text-ellipsis-2"><a href="<?php echo esc_url($permalink); ?>" class="hot-post-item__title"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html($title); ?></a></div>
<div class="px12 muted-3-color text-ellipsis flex jsb hot-post-item__meta">
<span class="hot-post-item__time"><i class="fa fa-clock-o mr3" aria-hidden="true"></i><?php echo esc_html($time_ago); ?></span>
<span><i class="fa <?php echo esc_attr($metric_icon); ?>" aria-hidden="true"></i> <?php echo esc_html((string) $metric_txt); ?></span>
</div>
</div>
</div>
<?php
}
wp_reset_postdata();
return (string) ob_get_clean();
}
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$q = new \WP_Query(self::build_query_args($instance, $page));
$items = self::render_items_html($q, $instance, ($page - 1) * $count);
$html = $items !== '' ? '<div class="widget-hot-posts widget-hot-posts--graphic zib-widget">' . $items . '</div>' : '';
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无热门文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$base_qs = array_filter([
'count' => $count,
'order_by' => sanitize_key((string) ($instance['order_by'] ?? 'views')),
'days' => max(0, (int) ($instance['days'] ?? 30)),
'show_thumb' => !empty($instance['show_thumb']) ? 1 : 0,
'target_blank'=> !empty($instance['target_blank']) ? 1 : 0,
'paginate' => $paginate,
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/hot-posts'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$payload = self::query_payload($instance, 1);
$rest = rest_url('aksrt/v1/widgets/hot-posts');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = $instance['title'] ?? __('热门文章', 'aksrt');
$count = (int) ($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = (int) ($instance['days'] ?? 30);
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr((string) $title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="20" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('order_by')); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order_by')); ?>" name="<?php echo esc_attr($this->get_field_name('order_by')); ?>">
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('days')); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('days')); ?>" name="<?php echo esc_attr($this->get_field_name('days')); ?>" type="number" min="0" max="365" value="<?php echo (int) $days; ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => absint($new_instance['count'] ?? 5),
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
'days' => absint($new_instance['days'] ?? 30),
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
];
}
}
-206
View File
@@ -1,206 +0,0 @@
<?php
/**
* Vertical icon cards grid (zibll zib_widget_ui_icon_card → aksrt_icon_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class IconCardsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
'description' => esc_html__('图标在上、文案在下的竖版图标卡片网格。', 'aksrt'),
'classname' => 'widget_aksrt_icon_cards',
]);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
if (!$cards) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张图标卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(6, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2)));
$m_row = max(1, min(4, (int) ($instance['m_row'] ?? 2)));
$go_link = !empty($instance['go_link']);
$show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']);
$icon_size = max(0, min(64, (int) ($instance['icon_size'] ?? $instance['size'] ?? 0)));
$icon_radius = !empty($instance['icon_radius']);
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
}
$is_row = count($cards) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
echo $show_bg ? '<div class="zib-widget nobottom notop">' : '<div class="clear">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($cards as $card) {
$url = (string) ($card['url'] ?? '');
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$card_args = [
'class' => $show_bg ? 'box-body nopw-sm' : 'mb20',
'icon' => (string) ($card['icon'] ?? ''),
'icon_image' => (string) ($card['icon_image'] ?? ''),
'icon_size' => $icon_size > 0 ? (16 + $icon_size) : 0,
'icon_radius' => $icon_radius || !empty($card['icon_radius']),
'icon_color' => (string) ($card['icon_color'] ?? ''),
'icon_class' => (string) ($card['icon_class'] ?? ''),
'title' => (string) ($card['title'] ?? ''),
'desc' => (string) ($card['desc'] ?? ''),
'link' => [
'url' => $url,
'target' => (string) ($card['target'] ?? ''),
],
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if (function_exists('aksrt_icon_card')) {
echo aksrt_icon_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,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 $card) {
if (!is_array($card)) {
continue;
}
$title = sanitize_text_field((string) ($card['title'] ?? ''));
$icon = sanitize_text_field((string) ($card['icon'] ?? ''));
$icon_image = esc_url_raw(trim((string) ($card['icon_image'] ?? $card['customize_icon'] ?? '')));
if ($title === '' && $icon === '' && $icon_image === '') {
continue;
}
$url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? '')));
if (is_array($card['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($card['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($card['link']['target'] ?? ''));
} else {
$target = sanitize_text_field((string) ($card['target'] ?? ''));
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')),
'icon' => $icon,
'icon_image' => $icon_image,
'icon_color' => sanitize_html_class((string) ($card['icon_color'] ?? '')),
'icon_class' => sanitize_html_class((string) ($card['icon_class'] ?? '')),
'url' => $url,
'target' => $target,
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
while (count($cards) < 4) {
$cards[] = ['title' => '', 'icon' => '', 'url' => '', 'desc' => ''];
}
?>
<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('PC 列数:', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
</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_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 2))); ?>">
</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>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius')); ?>" value="1" <?php checked(!empty($instance['icon_radius'])); ?>> <?php esc_html_e('圆形底图标', 'aksrt'); ?></label>
</p>
<hr>
<?php foreach ($cards as $i => $card) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][title]")); ?>" value="<?php echo esc_attr((string) ($card['title'] ?? '')); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($card['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('图标类名', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][desc]")); ?>" value="<?php echo esc_attr((string) ($card['desc'] ?? '')); ?>" placeholder="<?php esc_attr_e('描述', 'aksrt'); ?>">
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($card['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
</div>
<?php endforeach;
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'columns' => max(1, min(6, (int) ($new_instance['columns'] ?? 2))),
'm_row' => max(1, min(4, (int) ($new_instance['m_row'] ?? 2))),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
'icon_radius' => !empty($new_instance['icon_radius']) ? 1 : 0,
'cards' => $this->normalize($new_instance['cards'] ?? []),
]; return $business;
}
}
-18
View File
@@ -1,18 +0,0 @@
<?php
/** Structured marketing widgets. */
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
abstract class MarketingWidgetBase extends \WP_Widget {
protected string $mode = 'cards';
public function __construct(string $id, string $name, string $mode) { $this->mode=$mode; parent::__construct($id, esc_html__('AKSRT '.$name,'aksrt'), ['description'=>esc_html__('结构化营销展示模块','aksrt')]); }
public function form($instance): void { $title=sanitize_text_field((string)($instance['title']??'')); $items=(array)($instance['items']??[]); if(!$items && !empty($instance['items_raw'])) $items=array_filter(array_map('trim',preg_split('/\r?\n/',(string)$instance['items_raw']))); $raw=implode("\n",array_map(static fn($row)=>is_array($row)?implode('|',array_map('strval',$row)):(string)$row,$items)); echo '<p><label>'.esc_html__('标题','aksrt').'</label><input class="widefat" name="'.esc_attr($this->get_field_name('title')).'" value="'.esc_attr($title).'"></p><p><label>'.esc_html__('条目(每行:标题|描述|链接)','aksrt').'</label><textarea class="widefat" rows="6" name="'.esc_attr($this->get_field_name('items_raw')).'">'.esc_textarea($raw).'</textarea></p>'; }
public function update($new_instance,$old_instance): array { $rows=[]; foreach(preg_split('/\r?\n/',(string)($new_instance['items_raw']??'')) as $line){$parts=array_map('trim',explode('|',$line,3)); if(trim(implode('',$parts))==='')continue; $rows[]=['title'=>sanitize_text_field($parts[0]??''),'desc'=>sanitize_text_field($parts[1]??''),'url'=>esc_url_raw($parts[2]??'')];} return ['title'=>sanitize_text_field($new_instance['title']??''),'items'=>$rows]; }
public function widget($args,$instance): void { echo $args['before_widget']; $title=apply_filters('widget_title',$instance['title']??'',$instance,$this->id_base); if($title) echo $args['before_title'].esc_html($title).$args['after_title']; $items=(array)($instance['items']??[]); echo '<div class="aksrt-marketing-block aksrt-marketing-block--'.esc_attr($this->mode).'">'; if(!$items){echo '<p class="widget-empty">'.esc_html__('暂无内容','aksrt').'</p>';} else { foreach($items as $item){$title=sanitize_text_field((string)($item['title']??''));$desc=sanitize_text_field((string)($item['desc']??''));$url=esc_url((string)($item['url']??''));$tag=$url?'<a href="'.$url.'">':'<div>'; echo '<article class="aksrt-marketing-card">'.$tag.'<strong>'.esc_html($title).'</strong>'.($desc!==''?'<p>'.esc_html($desc).'</p>':'').($url?'</a>':'</div>').'</article>'; }} echo '</div>'.$args['after_widget']; }
}
final class PricingPlansWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_pricing_plans','定价计划','pricing');} }
final class TestimonialsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_testimonials','评价轮播','testimonials');} }
final class FaqWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_faq','FAQ 常见问题','faq');} }
final class StatsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_stats','数据统计','stats');} }
final class HighlightsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_highlights','亮点卡片','highlights');} }
final class PartnersWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_partners','合作伙伴滚动','partners');} }
-206
View File
@@ -1,206 +0,0 @@
<?php
/**
* Scrolling notice bar (zibll widget_ui_notice / zib_notice → vertical new-swiper).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class NoticeWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_notice', esc_html__('AKSRT 滚动公告', 'aksrt'), [
'description' => esc_html__('多条公告垂直滚动(共享 Swiper 栈)。', 'aksrt'),
'classname' => 'widget_aksrt_notice',
]);
}
public function widget($args, $instance): void {
$items = $this->normalize_items($instance);
if (!$items) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$color = sanitize_html_class((string) ($instance['color'] ?? 'c-blue'));
$blank = !empty($instance['blank']);
$radius = !empty($instance['radius']);
$interval = max(2, min(15, (int) ($instance['interval'] ?? 4)));
$align = sanitize_html_class((string) ($instance['alignment'] ?? ''));
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_cls = 'aksrt-scroll-notice swiper-bulletin color-' . $color;
if ($radius) {
$wrap_cls .= ' is-radius radius';
}
if ($align !== '') {
$wrap_cls .= ' ' . $align;
}
echo '<div class="' . esc_attr($wrap_cls) . '">';
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="fas fa-bullhorn" aria-hidden="true"></i></div>';
// Prefer shared vertical new-swiper (zib_notice parity) via slider.js Type C.
echo '<div class="new-swiper aksrt-notice-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
echo '<div class="swiper-wrapper">';
foreach ($items as $item) {
$inner = '';
if ($item['icon'] !== '') {
$icon = $item['icon'];
if (strpos($icon, 'fa-') === 0 && strpos($icon, ' ') === false) {
$icon = 'fa ' . $icon;
}
$inner .= '<div class="relative bulletin-icon mr6"><i class="abs-center ' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
}
$inner .= esc_html($item['title']);
echo '<div class="swiper-slide notice-slide">';
if ($item['href'] !== '') {
echo '<a class="text-ellipsis"' .
($blank ? ' target="_blank" rel="noopener noreferrer"' : '') .
' href="' . esc_url($item['href']) . '">' . $inner . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<span class="text-ellipsis">' . $inner . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
* @return array<int, array{title:string,icon:string,href:string}>
*/
private function normalize_items(array $instance): array {
$raw = $instance['items'] ?? $instance['img_ids'] ?? null;
if ($raw === null && !empty($instance['content'])) {
return [[
'title' => wp_strip_all_tags((string) $instance['content']),
'icon' => sanitize_text_field((string) ($instance['icon'] ?? 'fas fa-bullhorn')),
'href' => '',
]];
}
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? $row['content'] ?? ''));
if ($title === '') {
continue;
}
$out[] = [
'title' => $title,
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'href' => esc_url_raw(trim((string) ($row['href'] ?? $row['url'] ?? $row['link'] ?? ''))),
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
public function form($instance): void {
$items = $this->normalize_items($instance);
if (!$items) {
$items = [
['title' => '', 'icon' => 'fas fa-bullhorn', 'href' => ''],
['title' => '', 'icon' => 'fas fa-info-circle', 'href' => ''],
];
}
$json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$colors = [
'c-blue' => __('透明蓝', 'aksrt'),
'c-green' => __('透明绿', 'aksrt'),
'c-yellow' => __('透明黄', 'aksrt'),
'c-red' => __('透明粉红', 'aksrt'),
'c-purple' => __('透明紫', 'aksrt'),
'c-blue-2' => __('透明深蓝', 'aksrt'),
'c-green-2' => __('透明墨绿', 'aksrt'),
'c-yellow-2' => __('透明橘黄', 'aksrt'),
'c-red-2' => __('透明红', 'aksrt'),
'c-purple-2' => __('透明深紫', 'aksrt'),
'b-theme' => __('主题色', 'aksrt'),
'b-red' => __('红色', 'aksrt'),
'b-yellow' => __('黄色', 'aksrt'),
'b-blue' => __('蓝色', 'aksrt'),
'b-green' => __('绿色', 'aksrt'),
'b-purple' => __('紫色', 'aksrt'),
];
?>
<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('items')); ?>" placeholder='[{"title":"公告内容","icon":"fas fa-bullhorn","href":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('主题色彩', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('color')); ?>">
<?php foreach ($colors as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['color'] ?? 'c-blue'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('对齐', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('alignment')); ?>">
<option value="" <?php selected(($instance['alignment'] ?? ''), ''); ?>><?php esc_html_e('靠左', 'aksrt'); ?></option>
<option value="text-center" <?php selected(($instance['alignment'] ?? ''), 'text-center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
<option value="text-right" <?php selected(($instance['alignment'] ?? ''), 'text-right'); ?>><?php esc_html_e('靠右', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?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'] ?? '')),
'items' => $this->normalize_items(['items' => $new_instance['items'] ?? []]),
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'c-blue')),
'alignment' => sanitize_html_class((string) ($new_instance['alignment'] ?? '')),
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'radius' => !empty($new_instance['radius']) ? 1 : 0,
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-214
View File
@@ -1,214 +0,0 @@
<?php
/**
* Recent comments mini list (zibll widget_ui_new_comment → zib_widget_comments).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class RecentCommentsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [
'description' => esc_html__('显示最新评论列表(posts-mini + 身份/点赞)。', 'aksrt'),
]);
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$count = max(1, min(50, (int) ($instance['count'] ?? $instance['limit'] ?? 5)));
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
// zibll: outpost = post__not_in (multi-ID), outer = author__not_in (multi-ID).
// Also keep optional keyword filter via outer_keywords for legacy installs.
$outpost_raw = (string) ($instance['outpost'] ?? '');
$outer_raw = (string) ($instance['outer'] ?? '');
$keywords = sanitize_text_field((string) ($instance['outer_keywords'] ?? ''));
$post_not_in = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $outpost_raw) ?: [])));
// Heuristic: if outer looks numeric/id-list → author__not_in; else treat as keyword filter (legacy).
$author_not_in = [];
$legacy_keys = [];
if ($outer_raw !== '') {
$parts = array_filter(array_map('trim', preg_split('/[,,\s]+/', $outer_raw) ?: []));
$all_numeric = $parts && array_reduce($parts, static function ($ok, $p) {
return $ok && ctype_digit((string) $p);
}, true);
if ($all_numeric) {
$author_not_in = array_values(array_filter(array_map('intval', $parts)));
} else {
$legacy_keys = $parts;
}
}
if ($keywords !== '') {
$legacy_keys = array_merge($legacy_keys, array_filter(array_map('trim', preg_split('/[,,]+/', $keywords) ?: [])));
}
$query = [
'number' => $count * 2, // over-fetch when filtering keywords.
'status' => 'approve',
'post_status' => 'publish',
'post_type' => ['post'],
'orderby' => 'comment_date_gmt',
'order' => 'DESC',
];
if ($post_not_in) {
$query['post__not_in'] = $post_not_in;
}
if ($author_not_in) {
$query['author__not_in'] = $author_not_in;
}
$comments = get_comments($query);
if ($legacy_keys && $comments) {
$comments = array_values(array_filter($comments, static function ($c) use ($legacy_keys) {
$body = (string) $c->comment_content;
foreach ($legacy_keys as $k) {
if ($k !== '' && str_contains($body, $k)) {
return false;
}
}
return true;
}));
}
$comments = array_slice($comments ?: [], 0, $count);
if (!$comments) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无评论', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无评论', 'aksrt') . '</p>';
}
ob_start();
// zibll: .box-body.comment-mini-lists.zib-widget + .posts-mini rows.
echo '<div class="box-body comment-mini-lists zib-widget ajax-item widget-recent-comments">';
foreach ($comments as $comment) {
$cid = (int) $comment->comment_ID;
$user_id = (int) $comment->user_id;
$link = get_comment_link($comment);
$time = human_time_diff(strtotime((string) $comment->comment_date), current_time('timestamp'));
// Content: strip imgs/code like zibll noimg filter.
$cont = get_comment_text($cid);
$cont = convert_smilies($cont);
$cont = preg_replace('/\<img(.*?)\>/i', '[' . __('图片', 'aksrt') . ']', $cont);
$cont = preg_replace('/\[code]([\s\S]*?)\[\/code]/i', '[' . __('代码', 'aksrt') . ']', (string) $cont);
$cont = wp_strip_all_tags((string) $cont);
$cont = wp_trim_words($cont, 40, '…');
// Name with identity.
if ($user_id > 0 && function_exists('aksrt_get_user_name')) {
$user_name = aksrt_get_user_name([
'id' => $user_id,
'class' => 'inflex ac relative-h',
'follow' => false,
'medal' => true,
'level' => true,
'auth' => true,
]);
} elseif ($user_id > 0) {
$home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$user_name = '<a href="' . esc_url($home) . '">' . esc_html(get_comment_author($comment)) . '</a>';
} else {
$user_name = esc_html(get_comment_author($comment));
}
$avatar = '';
if ($show_avatar) {
$avatar_url = $user_id > 0 && function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 42])
: get_avatar_url($comment, ['size' => 42]);
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
$avatar = '<div class="avatar-img"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="" width="36" height="36" loading="lazy" /></div>';
}
$like = function_exists('aksrt_get_comment_like_html')
? aksrt_get_comment_like_html($comment, true)
: '';
echo '<div class="posts-mini">';
echo $avatar; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="posts-mini-con em09 ml10 flex xx jsb">';
echo '<p class="flex jsb">';
echo '<span class="flex1 flex">';
echo $user_name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="flex0 icon-spot muted-3-color" title="' . esc_attr((string) $comment->comment_date) . '">' . esc_html($time) . '</span>';
echo '</span>';
if ($like !== '') {
echo '<span class="ml10 flex0">' . $like . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</p>';
echo '<a class="muted-color text-ellipsis-5" href="' . esc_url($link) . '">' . esc_html($cont) . '</a>';
echo '</div></div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 5))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked(!isset($instance['show_avatar']) || !empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('排除文章 ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outpost')); ?>" value="<?php echo esc_attr((string) ($instance['outpost'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排除作者 ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outer')); ?>" value="<?php echo esc_attr((string) ($instance['outer'] ?? '')); ?>">
</label>
<small><?php esc_html_e('填数字 ID 时按作者排除;填文字时按内容关键词排除(兼容旧配置)。', 'aksrt'); ?></small>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 5))),
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
'outpost' => sanitize_text_field((string) ($new_instance['outpost'] ?? '')),
'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')),
'outer_keywords' => sanitize_text_field((string) ($new_instance['outer_keywords'] ?? '')),
];
}
}
-176
View File
@@ -1,176 +0,0 @@
<?php
/**
* Search box with hot keywords.
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class SearchWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
'description' => esc_html__('搜索框:热门关键词。', 'aksrt'),
'classname' => 'widget_aksrt_search',
]);
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$placeholder = sanitize_text_field((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt')));
if ($placeholder === '' && function_exists('aksrt_get_search_placeholder')) {
$placeholder = aksrt_get_search_placeholder();
}
$show_kw = !empty($instance['show_keywords']);
$kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt')));
$keyword_limit = max(1, min(50, (int) ($instance['keyword_limit'] ?? 12)));
$search_type = function_exists('aksrt_get_default_search_type')
? aksrt_get_default_search_type()
: 'post';
$keywords = [];
if ($show_kw) {
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'hot_search_keywords')) {
$keywords = \AKSRT\Settings\Settings::hot_search_keywords();
} elseif (function_exists('aksrt_get_search_hot_keywords')) {
$keywords = aksrt_get_search_hot_keywords();
} elseif (function_exists('aksrt_get_setting')) {
$raw = (string) aksrt_get_setting('hot_search_keywords', '');
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $raw) ?: [])));
}
$custom = trim((string) ($instance['keywords'] ?? ''));
if ($custom !== '') {
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $custom) ?: [])));
}
$keywords = array_values(array_filter(array_map(static function ($keyword): string {
return is_array($keyword)
? trim((string) ($keyword['keyword'] ?? $keyword['text'] ?? ''))
: trim((string) $keyword);
}, is_array($keywords) ? $keywords : [])));
$keywords = array_values(array_unique($keywords));
}
$keyword_pool = $keywords;
if (count($keywords) > $keyword_limit) {
shuffle($keywords);
$keywords = array_slice($keywords, 0, $keyword_limit);
}
$uid = 'aksrt-sw-' . esc_attr($this->id);
?>
<div class="widget-search-box aksrt-search-widget search-input" id="<?php echo $uid; ?>">
<form
role="search"
method="get"
class="aksrt-search-widget__form search-form"
action="<?php echo esc_url(home_url('/')); ?>"
data-aksrt-search-form="widget"
data-default-search-type="<?php echo esc_attr($search_type); ?>"
>
<div class="aksrt-search-widget__line line-form">
<div class="aksrt-search-widget__field search-input-text">
<label class="screen-reader-text" for="<?php echo $uid; ?>-s"><?php esc_html_e('搜索关键词', 'aksrt'); ?></label>
<input
id="<?php echo $uid; ?>-s"
type="search"
name="s"
class="widget-search-box__input line-form-input"
placeholder="<?php echo esc_attr($placeholder); ?>"
value="<?php echo esc_attr(get_search_query()); ?>"
autocomplete="off"
>
<button type="submit" class="widget-search-box__btn" aria-label="<?php esc_attr_e('搜索', 'aksrt'); ?>">
<i class="fas fa-search" aria-hidden="true"></i>
</button>
</div>
</div>
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($search_type); ?>">
</form>
<?php if ($keywords) : ?>
<div class="aksrt-search-widget__keywords">
<div class="aksrt-search-widget__kw-head">
<?php if ($kw_title !== '') : ?>
<div class="aksrt-search-widget__kw-title"><?php echo esc_html($kw_title); ?></div>
<?php endif; ?>
<button type="button" class="aksrt-search-widget__kw-refresh" data-aksrt-search-keyword-refresh aria-label="<?php esc_attr_e('刷新热门搜索', 'aksrt'); ?>">
<i class="fas fa-sync-alt" aria-hidden="true"></i>
</button>
</div>
<div
class="aksrt-search-widget__kw-list"
data-aksrt-search-keywords
data-keywords="<?php echo esc_attr((string) wp_json_encode($keyword_pool)); ?>"
data-keyword-limit="<?php echo esc_attr((string) $keyword_limit); ?>"
data-search-type="<?php echo esc_attr($search_type); ?>"
data-search-url="<?php echo esc_url(home_url('/')); ?>"
>
<?php foreach ($keywords as $kw) :
$url = function_exists('aksrt_build_search_url')
? aksrt_build_search_url($kw, ['aksrt_search_type' => $search_type])
: add_query_arg(['s' => $kw, 'aksrt_search_type' => $search_type], home_url('/'));
?>
<a class="aksrt-search-widget__kw" href="<?php echo esc_url($url); ?>"><?php echo esc_html($kw); ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
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'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('placeholder')); ?>" value="<?php echo esc_attr((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_keywords')); ?>" value="1" <?php checked(!empty($instance['show_keywords'])); ?>> <?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('keywords_title')); ?>" value="<?php echo esc_attr((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('自定义关键词(逗号/换行;空=后台配置)', 'aksrt'); ?>
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('keywords')); ?>"><?php echo esc_textarea((string) ($instance['keywords'] ?? '')); ?></textarea>
</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('keyword_limit')); ?>" value="<?php echo esc_attr((string) max(1, min(50, (int) ($instance['keyword_limit'] ?? 12)))); ?>">
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'placeholder' => sanitize_text_field((string) ($new_instance['placeholder'] ?? '')),
'show_keywords' => !empty($new_instance['show_keywords']) ? 1 : 0,
'keywords_title' => sanitize_text_field((string) ($new_instance['keywords_title'] ?? '')),
'keywords' => sanitize_textarea_field((string) ($new_instance['keywords'] ?? '')),
'keyword_limit' => max(1, min(50, (int) ($new_instance['keyword_limit'] ?? 12))),
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-391
View File
@@ -1,391 +0,0 @@
<?php
/**
* Multi-tab posts (zibll zib_widget_ui_tab_post + lazy REST).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class TabPostsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tab_posts',
esc_html__('AKSRT 多栏目文章', 'aksrt'),
[
'description' => esc_html__('Tab 切换显示不同分类/排序的文章(懒加载)。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* @param mixed $tabs
* @return list<array{title:string,category_id:int,orderby:string,count:int,enabled:bool}>
*/
public static function normalize_tabs_static($tabs): array {
if (is_string($tabs) && $tabs !== '') {
$legacy_keys = array_map('trim', explode(',', $tabs));
$legacy_defs = [
'latest' => ['title' => '最新', 'orderby' => 'date'],
'comments' => ['title' => '热评', 'orderby' => 'comments'],
'random' => ['title' => '随机', 'orderby' => 'rand'],
'views' => ['title' => '热门', 'orderby' => 'views'],
];
$result = [];
foreach ($legacy_keys as $key) {
if (isset($legacy_defs[$key])) {
$result[] = [
'title' => __($legacy_defs[$key]['title'], 'aksrt'),
'category_id' => 0,
'orderby' => $legacy_defs[$key]['orderby'],
'count' => 0,
'enabled' => true,
];
} elseif (is_numeric($key) && absint($key) > 0) {
$term = get_term(absint($key), 'category');
$result[] = [
'title' => ($term && !is_wp_error($term)) ? $term->name : '',
'category_id' => absint($key),
'orderby' => 'date',
'count' => 0,
'enabled' => true,
];
}
}
return $result;
}
if (is_array($tabs)) {
$allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
$result = [];
foreach ($tabs as $tab) {
if (!is_array($tab)) {
continue;
}
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed, true)) {
$orderby = 'date';
}
$result[] = [
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'enabled' => !empty($tab['enabled']),
];
}
return $result;
}
return [];
}
private function normalize_tabs($tabs): array {
return self::normalize_tabs_static($tabs);
}
/**
* Build query for a tab → reuse main posts payload renderer.
*
* @param array $tab
* @param int $default_count
* @return array
*/
public static function tab_to_main_instance(array $tab, int $default_count, array $opts = []): array {
$count = !empty($tab['count']) ? (int) $tab['count'] : $default_count;
$style = sanitize_key((string) ($opts['style'] ?? $tab['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($opts['paginate'] ?? $tab['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
return [
'count' => max(1, min(30, $count)),
'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => absint($tab['category_id'] ?? 0),
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
'style' => $style,
'show_thumb' => !isset($opts['show_thumb']) ? 1 : (int) $opts['show_thumb'],
'show_meta' => !isset($opts['show_meta']) ? 1 : (int) $opts['show_meta'],
'show_number' => !empty($opts['show_number']) ? 1 : 0,
'paginate' => $paginate,
];
}
/**
* REST helper: render one tab list HTML.
*
* @param array<string,mixed> $opts
*/
public static function render_tab_html(array $tab, int $default_count, array $opts = []): string {
$payload = self::render_tab_payload($tab, $default_count, $opts, 1);
return (string) ($payload['html'] ?? '');
}
/**
* Full tab payload (html + pager) for REST/ajax.
*
* @param array<string,mixed> $opts
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function render_tab_payload(array $tab, int $default_count, array $opts = [], int $page = 1): array {
$inst = self::tab_to_main_instance($tab, $default_count, $opts);
if (class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
return WidgetMainPosts::query_payload($inst, $page);
}
return [
'html' => '',
'paginate_html' => '',
'has_more' => false,
'page' => 1,
'max_pages' => 1,
'total' => 0,
];
}
/** Skeleton rows for lazy tab panels. */
public static function skeleton_html(int $count = 5, string $style = 'mini'): string {
$count = max(1, min(12, $count));
ob_start();
echo '<div class="widget-tab-posts__skeleton posts-mini-lists style-' . esc_attr($style) . '">';
for ($i = 0; $i < $count; $i++) {
echo '<div class="posts-mini placeholder-row">';
echo '<div class="placeholder radius thumb-placeholder"></div>';
echo '<div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div>';
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
$count = absint($instance['count'] ?? 5);
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
$tabs = array_values(array_filter($tabs, static fn($t) => !empty($t['enabled'])));
if (!$tabs) {
if (current_user_can('manage_options')) {
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请至少启用一个 Tab', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请至少启用一个 Tab', 'aksrt') . '</p>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$widget_id = $this->id;
$rest = rest_url('aksrt/v1/widgets/tab-posts');
$use_shared = function_exists('aksrt_render_mini_list');
?>
<div class="widget-tab-posts" id="<?php echo esc_attr($widget_id); ?>-tabs"
data-aksrt-tab-posts
data-rest="<?php echo esc_url($rest); ?>"
data-count="<?php echo esc_attr((string) $count); ?>"
data-style="<?php echo esc_attr($style); ?>"
data-paginate="<?php echo esc_attr($paginate); ?>">
<ul class="widget-tab-posts__nav" role="tablist">
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
$tab_title = $tab['title'] !== '' ? $tab['title'] : sprintf(__('Tab %d', 'aksrt'), $i + 1);
?>
<li role="presentation">
<button type="button" role="tab"
class="widget-tab-posts__tab<?php echo $first ? ' active' : ''; ?>"
data-tab="<?php echo esc_attr($tab_key); ?>"
data-index="<?php echo (int) $i; ?>"
data-orderby="<?php echo esc_attr($tab['orderby']); ?>"
data-cat="<?php echo (int) $tab['category_id']; ?>"
data-tab-count="<?php echo (int) $tab['count']; ?>"
data-limit-day="<?php echo esc_attr((string) ((int) ($tab['limit_day'] ?? 0))); ?>"
data-pay-type="<?php echo esc_attr((string) ($tab['pay_type'] ?? '')); ?>"
aria-selected="<?php echo $first ? 'true' : 'false'; ?>"
><?php echo esc_html($tab_title); ?></button>
</li>
<?php $first = false; endforeach; ?>
</ul>
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
?>
<div class="widget-tab-posts__panel<?php echo $first ? ' active' : ''; ?>"
data-tab-panel="<?php echo esc_attr($tab_key); ?>"
data-loaded="<?php echo $first ? '1' : '0'; ?>"
role="tabpanel"<?php echo $first ? '' : ' hidden'; ?>>
<?php if ($first) :
$opts = ['style' => $style, 'paginate' => $paginate];
$payload = self::render_tab_payload($tab, $count, $opts, 1);
$html = (string) ($payload['html'] ?? '');
if ($html !== '') {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--' . esc_attr($style) . ' aksrt-main-posts style-' . esc_attr($style) . '">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (!empty($payload['paginate_html'])) {
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
} else {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无文章', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
else : ?>
<div class="post_ajax_loader" data-aksrt-ajax-loader><?php echo self::skeleton_html($count, $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
<?php endif; ?>
</div>
<?php $first = false; endforeach; ?>
</div>
<?php
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = $instance['title'] ?? '';
$count = (int) ($instance['count'] ?? 5);
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
$orderby_options = [
'date' => __('最新', 'aksrt'),
'modified' => __('更新', 'aksrt'),
'views' => __('浏览', 'aksrt'),
'likes' => __('点赞', 'aksrt'),
'comments' => __('评论', 'aksrt'),
'favorites' => __('收藏', 'aksrt'),
'downloads' => __('下载', 'aksrt'),
'rand' => __('随机', 'aksrt'),
];
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr((string) $title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('默认每个 Tab 显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="20" value="<?php echo (int) $count; ?>">
</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="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?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('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<hr style="margin:12px 0;">
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('Tab 配置', 'aksrt'); ?></p>
<?php
$max_tabs = 8;
$tabs = array_slice($tabs, 0, $max_tabs);
while (count($tabs) < 3) {
$tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'limit_day' => 0, 'pay_type' => '', 'enabled' => false];
}
foreach ($tabs as $i => $tab) :
?>
<div style="border:1px solid #ddd;padding:8px;margin-bottom:8px;border-radius:4px;background:#fafafa;">
<p style="margin:0 0 6px;">
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][enabled]")); ?>" value="1" <?php checked(!empty($tab['enabled'])); ?>> <?php printf(esc_html__('Tab %d 启用', 'aksrt'), $i + 1); ?></label>
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][title]")); ?>" value="<?php echo esc_attr($tab['title']); ?>" placeholder="<?php esc_attr_e('留空则使用分类名', 'aksrt'); ?>">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('分类 ID:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][category_id]")); ?>" value="<?php echo (int) $tab['category_id']; ?>" placeholder="0">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('排序:', 'aksrt'); ?></label>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][orderby]")); ?>">
<?php foreach ($orderby_options as $val => $label) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($tab['orderby'], $val); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</p>
<p style="margin:0;">
<label><?php esc_html_e('数量(0=使用默认):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][count]")); ?>" value="<?php echo (int) $tab['count']; ?>">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('最近 N 天(0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][limit_day]")); ?>" value="<?php echo (int) ($tab['limit_day'] ?? 0); ?>">
</p>
<p style="margin:0;">
<label><?php esc_html_e('付费类型:', 'aksrt'); ?></label>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][pay_type]")); ?>">
<option value="" <?php selected(($tab['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($tab['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($tab['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($tab['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
</select>
</p>
</div>
<?php endforeach;
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$result = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'style' => $style,
'paginate' => $paginate,
];
$tabs = [];
if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) {
$allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
foreach ($new_instance['tabs'] as $tab) {
if (!is_array($tab)) {
continue;
}
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed, true)) {
$orderby = 'date';
}
$tabs[] = [
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
'enabled' => !empty($tab['enabled']),
];
}
}
$result['tabs'] = !empty($tabs) ? $tabs : 'latest,comments,random';
return $result;
}
}
-192
View File
@@ -1,192 +0,0 @@
<?php
/**
* Tag cloud (zibll widget_ui_tag_cloud lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class TagCloudWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tag_cloud',
esc_html__('AKSRT 标签云', 'aksrt'),
[
'description' => esc_html__('展示文章标签/分类云', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'post_tag'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'post_tag';
}
$number = max(1, min(100, (int) ($instance['count'] ?? $instance['number'] ?? 20)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'name'));
// Zibll widget_ui_tag_cloud: name / count / term_id(创建时间).
if (!in_array($orderby, ['count', 'name', 'term_id', 'id'], true)) {
$orderby = 'name';
}
// Zibll `order` ASC/DESC (default ASC).
$order = strtoupper((string) ($instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC';
$show_count = !empty($instance['show_count']);
$blank = !empty($instance['blank']);
$color = sanitize_html_class((string) ($instance['color'] ?? 'rand'));
$fixed_width = !empty($instance['fixed_width']);
$wp_orderby = $orderby === 'id' ? 'term_id' : $orderby;
$tags = get_terms([
'taxonomy' => $taxonomy,
'orderby' => $wp_orderby,
'order' => $order,
'number' => $number,
'hide_empty' => !empty($instance['hide_empty']),
]);
if (is_wp_error($tags) || !$tags) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无标签', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无标签', 'aksrt') . '</p>';
}
$palette = ['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red', '', 'c-blue-2', 'c-yellow-2', 'c-green-2', 'c-purple-2', 'c-red-2', ''];
$i = random_int(0, 10);
$scale = !empty($instance['scale']);
$min_c = null;
$max_c = null;
if ($scale) {
foreach ($tags as $t0) {
$c0 = (int) $t0->count;
$min_c = $min_c === null ? $c0 : min($min_c, $c0);
$max_c = $max_c === null ? $c0 : max($max_c, $c0);
}
}
$wrap = 'zib-widget widget-tag-cloud tag-cloud aksrt-tag-cloud author-tag ajax-item' . ($fixed_width ? ' fixed-width' : '');
ob_start();
echo '<div class="' . esc_attr($wrap) . '">';
foreach ($tags as $tag) {
$url = get_term_link($tag);
if (is_wp_error($url)) {
continue;
}
$cls = 'but';
if ($color === 'rand') {
if ($i > 10) {
$i = 0;
}
$cls .= ' ' . $palette[$i];
$i++;
} elseif ($color !== '') {
$cls .= ' ' . $color;
}
$tgt = $blank ? ' target="_blank" rel="noopener noreferrer"' : '';
$count_html = $show_count ? '<span class="em09 tag-count"> (' . (int) $tag->count . ')</span>' : '';
$style_attr = '';
if ($scale && $max_c !== null && $min_c !== null) {
$span = max(1, $max_c - $min_c);
$ratio = ((int) $tag->count - $min_c) / $span;
$em = round(0.85 + ($ratio * 0.4), 3);
$style_attr = ' style="font-size:' . esc_attr((string) $em) . 'em;--tag-scale:' . esc_attr((string) $em) . ';"';
}
echo '<a href="' . esc_url($url) . '" class="text-ellipsis ' . esc_attr(trim($cls)) . '"' . $tgt . $style_attr . '>' . esc_html($tag->name) . $count_html . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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'] ?? __('标签云', 'aksrt'))); ?>">
</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'] ?? 'post_tag'), $tax->name); ?>><?php echo esc_html($tax->labels->name); ?></option>
<?php endforeach; ?>
</select>
</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('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 20))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="name" <?php selected(($instance['orderby'] ?? 'name'), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="count" <?php selected(($instance['orderby'] ?? ''), 'count'); ?>><?php esc_html_e('文章数量', 'aksrt'); ?></option>
<option value="term_id" <?php selected(($instance['orderby'] ?? ''), 'term_id'); ?>><?php esc_html_e('创建时间', 'aksrt'); ?></option>
</select>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="ASC" <?php selected(($instance['order'] ?? 'ASC'), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
<option value="DESC" <?php selected(($instance['order'] ?? ''), 'DESC'); ?>><?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('color')); ?>">
<option value="rand" <?php selected(($instance['color'] ?? 'rand'), 'rand'); ?>><?php esc_html_e('随机颜色', 'aksrt'); ?></option>
<option value="c-hui" <?php selected(($instance['color'] ?? ''), 'c-hui'); ?>><?php esc_html_e('灰色', 'aksrt'); ?></option>
<option value="c-blue" <?php selected(($instance['color'] ?? ''), 'c-blue'); ?>><?php esc_html_e('蓝色', 'aksrt'); ?></option>
<option value="c-blue-2" <?php selected(($instance['color'] ?? ''), 'c-blue-2'); ?>><?php esc_html_e('深蓝色', 'aksrt'); ?></option>
<option value="c-cyan" <?php selected(($instance['color'] ?? ''), 'c-cyan'); ?>><?php esc_html_e('青色', 'aksrt'); ?></option>
<option value="c-yellow" <?php selected(($instance['color'] ?? ''), 'c-yellow'); ?>><?php esc_html_e('黄色', 'aksrt'); ?></option>
<option value="c-yellow-2" <?php selected(($instance['color'] ?? ''), 'c-yellow-2'); ?>><?php esc_html_e('橙黄色', 'aksrt'); ?></option>
<option value="c-green" <?php selected(($instance['color'] ?? ''), 'c-green'); ?>><?php esc_html_e('绿色', 'aksrt'); ?></option>
<option value="c-green-2" <?php selected(($instance['color'] ?? ''), 'c-green-2'); ?>><?php esc_html_e('墨绿色', 'aksrt'); ?></option>
<option value="c-purple" <?php selected(($instance['color'] ?? ''), 'c-purple'); ?>><?php esc_html_e('紫色', 'aksrt'); ?></option>
<option value="c-purple-2" <?php selected(($instance['color'] ?? ''), 'c-purple-2'); ?>><?php esc_html_e('深紫色', 'aksrt'); ?></option>
<option value="c-red" <?php selected(($instance['color'] ?? ''), 'c-red'); ?>><?php esc_html_e('粉红色', 'aksrt'); ?></option>
<option value="c-red-2" <?php selected(($instance['color'] ?? ''), 'c-red-2'); ?>><?php esc_html_e('红色', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('fixed_width')); ?>" value="1" <?php checked(!empty($instance['fixed_width'])); ?>> <?php esc_html_e('固定宽度', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空标签', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="1" <?php checked(!empty($instance['scale'])); ?>> <?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'] ?? 'post_tag')),
'count' => max(1, min(100, (int) ($new_instance['count'] ?? 20))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'term_id', 'id'], true) ? $new_instance['orderby'] : 'name',
'order' => strtoupper((string) ($new_instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'rand')),
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'fixed_width' => !empty($new_instance['fixed_width']) ? 1 : 0,
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'scale' => !empty($new_instance['scale']) ? 1 : 0,
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-175
View File
@@ -1,175 +0,0 @@
<?php
/**
* Announcement marquee → vertical notice swiper (zibll notice family parity).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetAnnouncement extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_announcement',
__('AKSRT 公告跑马灯', 'aksrt'),
[
'description' => __('滚动显示公告/通知(共享垂直 Swiper)。', 'aksrt'),
'classname' => 'widget_aksrt_announcement',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
$speed = !empty($instance['speed']) ? max(5, min(30, (int) $instance['speed'])) : 10;
$mode = sanitize_key((string) ($instance['mode'] ?? 'swiper'));
if (!in_array($mode, ['swiper', 'marquee'], true)) {
$mode = 'swiper';
}
$interval = max(2, min(15, (int) ($instance['interval'] ?? max(3, (int) round($speed / 2)))));
$blank = !empty($instance['blank']);
$lines = array_values(array_filter(array_map('trim', explode("\n", $items))));
if (!$lines) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($mode === 'marquee') {
// Legacy CSS marquee path.
echo '<div class="aksrt-announcement">';
echo '<span class="aksrt-announcement__icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></span>';
echo '<div class="aksrt-announcement__track">';
echo '<ul class="aksrt-announcement__list" style="animation-duration: ' . esc_attr((string) $speed) . 's;">';
$render = function (string $line) use ($blank): void {
$parts = explode('|', $line, 2);
$text = trim($parts[0]);
$link = isset($parts[1]) ? trim($parts[1]) : '';
echo '<li class="aksrt-announcement__item">';
if ($link !== '') {
echo '<a href="' . esc_url($link) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . esc_html($text) . '</a>';
} else {
echo esc_html($text);
}
echo '</li>';
};
foreach ($lines as $line) {
$render($line);
}
// Duplicate first for seamless CSS loop.
$render($lines[0]);
echo '</ul></div></div>';
} else {
// Shared vertical new-swiper (same stack as NoticeWidget).
echo '<div class="aksrt-scroll-notice aksrt-announcement-swiper-wrap swiper-bulletin">';
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
echo '<div class="new-swiper aksrt-notice-swiper aksrt-announcement-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
echo '<div class="swiper-wrapper">';
foreach ($lines as $line) {
$parts = explode('|', $line, 2);
$text = trim($parts[0]);
$link = isset($parts[1]) ? trim($parts[1]) : '';
echo '<div class="swiper-slide notice-slide">';
if ($link !== '') {
echo '<a class="text-ellipsis"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . ' href="' . esc_url($link) . '">' . esc_html($text) . '</a>';
} else {
echo '<span class="text-ellipsis">' . esc_html($text) . '</span>';
}
echo '</div>';
}
echo '</div></div></div>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
$speed = !empty($instance['speed']) ? (int) $instance['speed'] : 10;
$mode = (string) ($instance['mode'] ?? 'swiper');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('items')); ?>"><?php esc_html_e('公告内容(每行一条):', 'aksrt'); ?></label>
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('items')); ?>" name="<?php echo esc_attr($this->get_field_name('items')); ?>" rows="5" placeholder="系统维护通知&#10;新功能上线|https://example.com"><?php echo esc_textarea($items); ?></textarea>
<small><?php esc_html_e('每行一条,格式:文本 或 文本|链接', 'aksrt'); ?></small>
</p>
<p>
<label><?php esc_html_e('模式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('mode')); ?>">
<option value="swiper" <?php selected($mode, 'swiper'); ?>><?php esc_html_e('垂直轮播(推荐)', 'aksrt'); ?></option>
<option value="marquee" <?php selected($mode, 'marquee'); ?>><?php esc_html_e('横向跑马灯', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>" name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text" value="<?php echo esc_attr($icon); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('speed')); ?>"><?php esc_html_e('滚动速度(秒,跑马灯):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('speed')); ?>" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" type="number" value="<?php echo esc_attr((string) $speed); ?>" min="5" max="30">
</p>
<p>
<label><?php esc_html_e('轮播间隔(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$mode = sanitize_key((string) ($new_instance['mode'] ?? 'swiper'));
if (!in_array($mode, ['swiper', 'marquee'], true)) {
$mode = 'swiper';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'items' => sanitize_textarea_field((string) ($new_instance['items'] ?? '')),
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-bullhorn')),
'speed' => max(5, min(30, (int) ($new_instance['speed'] ?? 10))),
'mode' => $mode,
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
'blank' => !empty($new_instance['blank']) ? 1 : 0,
]; return $business;
}
}
-117
View File
@@ -1,117 +0,0 @@
<?php
/**
* Post author card (zibll widget_ui_avatar + zib_get_user_card_box depth).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetAuthorCard extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_author_card',
esc_html__('AKSRT 文章作者卡片', 'aksrt'),
[
'description' => esc_html__('在文章/帖子侧栏显示当前内容作者信息。', 'aksrt'),
'classname' => 'widget_aksrt_author_card',
]
);
}
public function widget($args, $instance): void {
$author_id = 0;
if (is_singular()) {
$post = get_queried_object();
if ($post instanceof \WP_Post) {
$author_id = (int) $post->post_author;
}
}
if ($author_id <= 0) {
return;
}
$user = get_userdata($author_id);
if (!$user) {
return;
}
$show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']);
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($author_id, ['size' => 120])
: get_avatar_url($author_id, ['size' => 120]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($author_id)
: get_author_posts_url($author_id);
$bio = get_user_meta($author_id, 'description', true);
if (!is_string($bio) || $bio === '') {
$bio = (string) $user->description;
}
$cover = (string) get_user_meta($author_id, 'aksrt_background_image', true);
if ($cover === '') {
$cover = get_theme_file_uri('assets/img/person_background.png');
}
$post_count = count_user_posts($author_id, 'post', true);
$followers = 0;
if (class_exists('\AKSRT\User\Follow')) {
$followers = \AKSRT\User\Follow::get_instance()->get_follower_count($author_id);
}
$name_html = function_exists('aksrt_get_user_name')
? aksrt_get_user_name(['id' => $author_id, 'follow' => false, 'class' => 'flex ac jc'])
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$card_cls = 'aksrt-author-card user-card author has-cover';
echo '<div class="' . esc_attr($card_cls) . '">';
$bg = ' style="background-image:url(' . esc_url($cover) . ')"';
echo '<div class="aksrt-author-card__cover user-cover"' . $bg . '></div>';
echo '<div class="aksrt-author-card__body card-content">';
echo '<div class="user-content">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<a class="aksrt-author-card__avatar user-avatar" href="' . esc_url($home) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url($ph) . '" alt="" width="72" height="72" loading="lazy" /></a>';
echo '<div class="aksrt-author-card__name user-name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($bio !== '') {
echo '<div class="aksrt-author-card__bio user-desc muted-2-color em09">' . esc_html(wp_trim_words($bio, 28)) . '</div>';
}
if ($show_stats) {
echo '<div class="aksrt-author-card__stats">';
echo '<span><b>' . esc_html((string) $post_count) . '</b>' . esc_html__('文章', 'aksrt') . '</span>';
echo '<span><b>' . esc_html((string) $followers) . '</b>' . esc_html__('粉丝', 'aksrt') . '</span>';
echo '</div>';
}
$cuid = get_current_user_id();
echo '<div class="aksrt-author-card__actions user-btns">';
if ($cuid > 0 && $cuid !== $author_id && class_exists('\AKSRT\User\Follow')) {
$following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $author_id);
$label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
echo '<button type="button" class="aksrt-author-card__btn aksrt-author-card__follow but' . ($following ? ' is-following' : '') . '" data-aksrt-follow="' . (int) $author_id . '">' . esc_html($label) . '</button>';
}
echo '<a class="aksrt-author-card__btn but" href="' . esc_url($home) . '">' . esc_html__('查看主页', 'aksrt') . '</a>';
echo '</div>';
echo '</div>'; // .user-content
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
?>
<p class="description"><?php esc_html_e('自动读取当前文章/帖子作者。', 'aksrt'); ?></p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
];
}
}
-253
View File
@@ -1,253 +0,0 @@
<?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) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张组合信息卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张组合信息卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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;--zci-radius:%dpx;--zci-band-h:%dpx;', $radius, $band_h, $pc_row, $m_row, $radius, $band_h);
$wrap = 'aksrt-combo-info zib-combo-info-card row gutters-10' . ($top_band ? ' has-band zci-has-band' : '');
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$col = 'col-sm-' . $pc_span . ' col-xs-' . $m_span;
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 === '') {
continue;
}
echo '<div class="' . esc_attr($col) . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
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 {
$business = [
'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,
]; return $business;
}
}
-227
View File
@@ -1,227 +0,0 @@
<?php
/**
* Graphic cover cards (zibll zib_widget_ui_graphic_cover → aksrt_graphic_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetCoverCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_cover_cards',
__('AKSRT 图文封面卡片', 'aksrt'),
['description' => __('图片/视频封面 + 文字叠加的图文卡片(共享 graphic_card)。', 'aksrt')]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = $this->normalize($instance['items'] ?? []);
if (!$items) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张封面卡片', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张封面卡片', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
$height_scale = max(30, min(120, (int) ($instance['height_scale'] ?? 70)));
$mask_opacity = max(0, min(80, (int) ($instance['mask_opacity'] ?? 20)));
$font_size_pc = max(12, min(32, (int) ($instance['font_size_pc'] ?? 18)));
$font_size_m = max(11, min(24, (int) ($instance['font_size_m'] ?? 14)));
$font_bold = !empty($instance['font_bold']);
$font_color = sanitize_hex_color((string) ($instance['font_color'] ?? '')) ?: '';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$is_row = count($items) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
$style = '';
if ($font_size_pc !== 14) {
$style .= '--font-size:' . $font_size_pc . 'px;';
}
if ($font_size_m !== 14) {
$style .= '--font-size-sm:' . $font_size_m . 'px;';
}
if ($font_bold) {
$style .= '--font-weight:bold;--font-weight-sm:bold;';
}
if ($font_color !== '') {
$style .= '--color:' . $font_color . ';--color-sm:' . $font_color . ';';
}
echo '<div class="widget-graphic-cover aksrt-cover-cards' . ($is_row ? ' mb10' : ' mb20') . '"' . ($style !== '' ? ' style="' . esc_attr($style) . '"' : '') . '>';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($items as $item) {
$hide = sanitize_key((string) ($item['hide'] ?? ''));
if ($hide === 'pc' || $hide === 'm') {
$is_mobile = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
if ((!$is_mobile && $hide === 'pc') || ($is_mobile && $hide === 'm')) {
continue;
}
}
$card_title = (string) ($item['title'] ?? '');
$more = $card_title !== ''
? '<div class="abs-center text-center graphic-text this-font">' . esc_html($card_title) . '</div>'
: '';
$card = [
'class' => 'noshadow mb10',
'img' => (string) ($item['image'] ?? ''),
'video' => (string) ($item['video'] ?? ''),
'alt' => wp_strip_all_tags($card_title),
'link' => [
'url' => (string) ($item['link'] ?? ''),
'target' => (string) ($item['target'] ?? ''),
],
'lazy' => true,
'more' => $more,
'height_scale' => !empty($item['height']) && is_numeric($item['height'])
? (int) $item['height']
: $height_scale,
'mask_opacity' => isset($item['mask']) ? (int) $item['mask'] : $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @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;
}
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
$video = esc_url_raw(trim((string) ($row['video'] ?? '')));
if ($image === '' && $video === '') {
continue;
}
$out[] = [
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'image' => $image,
'video' => $video,
'link' => esc_url_raw(trim((string) ($row['link'] ?? ''))),
'target' => sanitize_text_field((string) ($row['target'] ?? '')),
'hide' => sanitize_key((string) ($row['hide'] ?? '')),
'height' => isset($row['height']) ? (int) $row['height'] : 0,
'mask' => isset($row['mask']) ? (int) $row['mask'] : null,
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$items = $this->normalize($instance['items'] ?? []);
$json = wp_json_encode($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('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'] ?? $instance['columns'] ?? 2))); ?>">
</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('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="120" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 70))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-80', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="80" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 20))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('items')); ?>" placeholder='[{"image":"https://...","title":"标题","link":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
<small><?php esc_html_e('字段: image, video, title, link, target, hide(pc|m), height, mask', 'aksrt'); ?></small>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(6, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'height_scale' => max(30, min(120, (int) ($new_instance['height_scale'] ?? 70))),
'mask_opacity' => max(0, min(80, (int) ($new_instance['mask_opacity'] ?? 20))),
'font_size_pc' => max(12, min(32, (int) ($new_instance['font_size_pc'] ?? 18))),
'font_size_m' => max(11, min(24, (int) ($new_instance['font_size_m'] ?? 14))),
'font_bold' => !empty($new_instance['font_bold']) ? 1 : 0,
'font_color' => sanitize_hex_color((string) ($new_instance['font_color'] ?? '')) ?: '',
'items' => $this->normalize($new_instance['items'] ?? []),
]; return $business;
}
}
-207
View File
@@ -1,207 +0,0 @@
<?php
/**
* Icon cover cards (zibll zib_widget_ui_icon_cover_card → aksrt_icon_cover_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetIconCoverCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_icon_cover_cards',
esc_html__('AKSRT 图标封面卡', 'aksrt'),
[
'description' => esc_html__('横向图标 + 标题/描述的导航卡片。', 'aksrt'),
'classname' => 'widget_aksrt_icon_cover_cards',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
if (!$cards) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张图标封面卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标封面卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(4, (int) ($instance['pc_row'] ?? 2)));
$m_row = max(1, min(2, (int) ($instance['m_row'] ?? 1)));
$icon_size = max(16, min(48, (int) ($instance['icon_size'] ?? 25)));
$radius = !empty($instance['icon_radius4']);
$go_link = !empty($instance['go_link']);
$show_bg = !empty($instance['show_widget_bg']);
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
}
$is_row = count($cards) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
echo $show_bg ? '<div class="zib-widget padding-10">' : '<div class="mb10">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($cards as $card) {
$url = (string) ($card['url'] ?? '');
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$card_args = [
'class' => $show_bg ? 'padding-10' : 'zib-widget mb10',
'icon' => (string) ($card['icon'] ?? ''),
'icon_image' => (string) ($card['icon_image'] ?? ''),
'icon_size' => $icon_size,
'icon_class' => $radius ? 'radius4' : '',
'icon_color' => (string) ($card['icon_color'] ?? ''),
'title' => (string) ($card['title'] ?? ''),
'desc' => (string) ($card['desc'] ?? ''),
'link' => [
'url' => $url,
'target' => (string) ($card['target'] ?? ''),
],
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_icon_cover_card')) {
echo aksrt_icon_cover_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,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;
}
$title = sanitize_text_field((string) ($row['title'] ?? ''));
$icon = sanitize_text_field((string) ($row['icon'] ?? ''));
$icon_image = esc_url_raw(trim((string) ($row['icon_image'] ?? $row['customize_icon'] ?? '')));
if ($title === '' && $icon === '' && $icon_image === '') {
continue;
}
$url = '';
$target = '';
if (is_array($row['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($row['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($row['link']['target'] ?? ''));
} else {
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
$target = sanitize_text_field((string) ($row['target'] ?? '_blank'));
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'icon' => $icon,
'icon_image' => $icon_image,
'icon_color' => sanitize_html_class((string) ($row['icon_color'] ?? '')),
'url' => $url,
'target' => $target,
];
if (count($out) >= 16) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
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="7" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"文档","desc":"使用说明","icon":"fas fa-book","url":"https://"}]'><?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="4" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="2" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
<label><?php esc_html_e('图标字号', 'aksrt'); ?>
<input class="tiny-text" type="number" min="16" max="48" name="<?php echo esc_attr($this->get_field_name('icon_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['icon_size'] ?? 25))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius4')); ?>" value="1" <?php checked(!empty($instance['icon_radius4'])); ?>> <?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'])); ?>> <?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
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'cards' => $this->normalize($new_instance['cards'] ?? []),
'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))),
'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))),
'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0,
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
]; return $business;
}
}
-157
View File
@@ -1,157 +0,0 @@
<?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',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$raw = trim((string) ($instance['url'] ?? ''));
if ($raw === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请配置 URL 或 iframe 代码', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('超级嵌入:', 'aksrt') . '</b>' . esc_html__('请配置 URL 或 iframe 代码', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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,
'framespacing' => true,
'border' => true,
],
]
);
// Prefer lazy: if iframe has src but no data-src, convert.
if (preg_match('/<iframe\b[^>]*>/i', $iframe_html, $m)
&& stripos($m[0], 'data-src=') === false
&& preg_match('/\ssrc=["\']([^"\']+)["\']/i', $m[0], $sm)
) {
$src = $sm[1];
$tag = preg_replace('/\ssrc=["\'][^"\']*["\']/i', ' src="" data-src="' . esc_url($src) . '"', $m[0], 1);
if (is_string($tag) && stripos($tag, 'lazyload') === false) {
if (preg_match('/\sclass=["\']([^"\']*)["\']/', $tag)) {
$tag = preg_replace('/\sclass=["\']([^"\']*)["\']/', ' class="$1 lazyload aksrt-lazy"', $tag, 1);
} else {
$tag = preg_replace('/^<iframe\b/i', '<iframe class="lazyload aksrt-lazy"', $tag, 1);
}
}
if (is_string($tag)) {
$iframe_html = str_replace($m[0], $tag, $iframe_html);
}
}
} else {
$url = esc_url($raw);
if ($url === '') {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$fs = $allow_fs ? ' allowfullscreen="allowfullscreen"' : '';
// zibll: class="lazyload" + data-src (not immediate src).
$iframe_html = '<iframe class="lazyload aksrt-lazy aksrt-iframe-el" src="" data-src="' . $url . '" loading="lazy" frameborder="0" framespacing="0" border="0"' . $fs . '></iframe>';
}
// zibll shell: mb20 > wp-block-embed is-type-video relative-h radius8 > padding-bottom.
echo '<div class="mb20 aksrt-iframe-embed">';
echo '<div class="wp-block-embed is-type-video relative-h radius8">';
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></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'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,
]; return $business;
}
}
-459
View File
@@ -1,459 +0,0 @@
<?php
/**
* Main posts list (zibll zib_widget_ui_main_post + ajax pager).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetMainPosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_main_posts',
esc_html__('AKSRT 文章列表', 'aksrt'),
[
'description' => esc_html__('卡片/迷你/列表样式的文章模块(支持 AJAX)。', 'aksrt'),
'classname' => 'widget_aksrt_main_posts',
]
);
}
/**
* @param array $instance
* @param int $paged
* @return array
*/
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
// Zibll `order` parity: asc/desc. Default desc.
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($cat > 0) {
$query['cat'] = $cat;
}
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = $order;
break;
case 'likes':
$query['meta_key'] = '_aksrt_like_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'favorites':
$query['meta_key'] = '_aksrt_favorite_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'downloads':
$query['meta_key'] = '_aksrt_res_download_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'rand':
$query['orderby'] = 'rand';
break;
case 'modified':
$query['orderby'] = 'modified';
$query['order'] = $order;
break;
default:
$query['orderby'] = 'date';
$query['order'] = $order;
}
// Pay type filter (reading/download/gallery/video/vip or any paid).
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
$mq = isset($query['meta_query']) && is_array($query['meta_query']) ? $query['meta_query'] : [];
if ($pay_type === 'any' || $pay_type === 'paid') {
$mq[] = [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_pay_type',
'value' => ['reading', 'download', 'gallery', 'video', 'vip'],
'compare' => 'IN',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
];
} else {
$mq[] = [
'key' => '_aksrt_pay_type',
'value' => $pay_type,
];
}
if ($mq) {
if (count($mq) > 1) {
$mq['relation'] = 'AND';
}
$query['meta_query'] = $mq;
}
}
return $query;
}
public static function render_items_html(\WP_Query $q, array $instance): string {
if (!$q->have_posts()) {
return '';
}
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
$show_meta = !isset($instance['show_meta']) || !empty($instance['show_meta']);
$show_number = !empty($instance['show_number']);
$start = max(0, (int) ($instance['_start_index'] ?? 0));
ob_start();
// Outer wrapper only on page 1 full render handled by caller for ajax append items.
$mode = $style === 'card' ? 'card' : ($style === 'mini' ? 'mini' : 'list');
if (function_exists('aksrt_render_post')) {
$html = '';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$html .= aksrt_render_post([
'mode' => $mode,
'context' => 'widget',
'show_thumb' => $show_thumb,
'show_meta' => $show_meta,
'show_excerpt' => $mode === 'list',
'show_number' => $show_number && $mode === 'mini',
'number_offset' => $show_number ? $i : 0,
'extra_class' => 'ajax-item',
]);
}
wp_reset_postdata();
return $html;
}
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$thumb = $show_thumb ? get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail') : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
if ($style === 'card') {
echo '<a class="aksrt-main-posts__card ajax-item" href="' . esc_url(get_permalink()) . '">';
echo '<div class="aksrt-main-posts__thumb' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</div>';
echo '<div class="aksrt-main-posts__body">';
echo '<div class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</div>';
if ($show_meta) {
echo '<div class="aksrt-main-posts__meta"><span>' . esc_html(get_the_date()) . '</span>';
if ($views > 0) {
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
}
echo '</div>';
}
echo '</div></a>';
} else {
echo '<a class="aksrt-main-posts__row ajax-item" href="' . esc_url(get_permalink()) . '">';
if ($show_number) {
echo '<span class="aksrt-main-posts__num">' . esc_html((string) $i) . '</span>';
}
if ($show_thumb) {
echo '<span class="aksrt-main-posts__thumb mini' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</span>';
}
echo '<span class="aksrt-main-posts__body">';
echo '<span class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</span>';
if ($show_meta) {
echo '<span class="aksrt-main-posts__meta">' . esc_html(get_the_date()) . '</span>';
}
echo '</span></a>';
}
}
wp_reset_postdata();
return (string) ob_get_clean();
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$instance['_start_index'] = ($page - 1) * $count;
$q = new \WP_Query(self::build_query_args($instance, $page));
$html = self::render_items_html($q, $instance);
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="aksrt-main-posts__empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$base_qs = array_filter([
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => (int) ($instance['cat'] ?? 0),
'limit_day' => (int) ($instance['limit_day'] ?? 0),
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
'style' => sanitize_key((string) ($instance['style'] ?? 'list')),
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
'show_number' => !empty($instance['show_number']) ? 1 : 0,
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/posts'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return [
'html' => $html,
'paginate_html' => $paginate_html,
'has_more' => $has_more,
'page' => $page,
'max_pages' => $max_pages,
'total' => $total,
];
}
public function widget($args, $instance): void {
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$load_mode = sanitize_key((string) ($instance['load_mode'] ?? 'detail'));
if (!in_array($load_mode, ['detail', 'ajax'], true)) {
$load_mode = 'detail';
}
$instance['style'] = $style;
$instance['paginate'] = $paginate;
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
}
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$rest = rest_url('aksrt/v1/widgets/posts');
$qs = http_build_query(array_filter([
'page' => 1,
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => (int) ($instance['cat'] ?? 0),
'limit_day' => (int) ($instance['limit_day'] ?? 0),
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
'style' => $style,
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
'show_number' => !empty($instance['show_number']) ? 1 : 0,
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
], static function ($v) {
return $v !== '' && $v !== null;
}));
$wrap_cls = 'aksrt-main-posts style-' . $style . ' ajaxpager';
if ($style === 'mini') {
$wrap_cls .= ' zib-widget posts-mini-lists';
}
echo '<div class="' . esc_attr($wrap_cls) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '" data-load-mode="' . esc_attr($load_mode) . '" data-query="' . esc_attr($qs) . '"' . ($load_mode === 'ajax' ? ' data-loaded="0"' : ' data-loaded="1"') . '>';
if ($load_mode === 'ajax') {
// Skeleton first paint (zibll load_mode=ajax).
echo '<div class="aksrt-main-posts__skeleton">';
for ($si = 0; $si < $count; $si++) {
if ($style === 'card') {
echo '<div class="placeholder-row card"><div class="placeholder thumb"></div><div class="placeholder k1"></div></div>';
} else {
echo '<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder t1 k1"></div><div class="placeholder s1"></div></div></div>';
}
}
echo '</div>';
} else {
$payload = self::query_payload($instance, 1);
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></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'); ?>
<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'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="list" <?php selected(($instance['style'] ?? 'list'), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?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('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?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="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="downloads" <?php selected(($instance['orderby'] ?? ''), 'downloads'); ?>><?php esc_html_e('下载', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<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><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('最近 N 天(0=不限)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('模块加载', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('load_mode')); ?>">
<option value="detail" <?php selected(($instance['load_mode'] ?? 'detail'), 'detail'); ?>><?php esc_html_e('直接加载', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['load_mode'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 懒加载', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?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('pay_type')); ?>">
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
<option value="gallery" <?php selected(($instance['pay_type'] ?? ''), 'gallery'); ?>><?php esc_html_e('付费图库', 'aksrt'); ?></option>
<option value="video" <?php selected(($instance['pay_type'] ?? ''), 'video'); ?>><?php esc_html_e('付费视频', 'aksrt'); ?></option>
<option value="vip" <?php selected(($instance['pay_type'] ?? ''), 'vip'); ?>><?php esc_html_e('会员专属', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!isset($instance['show_thumb']) || !empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_meta')); ?>" value="1" <?php checked(!isset($instance['show_meta']) || !empty($instance['show_meta'])); ?>> <?php esc_html_e('显示 meta', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_number')); ?>" value="1" <?php checked(!empty($instance['show_number'])); ?>> <?php esc_html_e('显示序号', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'downloads', 'rand'], true)) {
$orderby = 'date';
}
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))),
'style' => $style,
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($new_instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'show_meta' => !empty($new_instance['show_meta']) ? 1 : 0,
'show_number' => !empty($new_instance['show_number']) ? 1 : 0,
];
}
}
-287
View File
@@ -1,287 +0,0 @@
<?php
/**
* Horizontal one-line posts scroller (zibll zib_widget_ui_oneline_posts).
*
* Uses shared post renderer + slider.js Type A (.swiper-scroll).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetOnelinePosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_oneline_posts',
esc_html__('AKSRT 横向文章', 'aksrt'),
[
'description' => esc_html__('横向滚动的文章卡片/迷你列表(共享 swiper-scroll)。', 'aksrt'),
'classname' => 'widget_aksrt_oneline_posts',
]
);
}
/**
* @param array<string,mixed> $instance
* @return array<string,mixed>
*/
public static function build_query_args(array $instance): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
if ($cat > 0) {
$query['cat'] = $cat;
}
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = $order;
break;
case 'likes':
$query['meta_key'] = '_aksrt_like_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'favorites':
$query['meta_key'] = '_aksrt_favorite_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'modified':
$query['orderby'] = 'modified';
$query['order'] = $order;
break;
case 'rand':
$query['orderby'] = 'rand';
break;
default:
$query['orderby'] = 'date';
$query['order'] = $order;
}
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
if ($pay_type === 'any' || $pay_type === 'paid') {
$query['meta_query'] = [
'relation' => 'OR',
['key' => '_aksrt_pay_price', 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => '_aksrt_pay_type', 'value' => ['reading', 'download', 'gallery', 'video', 'vip'], 'compare' => 'IN'],
['key' => '_aksrt_visibility', 'value' => ['pay', 'credits'], 'compare' => 'IN'],
];
} else {
$query['meta_query'] = [['key' => '_aksrt_pay_type', 'value' => $pay_type]];
}
}
return $query;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$q = new \WP_Query(self::build_query_args($instance));
if (!$q->have_posts()) {
wp_reset_postdata();
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
$mode = $style === 'mini' ? 'mini' : ($style === 'list' ? 'list' : 'card');
$cfg = [
'mode' => $mode,
'context' => 'widget',
'show_thumb' => true,
'show_meta' => $style !== 'mini',
'show_excerpt' => false,
'show_author' => false,
'show_comments' => false,
'show_likes' => false,
'show_tags' => false,
'show_badges' => $style === 'card',
'skip_wrapper' => true,
'extra_class' => 'posts-item',
];
$slides = '';
if (function_exists('aksrt_render_post')) {
while ($q->have_posts()) {
$q->the_post();
$item = aksrt_render_post($cfg);
if ($item === '') {
continue;
}
// Ensure freeMode width calc has a posts-item root when renderer wraps differently.
if (strpos($item, 'posts-item') === false && strpos($item, 'posts-mini') === false) {
$item = '<div class="posts-item">' . $item . '</div>';
}
$slides .= '<div class="swiper-slide ajax-item">' . $item . '</div>';
}
wp_reset_postdata();
} else {
// Fallback: bare thumb+title (should rarely hit).
while ($q->have_posts()) {
$q->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
$slides .= '<div class="swiper-slide ajax-item"><a class="posts-item aksrt-oneline-fallback" href="' . esc_url(get_permalink()) . '">';
if ($thumb) {
$slides .= '<div class="item-thumbnail"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></div>';
}
$slides .= '<div class="item-body"><div class="item-heading">' . esc_html(get_the_title()) . '</div></div></a></div>';
}
wp_reset_postdata();
}
if ($slides === '') {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
// zibll: .widget-main-post.style-{card|mini|list} + .swiper-container.swiper-scroll data-slideClass="posts-item".
ob_start();
echo '<div class="widget-main-post style-' . esc_attr($style) . ' aksrt-oneline-posts ajax-item">';
echo '<div class="swiper-container swiper-scroll aksrt-oneline-swiper" data-slideClass="posts-item">';
echo '<div class="swiper-wrapper">';
echo $slides; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '<div class="swiper-button-prev" aria-label="' . esc_attr__('上一页', 'aksrt') . '"></div>';
echo '<div class="swiper-button-next" aria-label="' . esc_attr__('下一页', 'aksrt') . '"></div>';
echo '</div></div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
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 $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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="20" 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('style')); ?>">
<option value="card" <?php selected(($instance['style'] ?? 'card'), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?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('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?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="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<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><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('限制最近 N 天(0=不限)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'rand'], true)) {
$orderby = 'date';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 8))),
'style' => $style,
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
]; return $business;
}
}
-186
View File
@@ -1,186 +0,0 @@
<?php
/**
* Compact paid-content purchase card for the article sidebar.
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetPostPayBox extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_post_pay_box',
esc_html__('AKSRT 付费购买', 'aksrt'),
[
'description' => esc_html__('仅在文章页 - 侧边栏展示当前文章的紧凑购买卡。', 'aksrt'),
'classname' => 'widget_aksrt_post_pay_box',
]
);
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance = []): string {
if (!is_single() || get_post_type() !== 'post') {
return '';
}
$post = get_queried_object();
if (!($post instanceof \WP_Post) || !function_exists('aksrt_get_pay_meta') || !function_exists('aksrt_is_paid_post') || !aksrt_is_paid_post($post)) {
return '';
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? 'no');
if ($pay_type === '' || $pay_type === 'no') {
return '';
}
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
$allowed_themes = ['jb-red', 'jb-yellow', 'jb-blue', 'jb-green', 'jb-purple', 'jb-vip1', 'jb-vip2'];
if (!in_array($theme, $allowed_themes, true)) {
$theme = 'jb-red';
}
$price = function_exists('aksrt_get_user_pay_price')
? aksrt_get_user_pay_price($post)
: [
'amount' => (float) ($meta['price'] ?? 0),
'base' => (float) ($meta['price'] ?? 0),
'original' => (float) ($meta['original_price'] ?? 0),
'modo' => '0',
'currency' => '¥',
];
$amount = max(0.0, (float) ($price['amount'] ?? 0));
$base = max(0.0, (float) ($price['base'] ?? $amount));
$original = max(0.0, (float) ($price['original'] ?? 0));
$pay_modo = (($price['modo'] ?? '0') === 'points') ? 'points' : '0';
$currency = (string) ($price['currency'] ?? ($pay_modo === 'points' ? __('积分', 'aksrt') : '¥'));
$format = static function (float $value) use ($pay_modo, $currency): string {
$number = function_exists('aksrt_format_price') ? aksrt_format_price($value) : number_format($value, 2, '.', '');
return $pay_modo === 'points' ? $number . ' ' . $currency : $currency . $number;
};
$has_access = function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($post);
$is_logged_in = is_user_logged_in();
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post));
$demo = trim((string) ($meta['demo_url'] ?? ''));
$sales = 0;
if (function_exists('aksrt_get_download_box_meta')) {
$box = aksrt_get_download_box_meta($post);
$sales = max(0, (int) ($box['sales_count'] ?? 0));
}
$type_labels = [
'reading' => __('付费阅读', 'aksrt'),
'download' => __('付费下载', 'aksrt'),
'gallery' => __('付费图库', 'aksrt'),
'video' => __('付费视频', 'aksrt'),
'vip' => __('会员专属', 'aksrt'),
];
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
$action_labels = [
'reading' => __('查看内容', 'aksrt'),
'download' => __('前往下载', 'aksrt'),
'gallery' => __('查看图片', 'aksrt'),
'video' => __('查看视频', 'aksrt'),
'vip' => __('查看内容', 'aksrt'),
];
$action_label = $action_labels[$pay_type] ?? __('查看内容', 'aksrt');
$show_original = $original > $amount && $original > 0;
$show_member_discount = !$has_access && $amount > 0 && $base > $amount;
ob_start();
?>
<section class="aksrt-post-pay-card <?php echo esc_attr($theme); ?><?php echo $has_access ? ' is-unlocked' : ''; ?>" aria-label="<?php esc_attr_e('付费购买', 'aksrt'); ?>">
<header class="aksrt-post-pay-card__header">
<span class="aksrt-post-pay-card__type"><?php echo esc_html($type_label); ?></span>
<?php if ($sales > 0) : ?>
<span class="aksrt-post-pay-card__sales"><?php echo esc_html(sprintf(__('已售 %s', 'aksrt'), number_format_i18n($sales))); ?></span>
<?php endif; ?>
</header>
<div class="aksrt-post-pay-card__price-row">
<?php if ($has_access) : ?>
<strong class="aksrt-post-pay-card__status"><i class="fa fa-check-circle" aria-hidden="true"></i><?php esc_html_e('已解锁', 'aksrt'); ?></strong>
<?php elseif ($amount > 0) : ?>
<strong class="aksrt-post-pay-card__price"><?php echo esc_html($format($amount)); ?></strong>
<?php if ($show_original) : ?><del><?php echo esc_html($format($original)); ?></del><?php endif; ?>
<?php else : ?>
<strong class="aksrt-post-pay-card__price"><?php esc_html_e('免费', 'aksrt'); ?></strong>
<?php endif; ?>
</div>
<?php if ($show_member_discount) : ?>
<p class="aksrt-post-pay-card__hint"><?php echo esc_html(sprintf(__('已应用会员优惠,原价 %s', 'aksrt'), $format($base))); ?></p>
<?php endif; ?>
<div class="aksrt-post-pay-card__actions">
<?php if ($has_access) : ?>
<a class="but btn-block <?php echo esc_attr($theme); ?>" href="#posts-pay"><i class="fa fa-arrow-down" aria-hidden="true"></i><?php echo esc_html($action_label); ?></a>
<?php elseif (!$is_logged_in) : ?>
<a class="but btn-block jb-blue" href="<?php echo esc_url($login_url); ?>"><i class="fa fa-sign-in" aria-hidden="true"></i><?php esc_html_e('登录后购买', 'aksrt'); ?></a>
<?php if ($amount > 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) : ?>
<button type="button" class="but btn-block <?php echo esc_attr($theme); ?> cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>"><i class="fa fa-shopping-cart" aria-hidden="true"></i><?php echo esc_html(sprintf(__('游客购买(%s)', 'aksrt'), $format($amount))); ?></button>
<?php endif; ?>
<?php else : ?>
<button type="button" class="but btn-block <?php echo esc_attr($theme); ?> cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>"><i class="fa fa-shopping-cart" aria-hidden="true"></i><?php echo esc_html(sprintf(__('立即购买(%s)', 'aksrt'), $format($amount))); ?></button>
<?php endif; ?>
<?php if ($demo !== '') : ?>
<a class="but hollow btn-block" href="<?php echo esc_url($demo); ?>" target="_blank" rel="noopener noreferrer"><i class="fa fa-external-link" aria-hidden="true"></i><?php esc_html_e('演示地址', 'aksrt'); ?></a>
<?php endif; ?>
</div>
</section>
<?php
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
$options = [
'jb-red' => __('红', 'aksrt'),
'jb-yellow' => __('橙', 'aksrt'),
'jb-blue' => __('蓝', 'aksrt'),
'jb-green' => __('绿', 'aksrt'),
'jb-purple' => __('紫', 'aksrt'),
'jb-vip1' => __('会员金', 'aksrt'),
'jb-vip2' => __('会员灰', 'aksrt'),
];
?>
<p class="description"><?php esc_html_e('仅在文章页 - 侧边栏显示付费文章的紧凑购买卡。', 'aksrt'); ?></p>
<p>
<label><?php esc_html_e('色彩主题', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('theme')); ?>">
<?php foreach ($options as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($theme, $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$theme = sanitize_html_class((string) ($new_instance['theme'] ?? 'jb-red'));
$allowed = ['jb-red', 'jb-yellow', 'jb-blue', 'jb-green', 'jb-purple', 'jb-vip1', 'jb-vip2'];
if (!in_array($theme, $allowed, true)) {
$theme = 'jb-red';
}
return ['theme' => $theme];
}
}
-292
View File
@@ -1,292 +0,0 @@
<?php
/**
* Simple image slider widget (zibll widget_ui_slider → aksrt_render_slider / new-swiper).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetSlider extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_slider',
esc_html__('AKSRT 幻灯片', 'aksrt'),
[
'description' => esc_html__('多图轮播(共享 new-swiper / Swiper 栈),支持链接与标题。', 'aksrt'),
'classname' => 'widget_aksrt_slider slide-widget',
]
);
}
public function widget($args, $instance): void {
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('当前配置下没有可显示的内容', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('幻灯片模块:', 'aksrt') . '</b>' . esc_html__('当前配置下没有可显示的内容,请正确配置或移出该模块', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$interval = max(0, min(20, (int) ($instance['interval'] ?? 4)));
$loop = !isset($instance['loop']) || !empty($instance['loop']);
$dots = !isset($instance['pagination']) || !empty($instance['pagination']);
$nav = !isset($instance['button']) || !empty($instance['button']);
$blank = !empty($instance['blank']);
$height = max(80, min(800, (int) ($instance['height'] ?? $instance['pc_height'] ?? 400)));
$m_height = max(60, min(800, (int) ($instance['m_height'] ?? $height)));
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
$effect = 'slide';
}
// Zibll CFS_Module::slide() options parity.
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
$direction = 'horizontal';
}
$autoplay = !isset($instance['autoplay']) || !empty($instance['autoplay']);
$speed = max(0, min(3000, (int) ($instance['speed'] ?? 0)));
$spacebetween= max(0, min(500, (int) ($instance['spacebetween'] ?? 15)));
$scale_height= !empty($instance['scale_height']) || !isset($instance['scale_height']);
$scale = max(10, min(300, (int) ($instance['scale'] ?? 35)));
$slide_payload = [];
foreach ($slides as $slide) {
$slide_payload[] = [
'image' => $slide['image'],
'link' => $slide['href'],
'title' => $slide['title'],
'desc' => $slide['desc'],
'alt' => $slide['title'],
'target' => $blank ? '_blank' : '_self',
];
}
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 (function_exists('aksrt_render_slider')) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo aksrt_render_slider([
'slides' => $slide_payload,
'effect' => $effect,
'loop' => $loop,
'autoplay' => $autoplay,
'interval' => $interval * 1000,
'speed' => $speed,
'direction' => $direction,
'spaceBetween' => $spacebetween,
'pc_height' => $height . 'px',
'm_height' => $m_height . 'px',
'scale_height' => $scale_height && $direction !== 'vertical',
'scale_ratio' => $scale / 100,
'show_nav' => $nav,
'show_pagination' => $dots,
'slider_class' => 'slide-widget',
'wrapper_class' => 'aksrt-widget-slider-wrap',
]);
} else {
// Minimal fallback without helper.
echo '<div class="aksrt-widget-slider" style="--aksrt-slider-h:' . (int) $height . 'px">';
foreach ($slide_payload as $i => $slide) {
$active = $i === 0 ? ' is-active' : '';
echo '<div class="aksrt-widget-slider__slide' . esc_attr($active) . '">';
$img = '<img src="' . esc_url($slide['image']) . '" alt="' . esc_attr($slide['title']) . '" loading="lazy" />';
if ($slide['link'] !== '') {
echo '<a href="' . esc_url($slide['link']) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . $img . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$interval = (int) ($instance['interval'] ?? 4);
$height = (int) ($instance['height'] ?? $instance['pc_height'] ?? 400);
$m_height = (int) ($instance['m_height'] ?? $height);
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
$speed = (int) ($instance['speed'] ?? 0);
$spacebetween = (int) ($instance['spacebetween'] ?? 15);
$scale = (int) ($instance['scale'] ?? 35);
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
$slides = [
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
];
}
?>
<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 name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
<option value="horizontal" <?php selected($direction, 'horizontal'); ?>><?php esc_html_e('左右切换', 'aksrt'); ?></option>
<option value="vertical" <?php selected($direction, 'vertical'); ?>><?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('effect')); ?>">
<option value="slide" <?php selected($effect, 'slide'); ?>><?php esc_html_e('滑动', 'aksrt'); ?></option>
<option value="fade" <?php selected($effect, 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
<option value="coverflow" <?php selected($effect, 'coverflow'); ?>><?php esc_html_e('Coverflow', 'aksrt'); ?></option>
<option value="cube" <?php selected($effect, 'cube'); ?>><?php esc_html_e('Cube', 'aksrt'); ?></option>
<option value="flip" <?php selected($effect, 'flip'); ?>><?php esc_html_e('Flip', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('PC 高度(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="80" max="800" name="<?php echo esc_attr($this->get_field_name('height')); ?>" value="<?php echo esc_attr((string) $height); ?>">
</label>
<label><?php esc_html_e('移动端高度(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="60" max="800" name="<?php echo esc_attr($this->get_field_name('m_height')); ?>" value="<?php echo esc_attr((string) $m_height); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="1" <?php checked(!isset($instance['scale_height']) || !empty($instance['scale_height'])); ?>> <?php esc_html_e('按比例自动高度', 'aksrt'); ?></label>
<label><?php esc_html_e('长宽比例 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="10" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="<?php echo esc_attr((string) $scale); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('间距(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="500" step="5" name="<?php echo esc_attr($this->get_field_name('spacebetween')); ?>" value="<?php echo esc_attr((string) $spacebetween); ?>">
</label>
<label><?php esc_html_e('切换速度(ms,0=自动)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="3000" step="100" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) $speed); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('停顿时间(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) $interval); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop']) || !isset($instance['loop'])); ?>> <?php esc_html_e('循环播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('button')); ?>" value="1" <?php checked(!empty($instance['button']) || !isset($instance['button'])); ?>> <?php esc_html_e('翻页按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pagination')); ?>" value="1" <?php checked(!empty($instance['pagination']) || !isset($instance['pagination'])); ?>> <?php esc_html_e('指示点', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!isset($instance['autoplay']) || !empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php foreach ($slides as $i => $slide) : ?>
<p style="border-top:1px solid #ddd;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('幻灯片 %d', 'aksrt'), $i + 1)); ?></strong><br>
<label><?php esc_html_e('图片 URL', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" value="<?php echo esc_attr($slide['image']); ?>">
</label>
<label><?php esc_html_e('链接', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" value="<?php echo esc_attr($slide['href']); ?>">
</label>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" value="<?php echo esc_attr($slide['title']); ?>">
</label>
<label><?php esc_html_e('简介', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" value="<?php echo esc_attr($slide['desc']); ?>">
</label>
</p>
<?php endforeach; ?>
<?php
$extra = max(0, 4 - count($slides));
for ($j = 0; $j < $extra; $j++) {
$i = count($slides) + $j;
?>
<p style="border-top:1px dashed #eee;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('空槽 %d', 'aksrt'), $i + 1)); ?></strong>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" placeholder="<?php esc_attr_e('图片 URL', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" placeholder="<?php esc_attr_e('简介', 'aksrt'); ?>">
</p>
<?php
}
?>
<p class="description"><?php esc_html_e('使用主题共享 Swiper(.new-swiper)。图片 URL 必填;仅 1 张时不滑动。', 'aksrt'); ?></p>
<p class="description"><?php esc_html_e('对齐 Zibll 幻灯片:方向/效果/比例自动高度/间距/速度/自动播放/间隔。视频背景与图层视差由共享 Swiper 栈支持。', 'aksrt'); ?></p>
<?php
}
public function update($new_instance, $old_instance): array {
$slides = $this->normalize_slides($new_instance['slides'] ?? []);
$effect = sanitize_key((string) ($new_instance['effect'] ?? 'slide'));
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
$effect = 'slide';
}
$direction = sanitize_key((string) ($new_instance['direction'] ?? 'horizontal'));
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
$direction = 'horizontal';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'interval' => max(0, min(20, (int) ($new_instance['interval'] ?? 4))),
'height' => max(80, min(800, (int) ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400))),
'm_height' => max(60, min(800, (int) ($new_instance['m_height'] ?? ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400)))),
'effect' => $effect,
'direction' => $direction,
'speed' => max(0, min(3000, (int) ($new_instance['speed'] ?? 0))),
'spacebetween'=> max(0, min(500, (int) ($new_instance['spacebetween'] ?? 15))),
'scale_height'=> !empty($new_instance['scale_height']) ? 1 : 0,
'scale' => max(10, min(300, (int) ($new_instance['scale'] ?? 35))),
'loop' => !empty($new_instance['loop']) ? 1 : 0,
'button' => !empty($new_instance['button']) ? 1 : 0,
'pagination' => !empty($new_instance['pagination']) ? 1 : 0,
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'slides' => $slides,
]; return $business;
}
/**
* @param mixed $raw
* @return array<int, array{image:string,href:string,title:string,desc:string}>
*/
private function normalize_slides($raw): array {
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$image = esc_url_raw(trim((string) ($row['image'] ?? $row['link'] ?? '')));
if ($image === '') {
continue;
}
$out[] = [
'image' => $image,
'href' => esc_url_raw(trim((string) ($row['href'] ?? ''))),
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
'desc' => sanitize_text_field((string) ($row['desc'] ?? $row['dec'] ?? '')),
];
if (count($out) >= 8) {
break;
}
}
return $out;
}
}
-354
View File
@@ -1,354 +0,0 @@
<?php
/**
* Term aggregation cards (zibll zib_widget_ui_term_lists_card → zib_term_aggregation).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetTermAggregation extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_term_aggregation',
esc_html__('AKSRT 分类聚合', 'aksrt'),
[
'description' => esc_html__('分类封面 + 描述 + 更多文章 CTA + 文章列表聚合卡。', 'aksrt'),
'classname' => 'widget_aksrt_term_aggregation',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(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));
}
/**
* @param \WP_Term $term
*/
private static function term_cover_url(\WP_Term $term): string {
if (class_exists(WidgetTermCards::class) && method_exists(WidgetTermCards::class, 'term_cover_url')) {
return WidgetTermCards::term_cover_url($term);
}
$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) {
$url = wp_get_attachment_image_url($img_id, 'medium');
if ($url) {
return (string) $url;
}
}
$url = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
return $url !== '' ? $url : get_template_directory_uri() . '/assets/img/placeholder.svg';
}
/**
* Sum of post views under a term (best-effort).
*/
private static function term_views_sum(int $term_id, string $taxonomy): int {
$q = new \WP_Query([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 50,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
'update_post_term_cache' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
]],
]);
$sum = 0;
foreach ($q->posts as $pid) {
$sum += function_exists('aksrt_get_post_views')
? (int) aksrt_get_post_views((int) $pid)
: (int) get_post_meta((int) $pid, 'aksrt_post_views', true);
}
return $sum;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$term_ids = self::parse_ids_static((string) ($instance['term_ids'] ?? ''));
$count = max(1, min(20, (int) ($instance['count'] ?? 4)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$tgt = $target_blank ? ' target="_blank" rel="noopener noreferrer"' : '';
if (!$term_ids) {
$terms = get_terms([
'taxonomy' => $taxonomy,
'number' => max(1, min(4, (int) ($instance['auto_terms'] ?? 2))),
'orderby' => 'count',
'order' => 'DESC',
'hide_empty' => true,
]);
if (is_wp_error($terms) || !$terms) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$term_ids = array_map(static fn($t) => (int) $t->term_id, $terms);
}
ob_start();
echo '<div class="aksrt-term-agg ajax-item" style="--aksrt-ta-cols:' . esc_attr((string) $columns) . '">';
foreach ($term_ids as $term_id) {
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$cover = self::term_cover_url($term);
$views = self::term_views_sum((int) $term->term_id, $taxonomy);
$more_badge = '<badge class="img-badge px12"><i class="fa fa-fire" aria-hidden="true"></i> ' . esc_html((string) $views) . '</badge>';
$img_html = '';
if (function_exists('aksrt_graphic_card')) {
$img_html = aksrt_graphic_card([
'type' => '',
'class' => '',
'img' => $cover,
'alt' => $term->name,
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'lazy' => true,
'height_scale' => 70,
'mask_opacity' => 0,
'more' => $more_badge,
]);
}
$description = trim((string) $term->description);
if ($description === '' && current_user_can('manage_categories')) {
$edit = get_edit_term_link((int) $term->term_id, $taxonomy);
$description = esc_html__('请在后台为该分类添加描述。', 'aksrt');
if ($edit) {
$description .= ' <a href="' . esc_url($edit) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
}
} else {
$description = esc_html($description);
}
$posts_q = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => (int) $term->term_id,
]],
];
if ($orderby === 'views') {
$posts_q['meta_key'] = 'aksrt_post_views';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'like') {
$posts_q['meta_key'] = '_aksrt_like_count';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'favorite') {
$posts_q['meta_key'] = '_aksrt_favorite_count';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'comment_count') {
$posts_q['orderby'] = 'comment_count';
$posts_q['order'] = $order;
} elseif ($orderby === 'modified') {
$posts_q['orderby'] = 'modified';
$posts_q['order'] = $order;
} elseif ($orderby === 'rand') {
$posts_q['orderby'] = 'rand';
} else {
$posts_q['orderby'] = 'date';
$posts_q['order'] = $order;
}
$q = new \WP_Query($posts_q);
$found = (int) $q->found_posts;
echo '<div class="zib-widget term-aggregation mb10 aksrt-term-agg__card">';
echo '<div class="mb20 hover-zoom-img flex px12-sm">';
echo '<div class="term-img flex0 em09-sm">' . $img_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-title ml10 flex xx flex1 jsb">';
echo '<a class="em14 key-color text-ellipsis"' . $tgt . ' href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
echo '<div class="text-ellipsis-2 muted-color">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-meta flex jsb ac">';
echo '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>'
. esc_html(sprintf(
/* translators: %s: article count */
__('%s篇文章', 'aksrt'),
(string) $found
))
. '</span>';
echo '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $tgt . ' href="' . esc_url($link) . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
echo '</div></div></div>';
if ($q->have_posts()) {
while ($q->have_posts()) {
$q->the_post();
$pid = get_the_ID();
$title = get_the_title($pid);
$meta = '';
if ($orderby === 'views') {
$v = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($pid) : (int) get_post_meta($pid, 'aksrt_post_views', true);
$meta = '<i class="fa fa-eye mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'like') {
$v = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
$meta = '<i class="fa fa-heart mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'favorite') {
$v = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
$meta = '<i class="fa fa-star mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'comment_count') {
$meta = '<i class="fa fa-comment-o mr3" aria-hidden="true"></i>' . esc_html((string) get_comments_number($pid));
} else {
$ts = $orderby === 'modified' ? get_the_modified_time('U', $pid) : get_the_time('U', $pid);
$ago = human_time_diff((int) $ts, current_time('timestamp'));
$meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($ago);
}
echo '<div class="mt10 flex jsb ac">';
echo '<div class="text-ellipsis"><a class="icon-circle mln3"' . $tgt . ' href="' . esc_url(get_permalink($pid)) . '">' . esc_html($title) . '</a></div>';
echo '<div class="em09 muted-3-color flex0 ml10">' . $meta . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
wp_reset_postdata();
}
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
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 $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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); ?></option>
<?php endforeach; ?>
</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('term_ids')); ?>" value="<?php echo esc_attr((string) ($instance['term_ids'] ?? '')); ?>">
</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'] ?? 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('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 1))); ?>">
</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="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorite" <?php selected(($instance['orderby'] ?? ''), 'favorite'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<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('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'like', 'favorite', 'comment_count', 'rand'], true)) {
$orderby = 'date';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
]; return $business;
}
}
-304
View File
@@ -1,304 +0,0 @@
<?php
/**
* Taxonomy term graphic cards (zibll zib_widget_ui_term_card → zib_graphic_card).
*
* @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__('以图文封面卡片展示分类或标签(style-1..4)。', 'aksrt'),
'classname' => 'widget_aksrt_term_cards',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(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));
}
/**
* Resolve term cover URL.
*/
public static function term_cover_url(\WP_Term $term): string {
$thumb = '';
$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 = (string) (wp_get_attachment_image_url($img_id, 'medium_large') ?: wp_get_attachment_image_url($img_id, 'medium') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
}
if ($thumb === '') {
// Lightweight default gradient placeholder as data URI is heavy — use theme placeholder.
$thumb = get_template_directory_uri() . '/assets/img/placeholder.svg';
}
return $thumb;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$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 = self::parse_ids_static((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$type = sanitize_key((string) ($instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$height_scale = max(20, min(300, (int) ($instance['height_scale'] ?? 60)));
$mask_opacity = max(0, min(90, (int) ($instance['mask_opacity'] ?? 10)));
$pc_row = max(1, min(12, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(12, (int) ($instance['m_row'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$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 function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$icon = $taxonomy === 'category'
? '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>'
: '<i class="fa fa-cube mr6" aria-hidden="true"></i>';
$is_row = count($terms) > 1;
// Bootstrap-like col classes already used elsewhere; keep numeric CSS vars as fallback.
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
ob_start();
echo '<div class="aksrt-term-cards ajax-item' . ($is_row ? ' mb10' : ' mb20') . '">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($terms as $term) {
if (!($term instanceof \WP_Term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$name = mb_strlen($term->name) > 16 ? mb_substr($term->name, 0, 16) . '…' : $term->name;
$desc = trim((string) $term->description);
if (mb_strlen($desc) > 24) {
$desc = mb_substr($desc, 0, 24) . '…';
}
$count = (int) $term->count;
$count_label = sprintf(
/* translators: %d: post count */
_n('%d篇文章', '%d篇文章', $count, 'aksrt'),
$count
);
$text1 = $name;
$text2 = $desc;
$text3 = $icon . esc_html($count_label);
if ($type === 'style-2' || $type === 'style-4') {
$text3 = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
}
if ($type === 'style-3' || $type === 'style-4') {
$text1 = $icon . esc_html($name);
if ($type === 'style-3') {
$text3 = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . esc_html($count_label);
}
}
$card_args = [
'type' => $type,
'class' => 'mb10',
'img' => self::term_cover_url($term),
'alt' => $name . ($desc !== '' ? '-' . $desc : ''),
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'text1' => $text1,
'text2' => $text2,
'text3' => $text3,
'lazy' => true,
'height_scale' => $height_scale,
'mask_opacity' => $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '"><div class="aksrt-term-card__name">' . esc_html($name) . '</div></a>';
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
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 $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="12" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="12" 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('卡片样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<option value="style-1" <?php selected(($instance['type'] ?? 'style-1'), 'style-1'); ?>><?php esc_html_e('样式 1(左下标题)', 'aksrt'); ?></option>
<option value="style-2" <?php selected(($instance['type'] ?? ''), 'style-2'); ?>><?php esc_html_e('样式 2(居中)', 'aksrt'); ?></option>
<option value="style-3" <?php selected(($instance['type'] ?? ''), 'style-3'); ?>><?php esc_html_e('样式 3(左下多层)', 'aksrt'); ?></option>
<option value="style-4" <?php selected(($instance['type'] ?? ''), 'style-4'); ?>><?php esc_html_e('样式 4(图+下方文案)', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="20" max="300" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 60))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-90', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="90" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 10))); ?>">
</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><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$type = sanitize_key((string) ($new_instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$business = [
'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))),
'pc_row' => max(1, min(12, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(12, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(12, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'type' => $type,
'height_scale' => max(20, min(300, (int) ($new_instance['height_scale'] ?? 60))),
'mask_opacity' => max(0, min(90, (int) ($new_instance['mask_opacity'] ?? 10))),
'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,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
]; return $business;
}
}
-166
View File
@@ -1,166 +0,0 @@
<?php
/**
* Text title / section heading (zibll zib_widget_ui_text_title).
*
* @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',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = trim((string) ($instance['title'] ?? ''));
$desc = trim((string) ($instance['desc'] ?? ''));
if ($title === '' && $desc === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请填写标题或描述', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请填写标题或描述', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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'] ?? '')) ?: '';
$icon_width = max(0, min(64, (int) ($instance['title_icon_width'] ?? 0)));
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 . ';' : ''
);
$icon_w_attr = $icon_width > 0 ? ' style="--icon-width:' . (int) $icon_width . 'px;"' : '';
$left_html = $icon_left !== ''
? '<span class="text-title-icon aksrt-text-title__icon mr6"' . $icon_w_attr . '><i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i></span>'
: '';
$right_html = $icon_right !== ''
? '<span class="text-title-icon aksrt-text-title__icon ml6"' . $icon_w_attr . '><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>'
: '';
$title_html = $title !== '' ? $left_html . esc_html($title) . $right_html : '';
$desc_html = '';
if ($desc !== '') {
$desc_html = '<div class="aksrt-text-title__desc">' . esc_html($desc) . '</div>';
}
$box = 'aksrt-text-title text-title align-' . esc_attr($align);
echo '<div class="' . esc_attr($box) . '" style="' . esc_attr($style) . '">';
if ($title_html !== '') {
echo '<div class="aksrt-text-title__main">' . $title_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $desc_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
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
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
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'] ?? '')) ?: '',
'title_icon_width' => max(0, min(64, (int) ($new_instance['title_icon_width'] ?? 0))),
];
}
}
-324
View File
@@ -1,324 +0,0 @@
<?php
/**
* Current-user / guest identity card (zibll widget_ui_user parity).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetUserCard extends \WP_Widget {
private const DEFAULT_COVER_FILE = 'assets/img/person_background.png';
public function __construct() {
parent::__construct(
'aksrt_user_card',
__('AKSRT 用户卡片', 'aksrt'),
[
'description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到、快捷按钮与更多文章。', 'aksrt'),
'classname' => 'widget_aksrt_user_card',
]
);
}
public function widget($args, $instance): void {
$show_cover = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']);
$show_stats = !empty($instance['show_stats']) || !isset($instance['show_stats']);
$show_actions = !empty($instance['show_button']) || !isset($instance['show_button']);
$show_checkin = !empty($instance['show_checkin']) || !isset($instance['show_checkin']);
$loged_title = (string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'));
$btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'));
$btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'));
$btn1_url = (string) ($instance['button_1_url'] ?? '');
$btn2_url = (string) ($instance['button_2_url'] ?? '');
// Zibll button color palettes (button_1_class / button_2_class).
$btn1_class = sanitize_html_class((string) ($instance['button_1_class'] ?? 'jb-pink')) ?: 'jb-pink';
$btn2_class = sanitize_html_class((string) ($instance['button_2_class'] ?? 'jb-blue')) ?: 'jb-blue';
// Fixed user id wins; else current user.
$user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
if ($user_id <= 0 && is_user_logged_in()) {
$user_id = get_current_user_id();
}
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 ($user_id <= 0) {
$this->render_guest($show_cover, $loged_title, $this->get_guest_cover_url($instance));
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$user = get_userdata($user_id);
if (!$user) {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$btn1_url = esc_url($btn1_url !== '' ? $btn1_url : $this->get_default_compose_url());
$btn2_url = esc_url($btn2_url !== '' ? $btn2_url : $this->get_default_center_url());
$is_self = is_user_logged_in() && (int) get_current_user_id() === $user_id;
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 144])
: get_avatar_url($user_id, ['size' => 144]);
$profile_url = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($user_id)
: get_author_posts_url($user_id);
$bio = get_user_meta($user_id, 'description', true);
if (!is_string($bio) || $bio === '') {
$bio = (string) $user->description;
}
$cover = (string) get_user_meta($user_id, 'aksrt_background_image', true);
if ($cover === '') {
$cover = $this->get_default_cover_url();
}
$post_count = count_user_posts($user_id, 'post', true);
$follower_count = 0;
$following_count = 0;
if (class_exists('\AKSRT\User\Follow')) {
$follower_count = \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
$following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id);
}
$name_html = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $user_id,
'vip' => true,
'follow' => !$is_self,
'class' => 'aksrt-user-card__name-row',
])
: '<a href="' . esc_url($profile_url) . '">' . esc_html($user->display_name) . '</a>';
$checkin_btn = '';
if ($show_checkin && $is_self && function_exists('aksrt_header_user_checkin_btn')) {
$checkin_btn = aksrt_header_user_checkin_btn($user_id, true);
}
$checkin_html = $checkin_btn !== ''
? '<div class="aksrt-user-card__checkin">' . $checkin_btn . '</div>'
: '';
echo '<div class="aksrt-user-card user-card' . ($show_cover ? ' has-cover' : ' no-cover') . '">';
if ($show_cover) {
echo '<div class="aksrt-user-card__cover" style="background-image:url(' . esc_url($cover) . ')">' . $checkin_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} elseif ($checkin_btn !== '') {
echo '<div class="aksrt-user-card__checkin aksrt-user-card__checkin--without-cover">' . $checkin_btn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="aksrt-user-card__body card-content">';
echo '<div class="aksrt-user-card__avatar-wrap">';
echo '<div class="aksrt-user-card__avatar user-avatar">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<a href="' . esc_url($profile_url) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="' . esc_attr($user->display_name) . '" loading="lazy" width="76" height="76" /></a>';
echo '</div>';
echo '</div>';
echo '<h4 class="aksrt-user-card__name user-name">' . $name_html . '</h4>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($bio !== '') {
echo '<p class="aksrt-user-card__bio user-desc">' . esc_html(wp_trim_words($bio, 24)) . '</p>';
}
if ($show_stats) {
echo '<div class="aksrt-user-card__stats">';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($post_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('文章', 'aksrt') . '</span></div>';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($follower_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('粉丝', 'aksrt') . '</span></div>';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($following_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('关注', 'aksrt') . '</span></div>';
echo '</div>';
}
if ($show_actions && $is_self) {
echo '<div class="aksrt-user-card__actions user-btns">';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary but ' . esc_attr($btn1_class) . '" href="' . $btn1_url . '">' . esc_html($btn1_text) . '</a>';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost but ' . esc_attr($btn2_class) . '" href="' . $btn2_url . '">' . esc_html($btn2_text) . '</a>';
echo '</div>';
} elseif ($show_actions) {
echo '<div class="aksrt-user-card__actions"><a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="' . esc_url($profile_url) . '">' . esc_html__('查看主页', 'aksrt') . '</a></div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function render_guest(bool $show_cover, string $loged_title, string $cover): void {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
$register = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
$default_avatar = function_exists('get_avatar_url')
? get_avatar_url(0, ['size' => 144, 'force_default' => true])
: includes_url('images/blank.gif');
$oauth_html = '';
if (class_exists('\AKSRT\Auth\OAuth')) {
$oauth = \AKSRT\Auth\OAuth::get_instance();
if (method_exists($oauth, 'render_social_buttons')) {
$oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
} elseif (method_exists($oauth, 'get_login_buttons')) {
$oauth_html = (string) $oauth->get_login_buttons();
}
}
echo '<div class="aksrt-user-card user-card is-guest' . ($show_cover ? ' has-cover' : ' no-cover') . '">';
if ($show_cover) {
echo '<div class="aksrt-user-card__cover" style="background-image:url(' . esc_url($cover) . ')"></div>';
}
echo '<div class="aksrt-user-card__body card-content">';
echo '<div class="aksrt-user-card__avatar-wrap"><div class="aksrt-user-card__avatar user-avatar"><img src="' . esc_url($default_avatar) . '" alt="" width="76" height="76" loading="lazy" /></div></div>';
echo '<div class="aksrt-user-card__guest-title">' . esc_html($loged_title) . '</div>';
echo '<div class="aksrt-user-card__actions">';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary signin-loader" href="' . esc_url($login) . '">' . esc_html__('登录', 'aksrt') . '</a>';
if ($register) {
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost signup-loader" href="' . esc_url($register) . '">' . esc_html__('注册', 'aksrt') . '</a>';
}
echo '</div>';
if ($oauth_html !== '') {
echo '<div class="aksrt-user-card__oauth social_loginbar">' . $oauth_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div></div>';
}
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('固定用户 ID(0=当前用户)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('user_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['user_id'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('未登录文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('loged_title')); ?>" value="<?php echo esc_attr((string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('未登录封面图 URL(留空使用默认图片)', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('guest_cover')); ?>" value="<?php echo esc_attr((string) ($instance['guest_cover'] ?? '')); ?>" placeholder="<?php echo esc_attr($this->get_default_cover_url()); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('按钮1文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_1_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'))); ?>">
</label>
<label><?php esc_html_e('按钮1跳转链接', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('button_1_url')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_url'] ?? $this->get_default_compose_url())); ?>">
</label>
<label><?php esc_html_e('按钮1颜色', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('button_1_class')); ?>">
<?php foreach (self::palette_opts() as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_1_class'] ?? 'jb-pink'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('按钮2文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_2_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'))); ?>">
</label>
<label><?php esc_html_e('按钮2跳转链接', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('button_2_url')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_url'] ?? $this->get_default_center_url())); ?>">
</label>
<label><?php esc_html_e('按钮2颜色', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('button_2_class')); ?>">
<?php foreach (self::palette_opts() as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_2_class'] ?? 'jb-blue'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_img_bg')); ?>" value="1" <?php checked(!isset($instance['show_img_bg']) || !empty($instance['show_img_bg'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_button')); ?>" value="1" <?php checked(!isset($instance['show_button']) || !empty($instance['show_button'])); ?>> <?php esc_html_e('显示操作按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_checkin')); ?>" value="1" <?php checked(!isset($instance['show_checkin']) || !empty($instance['show_checkin'])); ?>> <?php esc_html_e('显示签到(本人)', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$button_1_url = esc_url_raw(trim((string) ($new_instance['button_1_url'] ?? '')));
$button_2_url = esc_url_raw(trim((string) ($new_instance['button_2_url'] ?? '')));
if ($button_1_url === '') {
$button_1_url = $this->get_default_compose_url();
}
if ($button_2_url === '') {
$button_2_url = $this->get_default_center_url();
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)),
'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')),
'guest_cover' => esc_url_raw(trim((string) ($new_instance['guest_cover'] ?? ''))),
'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')),
'button_1_url' => $button_1_url,
'button_1_class'=> self::sanitize_palette((string) ($new_instance['button_1_class'] ?? 'jb-pink'), 'jb-pink'),
'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')),
'button_2_url' => $button_2_url,
'button_2_class'=> self::sanitize_palette((string) ($new_instance['button_2_class'] ?? 'jb-blue'), 'jb-blue'),
'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
'show_button' => !empty($new_instance['show_button']) ? 1 : 0,
'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0,
];
}
private function get_default_center_url(): string {
return function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/');
}
private function get_default_compose_url(): string {
$compose_url = home_url('/');
$pages = get_pages([
'meta_key' => '_wp_page_template',
'meta_value' => 'templates/pages/page-submit.php',
'number' => 1,
]);
if (!empty($pages[0])) {
$compose_url = get_permalink($pages[0]);
}
if ($compose_url === home_url('/') && function_exists('aksrt_get_user_center_url')) {
$compose_url = aksrt_get_user_center_url('posts');
}
return $compose_url;
}
private function get_default_cover_url(): string {
return get_theme_file_uri(self::DEFAULT_COVER_FILE);
}
private function get_guest_cover_url(array $instance): string {
$cover = esc_url_raw(trim((string) ($instance['guest_cover'] ?? '')));
return $cover !== '' ? $cover : $this->get_default_cover_url();
}
/** Zibll CFS_Module::zib_palette() gradient/solid keys (c/b/jb families). */
public static function palette_opts(): array {
return [
'jb-red' => __('红', 'aksrt'),
'jb-yellow' => __('橙', 'aksrt'),
'jb-pink' => __('粉', 'aksrt'),
'jb-cyan' => __('青', 'aksrt'),
'jb-blue' => __('蓝', 'aksrt'),
'jb-green' => __('绿', 'aksrt'),
'jb-purple' => __('紫', 'aksrt'),
'jb-vip1' => __('会员金', 'aksrt'),
'jb-vip2' => __('会员灰', 'aksrt'),
'b-red' => __('红(纯色)', 'aksrt'),
'b-yellow' => __('橙(纯色)', 'aksrt'),
'b-cyan' => __('青(纯色)', 'aksrt'),
'b-blue' => __('蓝(纯色)', 'aksrt'),
'b-green' => __('绿(纯色)', 'aksrt'),
'b-purple' => __('紫(纯色)', 'aksrt'),
'b-black' => __('黑(纯色)', 'aksrt'),
];
}
public static function sanitize_palette(string $value, string $fallback): string {
$value = sanitize_html_class($value);
return array_key_exists($value, self::palette_opts()) ? $value : $fallback;
}
}
-150
View File
@@ -1,150 +0,0 @@
<?php
/**
* Video player widget (zibll dplayer lite via data-aksrt-video + embed).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetVideoPlayer extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_video_player',
__('AKSRT 视频播放器', 'aksrt'),
[
'description' => __('侧栏/流体区嵌入视频(共享 aksrt-video 播放器)。', 'aksrt'),
'classname' => 'widget_aksrt_video_player',
]
);
}
public function widget($args, $instance): void {
$title = (string) ($instance['title'] ?? '');
$video_url = trim((string) ($instance['video_url'] ?? $instance['url'] ?? ''));
$poster = trim((string) ($instance['poster'] ?? $instance['pic'] ?? ''));
$autoplay = !empty($instance['autoplay']);
$loop = !empty($instance['loop']);
$muted = $autoplay || !empty($instance['muted']);
$hide_controller = !empty($instance['hide_controller']);
$scale = max(20, min(100, (int) ($instance['scale_height'] ?? 56)));
$aspect = (string) ($instance['aspect_ratio'] ?? '');
if ($video_url === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请配置视频 URL', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('视频播放器:', 'aksrt') . '</b>' . esc_html__('请配置视频 URL 或移出该模块', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($aspect !== '' && str_contains($aspect, ':')) {
$parts = explode(':', $aspect);
if (count($parts) === 2 && (int) $parts[0] > 0) {
$scale = (int) round(((int) $parts[1] / (int) $parts[0]) * 100);
}
}
$embed = $this->get_embed_url($video_url);
$cls = 'aksrt-video-widget relative-h radius8' . ($hide_controller ? ' controller-hide' : '');
echo '<div class="' . esc_attr($cls) . '" style="padding-bottom:' . esc_attr((string) $scale) . '%">';
if ($embed !== '') {
$src = $embed;
if ($autoplay) {
$src = add_query_arg(['autoplay' => '1', 'mute' => '1'], $src);
}
echo '<iframe class="aksrt-video-widget__frame" src="' . esc_url($src) . '" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>';
} else {
// Shared player stack (assets/js/video-player.js).
$type = 'mp4';
if (preg_match('/\.m3u8(\?|$)/i', $video_url)) {
$type = 'hls';
} elseif (preg_match('/\.flv(\?|$)/i', $video_url)) {
$type = 'flv';
}
printf(
'<div class="aksrt-video aksrt-video-widget__player" data-aksrt-video data-src="%s" data-type="%s"%s%s%s%s></div>',
esc_url($video_url),
esc_attr($type),
$poster !== '' ? ' data-poster="' . esc_url($poster) . '"' : '',
$autoplay ? ' data-autoplay="true"' : '',
$loop ? ' data-loop="true"' : '',
$muted ? ' data-muted="true"' : ''
);
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function get_embed_url(string $url): string {
if (preg_match('#(?:youtube\.com/watch\?v=|youtu\.be/)([A-Za-z0-9_-]+)#', $url, $m)) {
return 'https://www.youtube.com/embed/' . rawurlencode($m[1]);
}
if (preg_match('#bilibili\.com/video/(BV[\w]+)#', $url, $m)) {
return 'https://player.bilibili.com/player.html?bvid=' . rawurlencode($m[1]) . '&high_quality=1';
}
if (preg_match('#v\.qq\.com/.+?/([A-Za-z0-9]+)\.html#', $url, $m)) {
return 'https://v.qq.com/txp/iframe/player.html?vid=' . rawurlencode($m[1]);
}
return '';
}
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('视频 URL(mp4/m3u8/flv 或 YouTube/B站/腾讯)', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('video_url')); ?>" value="<?php echo esc_attr((string) ($instance['video_url'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('封面图 URL', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('poster')); ?>" value="<?php echo esc_attr((string) ($instance['poster'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="20" max="100" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['scale_height'] ?? 56))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop'])); ?>> <?php esc_html_e('循环', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_controller')); ?>" value="1" <?php checked(!empty($instance['hide_controller'])); ?>> <?php esc_html_e('隐藏控制条样式', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'video_url' => esc_url_raw(trim((string) ($new_instance['video_url'] ?? ''))),
'poster' => esc_url_raw(trim((string) ($new_instance['poster'] ?? ''))),
'scale_height' => max(0, min(200, (int) ($new_instance['scale_height'] ?? 56))),
'aspect_ratio' => sanitize_text_field((string) ($new_instance['aspect_ratio'] ?? '')),
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
'loop' => !empty($new_instance['loop']) ? 1 : 0,
'muted' => !empty($new_instance['muted']) ? 1 : 0,
'hide_controller' => !empty($new_instance['hide_controller']) ? 1 : 0,
]; return $business;
}
}
-167
View File
@@ -1,167 +0,0 @@
<?php
/**
* Yiyan / Hitokoto widget (zibll widget_ui_yiyan shell + aksrt_render_yiyan_block).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetYiyan extends \WP_Widget {
/**
* @return array<string,string>
*/
private static function type_labels(): array {
return [
'' => __('随机', 'aksrt'),
'a' => __('动画', 'aksrt'),
'b' => __('漫画', 'aksrt'),
'c' => __('游戏', 'aksrt'),
'd' => __('文学', 'aksrt'),
'e' => __('原创', 'aksrt'),
'f' => __('来自网络', 'aksrt'),
'g' => __('其他', 'aksrt'),
'h' => __('影视', 'aksrt'),
'i' => __('诗词', 'aksrt'),
'j' => __('网易云', 'aksrt'),
'k' => __('哲学', 'aksrt'),
'l' => __('抖机灵', 'aksrt'),
];
}
private static function sanitize_type(string $type): string {
$type = strtolower(trim($type));
return preg_match('/^[a-l]$/', $type) ? $type : '';
}
public function __construct() {
parent::__construct(
'aksrt_yiyan',
__('AKSRT 一言轮播', 'aksrt'),
[
'description' => __('显示随机一言/每日语录,支持自动轮播。', 'aksrt'),
'classname' => 'widget_aksrt_yiyan yiyan-box',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$count = !empty($instance['count']) ? max(1, min(10, (int) $instance['count'])) : 5;
$interval = !empty($instance['interval']) ? max(3, min(30, (int) $instance['interval'])) : 5;
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-quote-left';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// zibll shell: theme-box + yiyan-box main-bg text-center box-body radius8 main-shadow.
echo '<div class="theme-box yiyan-box-wrap">';
echo '<div class="yiyan-box main-bg text-center box-body radius8 main-shadow">';
if (function_exists('aksrt_render_yiyan_block')) {
aksrt_render_yiyan_block([
'class' => 'aksrt-yiyan yiyan',
'type' => $type,
'count' => $count,
'interval' => $interval,
'icon' => $icon,
]);
} else {
?>
<div class="aksrt-yiyan yiyan"
data-aksrt-yiyan
data-count="<?php echo esc_attr((string) $count); ?>"
data-interval="<?php echo esc_attr((string) $interval); ?>"
<?php echo $type !== '' ? ' data-type="' . esc_attr($type) . '"' : ''; ?>>
<div class="aksrt-yiyan__content">
<i class="<?php echo esc_attr($icon); ?> aksrt-yiyan__icon" aria-hidden="true"></i>
<p class="aksrt-yiyan__text"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
<p class="aksrt-yiyan__from"></p>
</div>
<div class="aksrt-yiyan__dots" aria-hidden="true"></div>
</div>
<?php
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '一言';
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
$interval = !empty($instance['interval']) ? (int) $instance['interval'] : 5;
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-quote-left';
$labels = self::type_labels();
$form_types = ['', 'a', 'd', 'i', 'k', 'h', 'j'];
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('轮播条数', 'aksrt'); ?></label>
<input id="<?php echo esc_attr($this->get_field_id('count')); ?>"
name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number"
min="1" max="10" value="<?php echo esc_attr((string) $count); ?>" style="width:80px;">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('interval')); ?>"><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?></label>
<input id="<?php echo esc_attr($this->get_field_id('interval')); ?>"
name="<?php echo esc_attr($this->get_field_name('interval')); ?>" type="number"
min="3" max="30" value="<?php echo esc_attr((string) $interval); ?>" style="width:80px;">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('type')); ?>"><?php esc_html_e('分类', 'aksrt'); ?></label>
<select id="<?php echo esc_attr($this->get_field_id('type')); ?>"
name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<?php foreach ($form_types as $code) : ?>
<option value="<?php echo esc_attr($code); ?>" <?php selected($type, $code); ?>>
<?php echo esc_html($labels[$code] ?? $code); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>"
name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text"
value="<?php echo esc_attr($icon); ?>">
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(10, absint($new_instance['count'] ?? 5))),
'interval' => max(3, min(30, absint($new_instance['interval'] ?? 5))),
'type' => self::sanitize_type((string) ($new_instance['type'] ?? '')),
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-quote-left')),
];
}
}
+3
View File
@@ -12,6 +12,7 @@
defined('ABSPATH') || exit;
get_header();
\AKSRT\WidgetSystem\WidgetAreas::render('msg_top_fluid', 'fluid-widget-wrap');
if (!is_user_logged_in()) {
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
@@ -35,6 +36,7 @@ if (!$messages_enabled) :
</div>
</main>
<?php
\AKSRT\WidgetSystem\WidgetAreas::render('msg_bottom_fluid', 'fluid-widget-wrap');
get_footer();
return;
endif;
@@ -344,4 +346,5 @@ if (!$messages_base) {
</div>
</main>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('msg_bottom_fluid', 'fluid-widget-wrap'); ?>
<?php get_footer(); ?>
+4
View File
@@ -38,12 +38,14 @@ $render_context = aksrt_get_user_center_render_context($current_user);
get_header();
?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('user_top_fluid', 'fluid-widget-wrap'); ?>
<div id="toast-container"></div>
<script>function showToast(text,d){d=d||2500;var c=document.getElementById('toast-container');if(!c){c=document.createElement('div');c.id='toast-container';document.body.appendChild(c)}var t=document.createElement('div');t.className='toast-item';t.textContent=text;c.appendChild(t);setTimeout(function(){t.classList.add('fade-out');setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300)},d)}</script>
<main id="main-content" class="site-main container content-shell content-shell--stream aksrt-profile user-center" tabindex="-1">
<div class="content-wrap">
<?php get_template_part('templates/parts/user-center/header', null, ['context' => $render_context]); ?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('user_top_content', 'aksrt-user-center__widget-zone'); ?>
<div class="user-center-body aksrt-profile__layout">
<aside class="user-center-sidebar uc-sidebar aksrt-profile__aside" role="complementary" aria-label="<?php esc_attr_e('用户中心导航', 'aksrt'); ?>">
<?php get_template_part('templates/parts/user-center/sidebar', null, ['context' => $render_context]); ?>
@@ -66,5 +68,7 @@ get_header();
<?php get_template_part('templates/parts/user-center/runtime-config', null, ['context' => $render_context]); ?>
</main>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('user_bottom_fluid', 'fluid-widget-wrap'); ?>
<?php
get_footer();
+2
View File
@@ -126,6 +126,7 @@ get_header();
}
?>
<aside class="aksrt-submit-sidebar">
<?php \AKSRT\WidgetSystem\WidgetAreas::render('newposts_sidebar_top', 'aksrt-submit-sidebar__widgets'); ?>
<?php
$terms = get_template_directory() . '/templates/parts/submit/sidebar-terms.php';
if (file_exists($terms)) {
@@ -140,6 +141,7 @@ get_header();
include $actions;
}
?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('newposts_sidebar_bottom', 'aksrt-submit-sidebar__widgets'); ?>
</aside>
</div>
</form>
+4
View File
@@ -32,6 +32,8 @@ extract($ctx, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extr
require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php';
?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('author_top_fluid', 'fluid-widget-wrap'); ?>
<main id="main-content" class="site-main container content-shell content-shell--stream aksrt-user-home" tabindex="-1">
<div class="content-wrap">
<div class="content-layout content-layout--no-sidebar aksrt-user-home__layout">
@@ -75,4 +77,6 @@ require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php';
</div>
</main>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('author_bottom_fluid', 'fluid-widget-wrap'); ?>
<?php get_footer(); ?>
+94 -1
View File
@@ -12,6 +12,11 @@ if (!function_exists('sanitize_text_field')) {
return trim(preg_replace('/[\r\n\t]+/', ' ', strip_tags((string) $value)) ?? '');
}
}
if (!function_exists('__')) {
function __($text): string {
return (string) $text;
}
}
if (!function_exists('sanitize_textarea_field')) {
function sanitize_textarea_field($value): string {
return trim(strip_tags((string) $value));
@@ -45,7 +50,9 @@ require dirname(__DIR__) . '/vendor/autoload.php';
use AKSRT\WidgetSystem\FieldSchema;
use AKSRT\WidgetSystem\PostQuery;
use AKSRT\WidgetSystem\RendererRegistry;
use AKSRT\WidgetSystem\WidgetAreas;
use AKSRT\WidgetSystem\WidgetRegistry;
use AKSRT\WidgetSystem\WidgetTransfer;
$errors = [];
$expected = [
@@ -65,6 +72,36 @@ if (array_keys($registry) !== $expected) {
$errors[] = 'Registry slugs or ordering differ from the frozen contract.';
}
$expected_areas = [
'all_top_fluid', 'all_bottom_fluid', 'all_sidebar_top', 'all_sidebar_bottom',
'home_top_fluid', 'home_top_content', 'home_bottom_content', 'home_bottom_fluid', 'home_sidebar',
'single_top_fluid', 'single_top_content', 'single_bottom_content', 'single_bottom_fluid', 'single_sidebar',
'cat_top_fluid', 'cat_top_content', 'cat_bottom_content', 'cat_bottom_fluid', 'cat_sidebar',
'tag_top_fluid', 'tag_top_content', 'tag_bottom_content', 'tag_bottom_fluid', 'tag_sidebar',
'search_top_fluid', 'search_top_content', 'search_bottom_content', 'search_bottom_fluid', 'search_sidebar',
'author_top_fluid', 'author_bottom_fluid', 'msg_top_fluid', 'msg_bottom_fluid',
'user_top_fluid', 'user_top_content', 'user_bottom_fluid',
'newposts_sidebar_top', 'newposts_sidebar_bottom', 'all_footer', 'mobile_nav_fluid',
];
if (WidgetAreas::static_ids() !== $expected_areas || count(WidgetAreas::static_ids()) !== 40) {
$errors[] = 'Widget area registry must contain the exact 40 static areas.';
}
$templates = WidgetTransfer::templates();
$expected_templates = ['article_home', 'article_list', 'links_navigation', 'article_sidebar', 'knowledge_landing'];
if (array_keys($templates) !== $expected_templates || count($templates) !== 5) {
$errors[] = 'Import/export must expose the exact five built-in templates.';
}
foreach ($templates as $template => $contract) {
foreach ((array) ($contract['sidebars'] ?? []) as $items) {
foreach ((array) $items as $item) {
if (!isset($registry[(string) ($item['widget'] ?? '')])) {
$errors[] = $template . ': template contains an unknown widget.';
}
}
}
}
$supported = FieldSchema::supported_types();
$walk = static function (array $fields, string $owner) use (&$walk, &$errors, $supported): void {
$ids = [];
@@ -173,6 +210,62 @@ foreach ($frontend_files as $frontend_file) {
}
}
$root = dirname(__DIR__);
$legacy_paths = [
'/src/Core/WidgetAjax.php',
'/src/Core/SetupPart1.php',
'/src/Widgets',
'/inc/modules/widgets-register.php',
'/inc/modules/widget-common.php',
'/inc/modules/widget-areas.php',
'/assets/css/frontend/widgets.css',
'/assets/css/frontend/widgets',
'/assets/css/frontend/widget-batch5.css',
'/assets/js/widget-set.js',
];
foreach ($legacy_paths as $legacy_path) {
if (file_exists($root . $legacy_path)) {
$errors[] = 'Legacy widget runtime remains: ' . $legacy_path;
}
}
$runtime_source = (string) file_get_contents($root . '/src/WidgetSystem/Runtime.php');
foreach (['WidgetAreas::class', 'WidgetRegistry::all()', 'register_widget(new SchemaWidget', 'unregister_core_widgets'] as $contract) {
if (!str_contains($runtime_source, $contract)) {
$errors[] = 'Widget runtime missing contract: ' . $contract;
}
}
$asset_source = (string) file_get_contents($root . '/src/WidgetSystem/AssetManager.php');
if (str_contains($asset_source, 'cdn.jsdelivr.net')) {
$errors[] = 'Widget assets must not use CDN dependencies.';
}
$admin_import_sources = (string) file_get_contents($root . '/src/Admin/ImportExportController.php')
. (string) file_get_contents($root . '/src/Admin/ImportExportControllerPart1.php');
foreach (['/widgets/export', '/widgets/import', '/widgets/templates', 'aksrt_widget_templates'] as $legacy_contract) {
if (str_contains($admin_import_sources, $legacy_contract)) {
$errors[] = 'Legacy widget import/export contract remains: ' . $legacy_contract;
}
}
$pinned_assets = [
'DPlayer.min.js' => '361689807fc1e856f868df91036e41195d2c255619bd1c5526849c5ad7491241',
'hls.min.js' => 'e876282a26116e323c8ed3e86bed3838a80dbbe3a2faa09f834bee86963ff760',
'flv.min.js' => '0668d36757cdc17804d5c0bd6fe8956d76370e73a733bf5bbc0eddbfbca2c6cc',
'dash.all.min.js' => '3e753969c16a2a78f420a25d24869f14721eb1d284418f9f22d35a31c3693cd9',
'vanilla-tilt.min.js' => 'de6fa8d3f40dbae2726a72f7d1ae46bb0588267af05355bef1d128f737f4a282',
];
foreach ($pinned_assets as $asset => $hash) {
$path = $root . '/assets/widgets/third-party/' . $asset;
if (!is_file($path)) {
$errors[] = 'Missing pinned widget dependency: ' . $asset;
} elseif (hash_file('sha256', $path) !== $hash) {
$errors[] = 'Pinned widget dependency changed: ' . $asset;
}
}
if (!is_file($root . '/assets/widgets/third-party/THIRD_PARTY.md')) {
$errors[] = 'Missing widget dependency license manifest.';
}
$posts = $registry['posts'];
$normalized = $posts->normalize([
'title' => '<script>alert(1)</script> 最新课程',
@@ -215,4 +308,4 @@ if ($errors) {
exit(1);
}
echo "Widget system audit passed: 37 definitions/renderers, frozen baseline, query/REST/assets contracts.\n";
echo "Widget system audit passed: 37 widgets, 40 areas, 5 templates, single runtime and pinned assets.\n";