feat(widgets): complete phase 3 atomic integration
This commit is contained in:
@@ -27,10 +27,10 @@
|
||||
|
||||
## Phase 3 — 原子切换、挂载区域与导入导出
|
||||
|
||||
- [ ] 替换注册器并删除旧小工具类、旧公共层、旧路由和重复资源。
|
||||
- [ ] 重写 40 个静态挂载区域、动态页面区域及所有模板消费点。
|
||||
- [ ] 实现 `aksrt.widgets.v1` 导入导出和 5 套知识付费模板。
|
||||
- [ ] 核对默认 WordPress 小工具注销策略与区块编辑器/自定义器行为。
|
||||
- [x] 替换注册器并删除旧小工具类、旧公共层、旧路由和重复资源。
|
||||
- [x] 重写 40 个静态挂载区域、动态页面区域及所有模板消费点。
|
||||
- [x] 实现 `aksrt.widgets.v1` 导入导出和 5 套知识付费模板。
|
||||
- [x] 核对默认 WordPress 小工具注销策略与区块编辑器/自定义器行为。
|
||||
|
||||
## Phase 4 — 自动化与视觉终验
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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; }
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
})();
|
||||
@@ -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
@@ -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 () {
|
||||
|
||||
+2
File diff suppressed because one or more lines are too long
+15
@@ -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.
|
||||
+4
File diff suppressed because one or more lines are too long
+7
File diff suppressed because one or more lines are too long
+3
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-12
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
?>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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'); ?>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
@@ -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; ?>
|
||||
|
||||
@@ -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'],
|
||||
]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
@@ -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', []);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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'])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -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>',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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');} }
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -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'] ?? '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?php
|
||||
namespace AKSRT\Widgets;
|
||||
require_once __DIR__ . '/MarketingBlocksWidget.php';
|
||||
@@ -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="系统维护通知 新功能上线|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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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(); ?>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(); ?>
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user