feat(widgets): simplify locations and paid sidebar

Remove deprecated widget locations and widgets, standardize sidebar labels, and render a compact paid-post purchase card in the article sidebar.
This commit is contained in:
2026-08-14 01:49:50 +08:00
parent 51d2ab3aa7
commit bd7d3046a5
43 changed files with 294 additions and 3581 deletions
-92
View File
@@ -216,98 +216,6 @@
.aksrt-img-grid--4 { grid-template-columns: repeat(3, 1fr); }
}
/* ============================================================
* 4. 定价卡片 — .aksrt-pricing-card
* ============================================================ */
.aksrt-pricing-card {
position: relative;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, rgba(50, 50, 50, 0.06));
border-radius: var(--radius-lg, 8px);
padding: 28px 24px;
text-align: center;
transition: box-shadow .25s ease, transform .25s ease;
}
.aksrt-pricing-card:hover {
box-shadow: var(--shadow-lg, 0 0 10px rgba(116, 116, 116, 0.12));
transform: none;
}
/* 推荐/高亮卡片 */
.aksrt-pricing-card--featured {
border-color: var(--color-primary);
box-shadow: 0 0 0 1px var(--color-primary), var(--shadow-lg);
}
.aksrt-pricing-card__badge {
position: absolute;
top: -1px;
left: 50%;
transform: translateX(-50%);
background: var(--color-primary);
color: #fff;
padding: 2px 16px;
border-radius: 0 0 var(--radius, 8px) var(--radius, 8px);
font-size: 12px;
font-weight: 700;
}
.aksrt-pricing-card__title {
font-size: 18px;
font-weight: 700;
color: var(--color-ink, #333);
margin-bottom: 8px;
}
.aksrt-pricing-card__price {
font-size: 36px;
font-weight: 800;
color: var(--color-primary);
line-height: 1.2;
margin: 12px 0 4px;
}
.aksrt-pricing-card__price small {
font-size: 14px;
font-weight: 500;
color: var(--color-ink-3, #777);
}
.aksrt-pricing-card__desc {
font-size: 14px;
color: var(--color-ink-3, #777);
margin-bottom: 20px;
}
.aksrt-pricing-card__features {
list-style: none;
padding: 0;
margin: 0 0 24px;
text-align: left;
}
.aksrt-pricing-card__features li {
padding: 6px 0;
font-size: 14px;
color: var(--color-ink-2, #4e5358);
display: flex;
align-items: center;
gap: 8px;
}
.aksrt-pricing-card__features li::before {
content: '✓';
color: var(--color-success);
font-weight: 700;
font-size: 13px;
}
.aksrt-pricing-card__cta {
display: block;
width: 100%;
}
/* ============================================================
* 5. Toggle 开关 — .aksrt-toggle (纯 CSS + :checked)
* 保持原生 <input> 可访问性
@@ -1,15 +1,3 @@
/* Dark mode */
.theme-dark .aksrt-lang-switch__toggle,
html.dark .aksrt-lang-switch__toggle {
background: #2a2a3a;
border-color: rgba(255, 255, 255, 0.15);
}
.theme-dark .aksrt-lang-switch__dropdown,
html.dark .aksrt-lang-switch__dropdown {
background: #2a2a3a;
border-color: rgba(255, 255, 255, 0.15);
}
/* ============================================================
* 响应式
@@ -15,125 +15,3 @@
font-size: 18px;
}
}
/* ============================================================
* 22. 语言切换器
* ============================================================ */
.aksrt-lang-switch {
position: relative;
}
.aksrt-lang-switch__toggle {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: 1px solid var(--color-line);
border-radius: var(--radius, 10px);
background: var(--surface-1, #fff);
color: var(--color-ink-2);
font-size: 13px;
cursor: pointer;
transition: border-color 0.2s;
width: 100%;
}
.aksrt-lang-switch__toggle:hover {
border-color: var(--color-primary, var(--color-primary));
}
.aksrt-lang-switch__toggle img {
border-radius: 2px;
}
.aksrt-lang-switch__dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin: 4px 0 0;
padding: 4px;
list-style: none;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line);
border-radius: var(--radius, 10px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
z-index: 100;
opacity: 0;
visibility: hidden;
transform: translateY(4px);
transition: all 0.2s;
}
.aksrt-lang-switch:hover .aksrt-lang-switch__dropdown,
.aksrt-lang-switch__toggle:focus + .aksrt-lang-switch__dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.aksrt-lang-switch__item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
border-radius: 6px;
font-size: 13px;
color: var(--color-ink-2);
text-decoration: none;
transition: background 0.15s;
}
.aksrt-lang-switch__item:hover {
background: var(--surface-2, #F4F5F7);
}
.aksrt-lang-switch__item.is-active {
color: var(--color-primary, var(--color-primary));
font-weight: 600;
}
.aksrt-lang-switch__item img {
border-radius: 2px;
}
/* 国旗模式 */
.aksrt-lang-switch--flags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.aksrt-lang-switch__flag {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 24px;
border: 2px solid transparent;
border-radius: 4px;
overflow: hidden;
transition: border-color 0.2s;
text-decoration: none;
}
.aksrt-lang-switch__flag:hover {
border-color: var(--color-primary, var(--color-primary));
}
.aksrt-lang-switch__flag.is-active {
border-color: var(--color-primary, var(--color-primary));
}
.aksrt-lang-switch__flag img {
width: 100%;
height: 100%;
object-fit: cover;
}
.aksrt-lang-switch__flag span {
font-size: 10px;
font-weight: 600;
color: var(--color-ink-3);
}
@@ -376,9 +376,8 @@ html.dark .article-end-separator span {
padding-left: 24px;
}
/* WidgetPostsNavs + built-in shell share .posts-nav-box.toc-widget */
.widget .posts-nav-box.toc-widget,
.widget_aksrt_posts_navs .posts-nav-box.toc-widget {
/* Built-in article TOC shell. */
.sidebar .posts-nav-box.toc-widget {
margin: 0;
box-shadow: none;
background: transparent;
@@ -386,9 +385,8 @@ html.dark .article-end-separator span {
border-radius: 0;
}
.widget .posts-nav-box.toc-widget > h3,
.widget .posts-nav-box.toc-widget .toc-widget__title,
.widget_aksrt_posts_navs .posts-nav-box.toc-widget > h3 {
.sidebar .posts-nav-box.toc-widget > h3,
.sidebar .posts-nav-box.toc-widget .toc-widget__title {
margin: 0 0 10px;
font-size: 15px;
font-weight: 700;
+1 -248
View File
@@ -1,154 +1,8 @@
/**
* Batch3 widgets: partners scroll, iframe, combo info, text title, user lists, plate widgets.
* Batch3 widgets: iframe, combo info, text title, user lists, plate widgets.
* @package AKSRT
*/
/* ---- Partners scroll ---- */
.aksrt-partners-scroll-wrap {
margin-bottom: 20px;
}
.aksrt-partners-scroll-wrap.is-bare {
background: transparent;
box-shadow: none;
padding: 0;
}
.aksrt-partners-scroll {
overflow: hidden;
position: relative;
--aksrt-ps-w: 220px;
--aksrt-ps-h: 90px;
--aksrt-ps-radius: 10px;
--aksrt-ps-speed: 3;
}
.aksrt-ps-end-fade::before,
.aksrt-ps-end-fade::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
width: 48px;
z-index: 2;
pointer-events: none;
}
.aksrt-ps-end-fade::before {
left: 0;
background: linear-gradient(90deg, var(--surface-1, #fff), transparent);
}
.aksrt-ps-end-fade::after {
right: 0;
background: linear-gradient(270deg, var(--surface-1, #fff), transparent);
}
.aksrt-ps-row {
overflow: hidden;
margin-bottom: 10px;
}
.aksrt-ps-row:last-child {
margin-bottom: 0;
}
.aksrt-ps-track {
display: flex;
width: max-content;
animation: aksrt-ps-marquee calc(40s / var(--aksrt-ps-speed)) linear infinite;
}
.aksrt-ps-dir-right .aksrt-ps-track {
animation-name: aksrt-ps-marquee-rev;
}
.aksrt-ps-pause-hover:hover .aksrt-ps-track {
animation-play-state: paused;
}
.aksrt-ps-group {
display: flex;
flex-shrink: 0;
align-items: center;
}
.aksrt-ps-item {
display: flex;
align-items: center;
flex-shrink: 0;
width: var(--aksrt-ps-w);
height: var(--aksrt-ps-h);
margin-right: 12px;
border-radius: var(--aksrt-ps-radius);
overflow: hidden;
text-decoration: none;
color: inherit;
box-sizing: border-box;
padding: 8px 12px;
}
.aksrt-ps-card-bg .aksrt-ps-item {
background: var(--surface-2, #f3f4f6);
}
.aksrt-ps-img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
display: block;
}
.aksrt-ps-style-logo .aksrt-ps-item {
justify-content: center;
padding: 10px;
}
.aksrt-ps-card-icon {
width: 42px;
height: 42px;
flex-shrink: 0;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.aksrt-ps-card-icon .aksrt-ps-img {
max-height: 42px;
}
.aksrt-ps-card-text {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
}
.aksrt-ps-card-title {
font-weight: 700;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.aksrt-ps-card-desc {
font-size: 12px;
color: var(--muted-2-color, #999);
margin-top: 3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.aksrt-ps-style-chip .aksrt-ps-item {
width: auto;
height: auto;
min-height: 36px;
border-radius: 999px;
padding: 6px 14px;
gap: 6px;
}
.aksrt-ps-chip-icon {
width: 22px;
height: 22px;
display: flex;
align-items: center;
margin-right: 6px;
}
.aksrt-ps-chip-title {
font-size: 13px;
white-space: nowrap;
}
@keyframes aksrt-ps-marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@keyframes aksrt-ps-marquee-rev {
from { transform: translateX(-50%); }
to { transform: translateX(0); }
}
/* ---- Iframe embed ---- */
.aksrt-iframe-embed {
margin-bottom: 16px;
@@ -532,75 +386,6 @@
}
/* ---- Pricing plans ---- */
.aksrt-pricing { margin-bottom: 16px; }
.aksrt-pricing__cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
}
.aksrt-pricing__card {
position: relative;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 14px;
padding: 22px 18px 18px;
background: var(--surface-1, #fff);
display: flex;
flex-direction: column;
min-height: 100%;
}
.aksrt-pricing__card.is-featured {
box-shadow: 0 10px 28px rgba(0,0,0,.08);
border-color: transparent;
border-top: 3px solid var(--plan-accent, #3b82f6);
}
.aksrt-pricing__card.accent-pink { --plan-accent: #ec4899; }
.aksrt-pricing__card.accent-purple { --plan-accent: #a855f7; }
.aksrt-pricing__card.accent-blue { --plan-accent: #3b82f6; }
.aksrt-pricing__card.accent-green { --plan-accent: #22c55e; }
.aksrt-pricing__card.accent-yellow { --plan-accent: #eab308; }
.aksrt-pricing__card.accent-cyan { --plan-accent: #06b6d4; }
.aksrt-pricing__badge {
position: absolute;
top: 12px;
right: 12px;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: var(--plan-accent, #3b82f6);
color: #fff;
}
.aksrt-pricing__name { font-size: 18px; font-weight: 700; margin: 0 0 6px; }
.aksrt-pricing__desc { font-size: 13px; color: var(--muted-2-color, #888); margin: 0 0 12px; }
.aksrt-pricing__price { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.aksrt-pricing__main { font-size: 32px; font-weight: 800; color: var(--plan-accent, #3b82f6); line-height: 1; }
.aksrt-pricing__mark { font-size: .55em; margin-right: 2px; }
.aksrt-pricing__original { font-size: 13px; color: #999; text-decoration: line-through; }
.aksrt-pricing__features { list-style: none; margin: 0 0 16px; padding: 0; flex: 1; }
.aksrt-pricing__features li { display: flex; gap: 8px; font-size: 13px; padding: 5px 0; }
.aksrt-pricing__features i { color: var(--plan-accent, #3b82f6); margin-top: 2px; }
.aksrt-pricing__btn {
display: block;
text-align: center;
padding: 10px 14px;
border-radius: 999px;
background: var(--plan-accent, #3b82f6);
color: #fff !important;
text-decoration: none;
font-weight: 600;
}
.aksrt-pricing__trust {
display: flex;
flex-wrap: wrap;
gap: 14px 22px;
margin-top: 18px;
justify-content: center;
}
.aksrt-pricing__trust-item { display: flex; gap: 8px; align-items: flex-start; font-size: 13px; }
.aksrt-pricing__trust-item i { color: var(--color-primary, #3b82f6); margin-top: 2px; }
.aksrt-pricing__trust-item strong { display: block; }
.aksrt-pricing__trust-item span { color: var(--muted-2-color, #888); font-size: 12px; }
/* ---- FAQ ---- */
.aksrt-faq { margin-bottom: 16px; }
.aksrt-faq__search { display: flex; gap: 8px; margin-bottom: 14px; }
@@ -671,38 +456,6 @@
font-size: 13px;
}
/* ---- Stats ---- */
.aksrt-stats { margin-bottom: 16px; }
.aksrt-stats__intro { margin-bottom: 14px; }
.aksrt-stats__intro-title { font-size: 22px; margin: 0 0 6px; }
.aksrt-stats__intro-sub { color: var(--muted-2-color, #888); margin: 0; font-size: 13px; }
.aksrt-stats__grid {
display: grid;
grid-template-columns: repeat(var(--aksrt-stats-pc, 4), minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 767.98px) {
.aksrt-stats__grid {
grid-template-columns: repeat(var(--aksrt-stats-m, 2), minmax(0, 1fr));
}
}
.aksrt-stats__item {
border-radius: 12px;
padding: 16px 14px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
text-align: center;
}
.aksrt-stats__item.color-pink { --stat-c: #ec4899; }
.aksrt-stats__item.color-purple { --stat-c: #a855f7; }
.aksrt-stats__item.color-orange { --stat-c: #f97316; }
.aksrt-stats__item.color-blue { --stat-c: #3b82f6; }
.aksrt-stats__item.color-green { --stat-c: #22c55e; }
.aksrt-stats__icon { font-size: 20px; color: var(--stat-c, #3b82f6); margin-bottom: 8px; }
.aksrt-stats__value { font-size: 28px; font-weight: 800; color: var(--stat-c, #3b82f6); line-height: 1.1; }
.aksrt-stats__prefix, .aksrt-stats__unit { font-size: .55em; font-weight: 600; }
.aksrt-stats__label { margin-top: 6px; font-size: 12px; color: var(--muted-2-color, #888); }
/* ---- Forum topic lists ---- */
.aksrt-ftopic-list { list-style: none; margin: 0; padding: 0; }
.aksrt-ftopic-list__item {
+1 -63
View File
@@ -1,69 +1,7 @@
/**
* Batch3c: reviews + highlights.
* Batch3c: highlights.
*/
.aksrt-reviews {
margin-bottom: 16px;
--aksrt-rev-pc: 3;
--aksrt-rev-m: 1.2;
}
.aksrt-reviews__track {
display: grid;
grid-auto-flow: column;
grid-auto-columns: calc((100% - (var(--aksrt-rev-pc) - 1) * 12px) / var(--aksrt-rev-pc));
gap: 12px;
overflow-x: auto;
scroll-snap-type: x mandatory;
padding-bottom: 6px;
-webkit-overflow-scrolling: touch;
}
@media (max-width: 767.98px) {
.aksrt-reviews__track {
grid-auto-columns: calc((100% - (var(--aksrt-rev-m) - 1) * 12px) / var(--aksrt-rev-m));
}
}
.aksrt-reviews__slide { scroll-snap-align: start; min-width: 0; }
.aksrt-reviews__card {
position: relative;
border: 1px solid var(--color-line, #e5e7eb);
border-radius: 12px;
padding: 16px;
background: var(--surface-1, #fff);
min-height: 100%;
}
.aksrt-reviews__head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.aksrt-reviews__avatar {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
background: var(--surface-2, #eee);
display: inline-flex;
align-items: center;
justify-content: center;
color: #999;
flex-shrink: 0;
}
.aksrt-reviews__name { font-weight: 700; font-size: 14px; }
.aksrt-reviews__name a { color: inherit; text-decoration: none; }
.aksrt-reviews__role { font-size: 12px; color: var(--muted-2-color, #888); }
.aksrt-reviews__stars { margin-bottom: 8px; font-size: 12px; }
.aksrt-reviews__stars .is-on { color: #f5a623; }
.aksrt-reviews__stars .is-off { color: #ddd; }
.aksrt-reviews__content {
font-size: 13px;
line-height: 1.55;
color: var(--color-ink-2, #444);
margin: 0;
}
.aksrt-reviews__quote {
position: absolute;
right: 14px;
bottom: 10px;
opacity: .12;
font-size: 28px;
}
.aksrt-highlights { margin-bottom: 16px; display: flex; flex-direction: column; gap: 12px; }
.aksrt-highlights__row {
display: grid;
+1 -135
View File
@@ -1,107 +1,7 @@
/**
* Batch3d: feature intro, term cards, paid posts.
* Batch3d: term cards.
*/
.aksrt-fi {
display: grid;
grid-template-columns: 1fr 1.1fr;
gap: 28px;
align-items: center;
margin-bottom: 20px;
}
.aksrt-fi.media-left { grid-template-columns: 1.1fr 1fr; }
.aksrt-fi.media-left .aksrt-fi__text { order: 2; }
.aksrt-fi.media-left .aksrt-fi__media { order: 1; }
@media (max-width: 991.98px) {
.aksrt-fi,
.aksrt-fi.media-left {
grid-template-columns: 1fr;
}
.aksrt-fi.media-left .aksrt-fi__text,
.aksrt-fi.media-left .aksrt-fi__media { order: initial; }
}
.aksrt-fi__title {
font-size: 28px;
line-height: 1.25;
margin: 0 0 12px;
}
.aksrt-fi__desc {
font-size: 14px;
color: var(--muted-2-color, #666);
line-height: 1.7;
margin-bottom: 14px;
}
.aksrt-fi__stat {
display: flex;
align-items: baseline;
gap: 6px;
margin-bottom: 14px;
}
.aksrt-fi__stat-num {
font-size: 36px;
font-weight: 800;
color: var(--color-primary, #3b82f6);
line-height: 1;
}
.aksrt-fi__stat-plus { font-size: 22px; font-weight: 700; color: var(--color-primary, #3b82f6); }
.aksrt-fi__stat-label { font-size: 13px; color: var(--muted-2-color, #888); margin-left: 4px; }
.aksrt-fi__tabs { display: flex; flex-wrap: wrap; gap: 8px; }
.aksrt-fi__tab {
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
border-radius: 999px;
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
}
.aksrt-fi__tab.is-active {
background: var(--color-primary, #3b82f6);
border-color: transparent;
color: #fff;
}
.aksrt-fi__panel { display: none; }
.aksrt-fi__panel.is-active { display: block; }
.aksrt-fi__browser {
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-2, #f3f4f6);
box-shadow: 0 12px 32px rgba(0,0,0,.08);
}
.aksrt-fi__browser.is-plain { box-shadow: none; }
.aksrt-fi__browser-bar {
height: 28px;
background: #eef1f5;
display: flex;
align-items: center;
gap: 6px;
padding: 0 12px;
}
.aksrt-fi__browser-bar span {
width: 8px;
height: 8px;
border-radius: 50%;
background: #d0d5dd;
}
.aksrt-fi__browser-bar span:nth-child(1) { background: #ff5f57; }
.aksrt-fi__browser-bar span:nth-child(2) { background: #febc2e; }
.aksrt-fi__browser-bar span:nth-child(3) { background: #28c840; }
.aksrt-fi__browser-body {
position: relative;
width: 100%;
height: 0;
background: #111;
}
.aksrt-fi__img,
.aksrt-fi__video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.aksrt-term-cards {
display: grid;
grid-template-columns: repeat(var(--aksrt-tc-cols, 2), minmax(0, 1fr));
@@ -127,37 +27,3 @@
.aksrt-term-card__body { padding: 10px 12px 12px; }
.aksrt-term-card__name { font-weight: 700; font-size: 14px; }
.aksrt-term-card__count { font-size: 12px; color: var(--muted-2-color, #888); margin-top: 2px; }
.aksrt-paid-posts { list-style: none; margin: 0; padding: 0; }
.aksrt-paid-posts__item {
display: flex;
gap: 10px;
padding: 8px 0;
border-bottom: 1px dashed var(--color-line, #e5e7eb);
}
.aksrt-paid-posts__item:last-child { border-bottom: 0; }
.aksrt-paid-posts__thumb {
width: 56px;
height: 56px;
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
background: var(--surface-2, #eee);
}
.aksrt-paid-posts__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-paid-posts__body { min-width: 0; flex: 1; }
.aksrt-paid-posts__title {
display: block;
color: inherit;
text-decoration: none;
font-size: 13px;
font-weight: 600;
line-height: 1.4;
}
.aksrt-paid-posts__price {
display: inline-block;
margin-top: 4px;
font-size: 12px;
color: #e11d48;
font-weight: 700;
}
+43 -139
View File
@@ -1977,7 +1977,7 @@ a.main-color > .icon-cover-card:hover {
.widget_aksrt_icon_cover_cards .col-xs-3 { width: 25%; }
}
/* ===== Batch16-18: highlights/combo/reviews/announcement/gap/pricing polish ===== */
/* ===== Batch16-18: highlights/combo/announcement/gap polish ===== */
.aksrt-highlights__row {
display: grid;
@@ -2063,80 +2063,6 @@ a.main-color > .icon-cover-card:hover {
.aksrt-combo-info .col-xs-4 { width: 33.333%; }
}
/* Reviews swiper */
.aksrt-reviews-swiper {
position: relative;
overflow: hidden;
padding: 0 28px 4px;
}
.aksrt-reviews-swiper .swiper-slide {
width: calc(100% / var(--aksrt-rev-pc, 3));
max-width: 360px;
height: auto;
padding-right: 12px;
box-sizing: border-box;
}
@media (max-width: 767px) {
.aksrt-reviews-swiper .swiper-slide {
width: calc(100% / var(--aksrt-rev-m, 1.2));
}
}
.aksrt-reviews__card {
height: 100%;
border-radius: 12px;
border: 1px solid var(--color-line, #e5e7eb);
background: var(--surface-1, #fff);
padding: 16px;
position: relative;
}
.aksrt-reviews__head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.aksrt-reviews__avatar {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
display: block;
background: var(--surface-2, #eee);
}
.aksrt-reviews__avatar.is-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
color: #999;
}
.aksrt-reviews__name { font-weight: 700; }
.aksrt-reviews__role { font-size: 12px; color: var(--muted-2-color, #888); }
.aksrt-reviews__stars .fa-star.is-on { color: #f5b301; }
.aksrt-reviews__stars .fa-star.is-off { color: #ddd; }
.aksrt-reviews__content { margin: 10px 0 0; font-size: 13px; line-height: 1.6; color: var(--muted-color, #555); }
.aksrt-reviews__quote {
position: absolute;
right: 12px;
bottom: 10px;
opacity: .12;
font-size: 28px;
}
.aksrt-reviews-swiper .swiper-button-prev,
.aksrt-reviews-swiper .swiper-button-next {
width: 28px;
height: 28px;
margin-top: 0;
top: 42%;
border-radius: 50%;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
color: var(--color-ink, #111);
}
.aksrt-reviews-swiper .swiper-button-prev:after,
.aksrt-reviews-swiper .swiper-button-next:after { font-size: 12px; font-weight: 700; }
.aksrt-reviews-swiper .swiper-button-prev { left: 0; }
.aksrt-reviews-swiper .swiper-button-next { right: 0; }
/* Announcement vertical swiper reuse notice styles */
.aksrt-announcement-swiper-wrap {
display: flex;
@@ -2227,36 +2153,6 @@ a.main-color > .icon-cover-card:hover {
padding: 1px 8px;
}
.aksrt-paid-posts__item.is-mini {
position: relative;
margin-bottom: 10px;
}
.aksrt-paid-posts__item.is-mini .aksrt-paid-posts__price.badge {
position: absolute;
top: 6px;
right: 6px;
z-index: 2;
background: rgba(239, 68, 68, .92);
color: #fff;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
}
.aksrt-lang-switch-widget.hide-name .aksrt-lang-switch__toggle span,
.aksrt-lang-switch-widget.hide-name .aksrt-lang-switch__item {
font-size: 0;
}
.aksrt-lang-switch-widget.hide-current .aksrt-lang-switch__item.is-active,
.aksrt-lang-switch-widget.hide-current .aksrt-lang-switch__flag.is-active {
display: none;
}
.aksrt-lang-switch-widget.style-inline .aksrt-lang-switch--flags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.aksrt-forum-widget__item.is-top .aksrt-forum-widget__rank {
background: var(--theme-color, #f04494);
color: #fff;
@@ -2380,8 +2276,6 @@ a.main-color > .icon-cover-card:hover {
justify-content: center;
}
.aksrt-forum-widget__rank-row badge.img-badge { position: static; flex: 0 0 auto; margin-top: 4px; }
.aksrt-stats__item[data-aksrt-animate] { will-change: transform, opacity; }
/* batch28-30 residual */
.aksrt-ftab.widget-tab-posts .aksrt-ftab__nav { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0 0 12px; padding: 0; }
@@ -2392,14 +2286,6 @@ a.main-color > .icon-cover-card:hover {
.aksrt-ftab__btn.is-active { background: var(--theme-color, #f04494); color: #fff; }
.aksrt-ftab__panel[hidden] { display: none !important; }
.aksrt-ftab__panel.is-ajax-loading { opacity: .65; pointer-events: none; }
.aksrt-reviews__avatar.aksrt-lazy { object-fit: cover; border-radius: 50%; }
/* batch31-33 residual */
.aksrt-fi__img.aksrt-lazy { width: 100%; height: 100%; object-fit: cover; display: block; position: absolute; inset: 0; }
.aksrt-fi__video { width: 100%; height: 100%; object-fit: cover; position: absolute; inset: 0; }
/* batch34-36 residual */
.widget-hot-posts--graphic .hot-posts-graphic { margin-bottom: 12px; border-radius: 8px; overflow: hidden; }
.widget-hot-posts--graphic .hot-posts-graphic .graphic { position: relative; overflow: hidden; border-radius: 8px; background: var(--surface-2, #f3f4f6); }
@@ -2448,19 +2334,49 @@ a.main-color > .icon-cover-card:hover {
.posts-mini-lists.aksrt-main-posts { /* zibll mini outer */ }
/* batch37-39 residual */
.aksrt-post-pay-box { position: relative; overflow: hidden; }
.aksrt-post-pay-box .pay-tag.abs-center {
position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
z-index: 2; padding: 2px 10px; border-radius: 999px; background: rgba(0,0,0,.35); color: #fff; font-size: 12px;
/* Compact paid-content purchase card for the article sidebar. */
.aksrt-post-pay-card {
--aksrt-pay-accent: var(--color-primary, #3b82f6);
display: grid;
gap: 12px;
padding: 16px;
border: 1px solid color-mix(in srgb, var(--aksrt-pay-accent) 26%, var(--color-line, #e5e7eb));
border-radius: 12px;
background: linear-gradient(145deg, color-mix(in srgb, var(--aksrt-pay-accent) 8%, var(--surface-1, #fff)), var(--surface-1, #fff));
}
.aksrt-post-pay-box__hero { position: relative; overflow: hidden; min-height: 120px; color: #fff; }
.aksrt-post-pay-box__blob { opacity: .55; pointer-events: none; }
.aksrt-post-pay-box__blob--a { height: 200px; left: 75%; width: 200px; top: -34%; border-radius: 100%; }
.aksrt-post-pay-box__blob--b { height: 305px; width: 337px; left: -229px; border-radius: 100%; opacity: .7; }
.aksrt-post-pay-box .box-body { position: relative; z-index: 1; }
.aksrt-post-pay-box .img-badge.hot { position: absolute; top: 8px; right: 8px; z-index: 2; }
.aksrt-post-pay-box .but.btn-block { width: 100%; justify-content: center; }
.aksrt-post-pay-card.jb-red { --aksrt-pay-accent: #ef4444; }
.aksrt-post-pay-card.jb-yellow { --aksrt-pay-accent: #f59e0b; }
.aksrt-post-pay-card.jb-blue { --aksrt-pay-accent: #3b82f6; }
.aksrt-post-pay-card.jb-green { --aksrt-pay-accent: #22c55e; }
.aksrt-post-pay-card.jb-purple { --aksrt-pay-accent: #8b5cf6; }
.aksrt-post-pay-card.jb-vip1 { --aksrt-pay-accent: #d49a25; }
.aksrt-post-pay-card.jb-vip2 { --aksrt-pay-accent: #64748b; }
.aksrt-post-pay-card__header,
.aksrt-post-pay-card__price-row,
.aksrt-post-pay-card__actions { display: flex; align-items: center; gap: 8px; }
.aksrt-post-pay-card__header { justify-content: space-between; }
.aksrt-post-pay-card__type,
.aksrt-post-pay-card__sales {
font-size: 12px;
line-height: 1;
}
.aksrt-post-pay-card__type {
padding: 5px 8px;
border-radius: 999px;
color: var(--aksrt-pay-accent);
background: color-mix(in srgb, var(--aksrt-pay-accent) 13%, transparent);
font-weight: 700;
}
.aksrt-post-pay-card__sales { color: var(--muted-2-color, #888); }
.aksrt-post-pay-card__price-row { min-height: 32px; }
.aksrt-post-pay-card__price,
.aksrt-post-pay-card__status { color: var(--aksrt-pay-accent); font-size: 25px; line-height: 1.2; }
.aksrt-post-pay-card__status { font-size: 19px; }
.aksrt-post-pay-card__status i { margin-right: 6px; }
.aksrt-post-pay-card__price-row del { color: var(--muted-2-color, #888); font-size: 13px; }
.aksrt-post-pay-card__hint { margin: -4px 0 0; color: var(--muted-2-color, #888); font-size: 12px; }
.aksrt-post-pay-card__actions { align-items: stretch; flex-direction: column; }
.aksrt-post-pay-card__actions .but { justify-content: center; width: 100%; }
.aksrt-main-posts__skeleton .placeholder-row,
.aksrt-product-tab__skeleton .placeholder-row {
display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px;
@@ -2498,10 +2414,6 @@ a.main-color > .icon-cover-card:hover {
flex: 1 1 0; min-width: 0; text-align: center;
}
.aksrt-plate-info .count-mates .em14 { font-weight: 700; line-height: 1.2; }
.aksrt-fi[data-aksrt-feature-intro] .aksrt-fi__panel { display: none; }
.aksrt-fi[data-aksrt-feature-intro] .aksrt-fi__panel.is-active { display: block; }
/* batch43-45 residual */
.aksrt-product-widget__item--mini {
gap: 10px; padding: 8px 0;
@@ -2513,14 +2425,6 @@ a.main-color > .icon-cover-card:hover {
background: var(--surface-2, #f3f4f6); display: block;
}
.aksrt-product-widget__thumb.mini img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aksrt-paid-posts.style-card { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
.aksrt-paid-posts.style-list .aksrt-paid-posts__item { margin-bottom: 10px; }
.aksrt-paid-posts__price.badge {
display: inline-block; margin-top: 4px; padding: 1px 6px; border-radius: 4px;
background: rgba(240,68,148,.1); color: var(--theme-color, #f04494); font-size: 12px;
}
/* batch46-48 residual */
.widget .is-affix[data-affix="true"],
.ajaxpager.is-affix[data-affix="true"],
-38
View File
@@ -95,44 +95,6 @@
setInterval(update, 1000);
}
/* ============================================================
* 3. 合作伙伴无缝滚动 (marquee)
* 用法:<div class="aksrt-marquee" data-speed="1">...内容...</div>
* ============================================================ */
function initMarquee() {
document.querySelectorAll('.aksrt-marquee').forEach(function (el) {
if (el.dataset.marqueeBound) return;
el.dataset.marqueeBound = '1';
var speed = parseFloat(el.dataset.speed || '1');
var inner = el.querySelector('.aksrt-marquee__inner') || el.children[0];
if (!inner) return;
// 克隆内容实现无缝
var clone = inner.cloneNode(true);
el.appendChild(clone);
var pos = 0;
var width = inner.scrollWidth;
function animate() {
pos -= speed;
if (Math.abs(pos) >= width) pos = 0;
inner.style.transform = 'translateX(' + pos + 'px)';
clone.style.transform = 'translateX(' + (pos + width) + 'px)';
if (!reducedMotion) requestAnimationFrame(animate);
}
if (!reducedMotion) {
requestAnimationFrame(animate);
}
// 暂停/恢复
el.addEventListener('mouseenter', function () { el.dataset.paused = '1'; });
el.addEventListener('mouseleave', function () { delete el.dataset.paused; });
});
}
/* ============================================================
* 4. 3D 悬停倾斜 (vanilla tilt)
* 用法:<div data-aksrt-tilt data-tilt-max="15">...</div>
-83
View File
@@ -1,83 +0,0 @@
/**
* AKSRT Frontend Translate — 前端即时翻译
*
* 渐进增强,默认关。通过 Settings 启用后,
* 在语言切换 Widget 中添加"翻译本页"按钮,
* 使用浏览器原生翻译或自定义翻译 API。
*
* @package AKSRT
*/
(function () {
'use strict';
var config = (window.aksrtData && window.aksrtData.translate) || {};
if (!config.enabled) return;
/**
* 触发浏览器原生翻译(Chrome/Edge 内置)
*/
function triggerBrowserTranslate(targetLang) {
// Chrome/Edge 有内置翻译功能
if (document.querySelector('.goog-te-banner-frame') || document.querySelector('#goog-gt-tt')) {
return; // 已经在翻译中
}
// 尝试使用 Google Translate Element
if (window.google && window.google.translate) {
new google.translate.TranslateElement({
pageLanguage: config.sourceLang || 'zh-CN',
includedLanguages: targetLang || '',
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
autoDisplay: false,
}, 'aksrt-translate-container');
return;
}
// 动态加载 Google Translate
var script = document.createElement('script');
script.src = '//translate.google.com/translate_a/element.js?cb=aksrtTranslateInit';
document.head.appendChild(script);
window.aksrtTranslateInit = function () {
if (!window.google || !window.google.translate) return;
new google.translate.TranslateElement({
pageLanguage: config.sourceLang || 'zh-CN',
includedLanguages: targetLang || config.targetLang || 'en',
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
autoDisplay: false,
}, 'aksrt-translate-container');
};
}
/**
* 创建翻译容器和按钮
*/
function init() {
// 检查是否有语言切换 Widget
var langWidget = document.querySelector('.aksrt-lang-switch, [data-aksrt-lang-switch]');
if (!langWidget && !config.showButton) return;
// 创建隐藏的翻译容器
var container = document.createElement('div');
container.id = 'aksrt-translate-container';
container.style.cssText = 'position:fixed;top:-9999px;left:-9999px;visibility:hidden;';
document.body.appendChild(container);
// 如果配置了显示按钮,在语言切换 Widget 旁添加
if (config.showButton && langWidget) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'aksrt-translate-btn';
btn.innerHTML = '<i class="fas fa-language"></i> 翻译本页';
btn.addEventListener('click', function () {
triggerBrowserTranslate(config.targetLang || 'en');
btn.textContent = '翻译中...';
btn.disabled = true;
});
langWidget.appendChild(btn);
}
}
document.addEventListener('DOMContentLoaded', init);
})();
+2 -2
View File
@@ -26,9 +26,9 @@
document.querySelector('[data-nav="posts"]') ||
document.querySelector('[data-nav]');
// Support multiple TOC shells (built-in single sidebar + WidgetPostsNavs).
// The article sidebar and mobile drawer share one built-in TOC source.
var tocWidgets = Array.prototype.slice.call(
document.querySelectorAll('.posts-nav-box.toc-widget, .posts-nav-box[data-title]')
document.querySelectorAll('.posts-nav-box.toc-widget')
);
var tocLists = [];
tocWidgets.forEach(function (box) {
-37
View File
@@ -280,42 +280,6 @@
});
}
// Feature intro media tabs.
function initFeatureIntro() {
document.querySelectorAll('[data-aksrt-feature-intro]').forEach(function (root) {
if (root.dataset.aksrtFiBound === '1') return;
root.dataset.aksrtFiBound = '1';
var tabs = [].slice.call(root.querySelectorAll('.aksrt-fi__tab'));
var panels = [].slice.call(root.querySelectorAll('.aksrt-fi__panel'));
if (!tabs.length || !panels.length) return;
function go(i) {
tabs.forEach(function (t, idx) {
t.classList.toggle('is-active', idx === i);
});
panels.forEach(function (p, idx) {
p.classList.toggle('is-active', idx === i);
var v = p.querySelector('video');
if (!v) return;
if (idx === i) {
if (!v.getAttribute('src') && v.getAttribute('data-src')) {
v.setAttribute('src', v.getAttribute('data-src'));
}
if (v.play) v.play().catch(function () {});
} else if (v.pause) {
v.pause();
}
});
}
tabs.forEach(function (t) {
t.addEventListener('click', function () {
go(parseInt(t.getAttribute('data-index') || '0', 10));
});
});
});
}
function initFloatingQRCodes() {
var wrap = document.querySelector('.float-right');
if (!wrap) return;
@@ -363,7 +327,6 @@
initCategoryTabs();
initProductTabs();
initMainPostsLazy();
initFeatureIntro();
initFloatingQRCodes();
}
+1 -1
View File
@@ -49,7 +49,7 @@ $archive_settings = class_exists('\AKSRT\Core\TermMeta')
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
? aksrt_archive_has_sidebar('cat_sidebar', !empty($archive_settings['show_sidebar']))
: (!empty($archive_settings['show_sidebar']) && (is_active_sidebar('cat_sidebar') || is_active_sidebar('sidebar-blog')));
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('cat_sidebar'));
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
$header_style = (string) $archive_settings['header_style'];
$layout_mode = (string) $archive_settings['layout_mode'];
+1 -1
View File
@@ -41,7 +41,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-forum.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-slider.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-vip.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-auth-ui.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-i18n-switch.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-i18n-header.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-posts-renderer.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-resource.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-pay.php';
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* Header and mobile navigation language controls.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* Header language dropdown (zibll zib_get_translate_language_btn).
* Uses SPA i18n_available_langs when i18n enabled and position includes header.
*
* @param string $class Extra classes.
*/
function aksrt_get_translate_language_btn(string $class = ''): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return '';
}
$cfg = \AKSRT\Settings\Settings::get_i18n_config();
if (empty($cfg['enabled'])) {
return '';
}
$pos = (string) ($cfg['switcherPosition'] ?? 'footer');
if (!in_array($pos, ['header', 'both'], true)) {
return '';
}
$raw = (string) ($cfg['availableLangs'] ?? '');
$buttons = [];
foreach (preg_split('/\r\n|\r|\n/', $raw) as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '|') === false) {
continue;
}
[$code, $label] = array_map('trim', explode('|', $line, 2));
if ($code === '' || $label === '') {
continue;
}
$buttons[] = ['language' => $code, 'text' => $label];
}
if (!$buttons) {
return '';
}
$buttons_html = '';
foreach ($buttons as $button) {
$buttons_html .= '<li><a href="javascript:;" class="translate-button translate-ignore flex ac" data-language="'
. esc_attr($button['language']) . '">' . esc_html($button['text']) . '</a></li>';
}
$is_mobile = wp_is_mobile();
$dropdown = '<ul class="dropdown-menu hover-show-con">' . $buttons_html . '</ul>';
$hover = !$is_mobile ? ' hover-show' : '';
$toggle = !$is_mobile ? '' : ' data-toggle="dropdown"';
return '<div class="dropdown language-dropdown ' . esc_attr($class) . $hover . '">'
. '<a href="javascript:;" class="toggle-radius"' . $toggle . ' title="' . esc_attr__('切换语言', 'aksrt') . '">'
. '<i class="fas fa-language" aria-hidden="true"></i></a>' . $dropdown . '</div>';
}
-202
View File
@@ -1,202 +0,0 @@
<?php
/**
* AKSRT i18n Switch — 多语言切换辅助函数
*
* 支持 Polylang、WPML 或 WordPress 内置语言切换。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* 获取可用语言列表
*
* @return array<array{code: string, name: string, url: string, flag: string, active: bool}>
*/
function aksrt_get_available_languages(): array {
$languages = [];
// Polylang
if (function_exists('pll_the_languages')) {
$pll_langs = pll_the_languages(['raw' => 1]);
if (is_array($pll_langs)) {
foreach ($pll_langs as $lang) {
$languages[] = [
'code' => $lang['slug'] ?? '',
'name' => $lang['name'] ?? '',
'url' => $lang['url'] ?? '#',
'flag' => $lang['flag'] ?? '',
'active' => !empty($lang['current_lang']),
];
}
}
return $languages;
}
// WPML
if (defined('ICL_LANGUAGE_CODE') && function_exists('icl_get_languages')) {
$wpml_langs = icl_get_languages('skip_missing=0');
if (is_array($wpml_langs)) {
foreach ($wpml_langs as $lang) {
$languages[] = [
'code' => $lang['language_code'] ?? '',
'name' => $lang['native_name'] ?? '',
'url' => $lang['url'] ?? '#',
'flag' => $lang['country_flag_url'] ?? '',
'active' => !empty($lang['active']),
];
}
}
return $languages;
}
// WordPress 内置:通过 filter 提供语言列表
$filtered = apply_filters('aksrt_available_languages', []);
if (is_array($filtered) && !empty($filtered)) {
return $filtered;
}
return [];
}
/**
* 检查是否有多语言插件可用
*/
function aksrt_has_multilingual(): bool {
if (function_exists('pll_the_languages')) return true;
if (defined('ICL_LANGUAGE_CODE')) return true;
if (!empty(apply_filters('aksrt_available_languages', []))) return true;
return false;
}
/**
* 获取当前语言代码
*/
function aksrt_get_current_language(): string {
if (function_exists('pll_current_language')) {
return pll_current_language('slug');
}
if (defined('ICL_LANGUAGE_CODE')) {
return ICL_LANGUAGE_CODE;
}
return get_locale();
}
/**
* 渲染语言切换器 HTML
*
* @param string $style dropdown | inline | flags
* @return string
*/
function aksrt_render_language_switcher(string $style = 'dropdown'): string {
$languages = aksrt_get_available_languages();
if (count($languages) < 2) return '';
$current = aksrt_get_current_language();
if ($style === 'flags') {
$html = '<div class="aksrt-lang-switch aksrt-lang-switch--flags">';
foreach ($languages as $lang) {
$active_class = $lang['active'] ? ' is-active' : '';
$html .= '<a href="' . esc_url($lang['url']) . '" class="aksrt-lang-switch__flag' . esc_attr($active_class) . '" title="' . esc_attr($lang['name']) . '">';
if ($lang['flag']) {
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="' . esc_attr($lang['code']) . '" width="20" height="14">';
} else {
$html .= '<span>' . esc_html(strtoupper($lang['code'])) . '</span>';
}
$html .= '</a>';
}
$html .= '</div>';
return $html;
}
// dropdown style
$html = '<div class="aksrt-lang-switch">';
$html .= '<button type="button" class="aksrt-lang-switch__toggle" aria-haspopup="true" aria-expanded="false">';
// 当前语言
foreach ($languages as $lang) {
if ($lang['active'] || $lang['code'] === $current) {
if ($lang['flag']) {
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="" width="18" height="12"> ';
}
$html .= '<span>' . esc_html($lang['name']) . '</span>';
break;
}
}
$html .= ' <i class="fas fa-chevron-down" style="font-size:10px;margin-left:4px;"></i>';
$html .= '</button>';
$html .= '<ul class="aksrt-lang-switch__dropdown" role="menu">';
foreach ($languages as $lang) {
$active_class = $lang['active'] ? ' is-active' : '';
$html .= '<li role="menuitem">';
$html .= '<a href="' . esc_url($lang['url']) . '" class="aksrt-lang-switch__item' . esc_attr($active_class) . '">';
if ($lang['flag']) {
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="" width="18" height="12"> ';
}
$html .= esc_html($lang['name']);
$html .= '</a>';
$html .= '</li>';
}
$html .= '</ul>';
$html .= '</div>';
return $html;
}
/**
* Header language dropdown (zibll zib_get_translate_language_btn).
* Uses SPA i18n_available_langs when i18n enabled and position includes header.
*
* @param string $class Extra classes.
*/
function aksrt_get_translate_language_btn(string $class = ''): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return '';
}
$cfg = \AKSRT\Settings\Settings::get_i18n_config();
if (empty($cfg['enabled'])) {
return '';
}
$pos = (string) ($cfg['switcherPosition'] ?? 'footer');
if (!in_array($pos, ['header', 'both'], true)) {
return '';
}
$raw = (string) ($cfg['availableLangs'] ?? '');
$buttons = [];
foreach (preg_split('/
| |
/', $raw) as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '|') === false) {
continue;
}
[$code, $label] = array_map('trim', explode('|', $line, 2));
if ($code === '' || $label === '') {
continue;
}
$buttons[] = ['language' => $code, 'text' => $label];
}
if (!$buttons) {
return '';
}
$buttons_html = '';
foreach ($buttons as $button) {
$buttons_html .= '<li><a href="javascript:;" class="translate-button translate-ignore flex ac" data-language="'
. esc_attr($button['language']) . '">' . esc_html($button['text']) . '</a></li>';
}
$is_mobile = wp_is_mobile();
$dropdown = '<ul class="dropdown-menu hover-show-con">' . $buttons_html . '</ul>';
$hover = !$is_mobile ? ' hover-show' : '';
$toggle = !$is_mobile ? '' : ' data-toggle="dropdown"';
return '<div class="dropdown language-dropdown ' . esc_attr($class) . $hover . '">'
. '<a href="javascript:;" class="toggle-radius"' . $toggle . ' title="' . esc_attr__('切换语言', 'aksrt') . '">'
. '<i class="fas fa-language" aria-hidden="true"></i></a>' . $dropdown . '</div>';
}
+10 -3
View File
@@ -100,9 +100,16 @@ if (!function_exists('aksrt_single_build_context')) :
$show_pay_box = false;
}
$show_aside = function_exists('aksrt_is_show_sidebar')
? aksrt_is_show_sidebar()
: false;
$layout = function_exists('aksrt_get_post_show_layout')
? aksrt_get_post_show_layout($post_id)
: '';
$has_article_widgets = is_active_sidebar('all_sidebar_top')
|| is_active_sidebar('sidebar-article')
|| is_active_sidebar('all_sidebar_bottom');
$show_aside = !wp_is_mobile()
&& get_post_type($post_id) === 'post'
&& $layout !== 'no_sidebar'
&& $has_article_widgets;
return [
'post_id' => $post_id,
+22 -1
View File
@@ -50,7 +50,7 @@ function aksrt_resolve_sidebar_id(): string {
return 'home_sidebar';
}
return 'sidebar-blog';
return 'home_sidebar';
}
endif;
@@ -206,6 +206,27 @@ if (!function_exists('aksrt_get_site_layout')):
* Zibll site-layout mode: 1=no sidebar, 2=right, 3=left.
*/
function aksrt_get_site_layout(): int {
if (is_single()) {
$post_id = (int) get_queried_object_id();
$layout = aksrt_get_post_show_layout($post_id);
$has_article_widgets = is_active_sidebar('all_sidebar_top')
|| is_active_sidebar('sidebar-article')
|| is_active_sidebar('all_sidebar_bottom');
if (get_post_type($post_id) !== 'post' || wp_is_mobile() || $layout === 'no_sidebar' || !$has_article_widgets) {
return 1;
}
if ($layout === 'sidebar_left') {
return 3;
}
if ($layout === 'sidebar_right') {
return 2;
}
$cfg = aksrt_get_sidebar_config_safe();
return ($cfg['position'] ?? 'right') === 'left' ? 3 : 2;
}
if (!aksrt_is_show_sidebar()) {
return 1;
}
-3
View File
@@ -153,7 +153,6 @@ if (!function_exists('aksrt_homepage_has_sidebar')) {
'all_sidebar_top',
'home_sidebar',
'all_sidebar_bottom',
'sidebar-blog',
]);
}
}
@@ -175,8 +174,6 @@ if (!function_exists('aksrt_homepage_render_sidebar')) {
if (is_active_sidebar('home_sidebar')) {
dynamic_sidebar('home_sidebar');
} elseif (is_active_sidebar('sidebar-blog')) {
dynamic_sidebar('sidebar-blog');
}
if (is_active_sidebar('all_sidebar_bottom')) {
+7 -108
View File
@@ -19,11 +19,11 @@ function aksrt_unregister_default_widgets(): void {
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\AboutWidget');
register_widget('\AKSRT\Widgets\CategoriesWidget');
register_widget('\AKSRT\Widgets\TagCloudWidget');
register_widget('\AKSRT\Widgets\HotPostsWidget');
@@ -56,33 +56,20 @@ function aksrt_register_widgets() {
// 视频播放器
register_widget('\AKSRT\Widgets\WidgetVideoPlayer');
// 语言切换器
register_widget('\AKSRT\Widgets\WidgetLangSwitch');
// 一言轮播
register_widget('\AKSRT\Widgets\WidgetYiyan');
// 文章目录树(zibll posts_navs)
register_widget('\AKSRT\Widgets\WidgetPostsNavs');
// 幻灯片
register_widget('\AKSRT\Widgets\WidgetSlider');
// zibll more widgets batch
register_widget('\AKSRT\Widgets\WidgetPartnersScroll');
// 其余通用小工具
register_widget('\AKSRT\Widgets\WidgetIframe');
register_widget('\AKSRT\Widgets\WidgetComboInfo');
register_widget('\AKSRT\Widgets\WidgetTextTitle');
register_widget('\AKSRT\Widgets\WidgetUserLists');
register_widget('\AKSRT\Widgets\WidgetPricingPlans');
register_widget('\AKSRT\Widgets\WidgetFaq');
register_widget('\AKSRT\Widgets\WidgetStats');
register_widget('\AKSRT\Widgets\WidgetReviews');
register_widget('\AKSRT\Widgets\WidgetHighlights');
register_widget('\AKSRT\Widgets\WidgetFeatureIntro');
register_widget('\AKSRT\Widgets\WidgetTermCards');
register_widget('\AKSRT\Widgets\WidgetPaidPosts');
register_widget('\AKSRT\Widgets\WidgetPostPayBox');
register_widget('\AKSRT\Widgets\WidgetAuthorCard');
register_widget('\AKSRT\Widgets\WidgetOnelinePosts');
@@ -91,8 +78,6 @@ function aksrt_register_widgets() {
register_widget('\AKSRT\Widgets\WidgetTermAggregation');
register_widget('\AKSRT\Widgets\WidgetIconCoverCards');
register_widget('\AKSRT\Widgets\WidgetBookmarkLinks');
aksrt_maybe_repair_sidebar_blog_widgets();
}
add_action('widgets_init', 'aksrt_register_widgets');
@@ -105,7 +90,6 @@ if (!function_exists('aksrt_widget_sidebar_area_ids')) {
function aksrt_widget_sidebar_area_ids(): array {
return [
'home_sidebar',
'sidebar-blog',
'sidebar-article',
'all_sidebar_top',
'all_sidebar_bottom',
@@ -192,7 +176,6 @@ if (!function_exists('aksrt_widget_allowed_sidebars')) {
*/
function aksrt_widget_allowed_sidebars(string $widget_base_id): array {
$sidebar_only = [
'aksrt_about',
'aksrt_categories',
'aksrt_tag_cloud',
'aksrt_hot_posts',
@@ -221,6 +204,10 @@ if (!function_exists('aksrt_widget_allowed_sidebars')) {
)));
}
if ($widget_base_id === 'aksrt_post_pay_box') {
return ['sidebar-article'];
}
return [];
}
}
@@ -280,7 +267,7 @@ if (!function_exists('aksrt_widget_render_location_help')) {
*/
function aksrt_widget_render_location_help(string $scope): void {
$messages = [
'sidebar' => __('仅限侧栏区域:首页侧栏、博客侧栏、文章侧栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'),
'sidebar' => __('仅限侧栏区域:首页侧栏、文章页侧边栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'),
'homepage' => __('仅限首页挂载区域:首页顶部/底部全宽、首页内容上方/下方。', 'aksrt'),
'hybrid' => __('支持侧栏、首页和归档页挂载区域。', 'aksrt'),
'archive' => __('仅限归档页挂载区域:分类页、标签页、搜索页的顶部/底部/侧栏。', 'aksrt'),
@@ -412,92 +399,6 @@ if (!function_exists('aksrt_filter_widget_sidebar_assignments')) {
}
add_filter('pre_update_option_sidebars_widgets', 'aksrt_filter_widget_sidebar_assignments', 10, 2);
/**
* Ensure widget instance config exists.
*/
function aksrt_ensure_widget_instance(string $option_name, int $number, array $defaults): void {
$instances = get_option($option_name, []);
if (!is_array($instances)) {
$instances = [];
}
if (!isset($instances['_multiwidget'])) {
$instances['_multiwidget'] = 1;
}
if (empty($instances[$number]) || !is_array($instances[$number])) {
$instances[$number] = $defaults;
update_option($option_name, $instances);
}
}
/**
* Repair the sidebar-blog structure and seed default sidebar widgets on first install.
*/
function aksrt_maybe_repair_sidebar_blog_widgets(): void {
$sidebars = get_option('sidebars_widgets', []);
$seeded = (bool) get_option('aksrt_sidebar_blog_seeded', false);
if (!is_array($sidebars)) {
$sidebars = [];
}
$current = $sidebars['sidebar-blog'] ?? null;
$needs_repair = false;
$normalized = [];
if (is_array($current)) {
foreach ($current as $item) {
if (is_array($item)) {
$needs_repair = true;
}
if (is_string($item) && $item !== '') {
$normalized[] = $item;
}
}
}
if ($needs_repair) {
$sidebars['sidebar-blog'] = array_values($normalized);
update_option('sidebars_widgets', $sidebars);
update_option('aksrt_sidebar_blog_seeded', true);
return;
}
if ($current !== null) {
if (!$seeded) {
update_option('aksrt_sidebar_blog_seeded', true);
}
return;
}
if ($seeded) {
return;
}
aksrt_ensure_widget_instance('widget_aksrt_about', 2, [
'title' => '',
]);
aksrt_ensure_widget_instance('widget_aksrt_categories', 2, [
'title' => __('分类目录', 'aksrt'),
'count' => 8,
]);
aksrt_ensure_widget_instance('widget_aksrt_tag_cloud', 2, [
'title' => __('标签云', 'aksrt'),
'count' => 15,
]);
$sidebars['sidebar-blog'] = [
'aksrt_about-2',
'aksrt_categories-2',
'aksrt_tag_cloud-2',
];
update_option('sidebars_widgets', $sidebars);
update_option('aksrt_sidebar_blog_seeded', true);
}
/**
* Register a block editor collection for AKSRT widgets.
*/
@@ -527,7 +428,6 @@ add_action('enqueue_block_editor_assets', function () {
*/
add_filter('block_type_metadata', function ($metadata) {
$aksrt_widgets = [
'aksrt_about',
'aksrt_categories',
'aksrt_tag_cloud',
];
@@ -544,7 +444,6 @@ add_filter('block_type_metadata', function ($metadata) {
*/
add_action('in_widget_form', function (WP_Widget $widget): void {
$map = [
'aksrt_about' => 'sidebar',
'aksrt_categories' => 'sidebar',
'aksrt_tag_cloud' => 'sidebar',
'aksrt_hot_posts' => 'sidebar',
+1 -1
View File
@@ -24,7 +24,7 @@ get_header();
// Visibility is driven by body.site-layout-* (aksrt_is_show_sidebar), not widget presence.
$has_sidebar = function_exists('aksrt_is_show_sidebar')
? aksrt_is_show_sidebar()
: is_active_sidebar('sidebar-blog');
: is_active_sidebar('home_sidebar');
?>
<main id="main-content" class="site-main container" tabindex="-1">
<div class="content-wrap">
+1 -1
View File
@@ -114,7 +114,7 @@ $search_sort_options = [
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
? aksrt_archive_has_sidebar('search_sidebar', !empty($other_page_settings['page_search_sidebar']))
: (!empty($other_page_settings['page_search_sidebar']) && (is_active_sidebar('search_sidebar') || is_active_sidebar('sidebar-blog')));
: (!empty($other_page_settings['page_search_sidebar']) && is_active_sidebar('search_sidebar'));
if (function_exists('aksrt_archive_open_shell')) {
aksrt_archive_open_shell([
+1 -3
View File
@@ -17,7 +17,7 @@ if (wp_is_mobile()) {
$sidebar_id = function_exists('aksrt_resolve_sidebar_id')
? aksrt_resolve_sidebar_id()
: 'sidebar-blog';
: 'home_sidebar';
?>
<div class="sidebar" role="complementary" aria-label="<?php esc_attr_e('侧栏', 'aksrt'); ?>">
@@ -29,8 +29,6 @@ $sidebar_id = function_exists('aksrt_resolve_sidebar_id')
<?php dynamic_sidebar($sidebar_id); ?>
<?php elseif ((is_home() || is_front_page()) && is_active_sidebar('home_sidebar')) : ?>
<?php dynamic_sidebar('home_sidebar'); ?>
<?php elseif ((is_home() || is_front_page()) && is_active_sidebar('sidebar-blog')) : ?>
<?php dynamic_sidebar('sidebar-blog'); ?>
<?php endif; ?>
<?php if (is_active_sidebar('all_sidebar_bottom')) : ?>
-11
View File
@@ -380,17 +380,6 @@ trait ScriptsPart1c {
}
// Frontend translate (默认关)
$translate_js = get_template_directory() . '/assets/js/frontend-translate.js';
if (file_exists($translate_js)) {
wp_enqueue_script(
'aksrt-frontend-translate',
$base_uri . '/assets/js/frontend-translate.js',
[],
(string) filemtime($translate_js),
true
);
}
// Content protection (copy notice / right-click / F12)
$content_protect_js = get_template_directory() . '/assets/js/content-protect.js';
if (file_exists($content_protect_js)) {
+26 -57
View File
@@ -58,7 +58,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'all_footer',
'name' => __('全站页脚小工具', 'aksrt'),
'name' => __('全站 - 页脚小工具', 'aksrt'),
'description' => __('显示在页脚内部的统一小工具区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -68,7 +68,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'mobile_nav_fluid',
'name' => __('移动端菜单底部', 'aksrt'),
'name' => __('移动端 - 菜单底部', 'aksrt'),
'description' => __('显示在移动端抽屉菜单底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -118,7 +118,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'home_sidebar',
'name' => __('首页侧栏', 'aksrt'),
'name' => __('首页 - 侧边栏', 'aksrt'),
'description' => __('显示在首页右侧栏。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -126,21 +126,10 @@ trait SetupPart1 {
'after_title' => $title_after,
]);
// 新前端使用的侧边栏 (sidebar.php)
register_sidebar([
'id' => 'sidebar-blog',
'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'),
'name' => __('文章页 - 侧边栏', 'aksrt'),
'description' => __('显示在文章详情页右侧。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -151,7 +140,7 @@ trait SetupPart1 {
// ===== 文章页 Widget 区域(模板zibll 多位置) =====
register_sidebar([
'id' => 'single_top_fluid',
'name' => __('文章页 · 顶部全宽', 'aksrt'),
'name' => __('文章页 - 顶部全宽', 'aksrt'),
'description' => __('文章页顶部全宽区域(在封面/面包屑之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -161,7 +150,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'single_top_content',
'name' => __('文章页 · 内容上方', 'aksrt'),
'name' => __('文章页 - 内容上方', 'aksrt'),
'description' => __('文章主体卡片上方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -171,7 +160,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'single_bottom_content',
'name' => __('文章页 · 内容下方', 'aksrt'),
'name' => __('文章页 - 内容下方', 'aksrt'),
'description' => __('评论区下方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -181,7 +170,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'single_bottom_fluid',
'name' => __('文章页 · 底部全宽', 'aksrt'),
'name' => __('文章页 - 底部全宽', 'aksrt'),
'description' => __('文章页底部全宽区域(在页脚之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -189,30 +178,10 @@ trait SetupPart1 {
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'sidebar-footer-1',
'name' => __('页脚栏 1', 'aksrt'),
'description' => '',
'before_widget' => '<div class="footer-widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="footer-widget-title">',
'after_title' => '</h4>',
]);
register_sidebar([
'id' => 'sidebar-footer-2',
'name' => __('页脚栏 2', 'aksrt'),
'description' => '',
'before_widget' => '<div class="footer-widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="footer-widget-title">',
'after_title' => '</h4>',
]);
// ===== 分类页 Widget 区域 =====
register_sidebar([
'id' => 'cat_top_fluid',
'name' => __('分类页 · 顶部全宽', 'aksrt'),
'name' => __('分类页 - 顶部全宽', 'aksrt'),
'description' => __('分类归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -222,7 +191,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'cat_top_content',
'name' => __('分类页 · 内容上方', 'aksrt'),
'name' => __('分类页 - 内容上方', 'aksrt'),
'description' => __('分类归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -232,7 +201,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'cat_bottom_content',
'name' => __('分类页 · 内容下方', 'aksrt'),
'name' => __('分类页 - 内容下方', 'aksrt'),
'description' => __('分类归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -242,7 +211,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'cat_bottom_fluid',
'name' => __('分类页 · 底部全宽', 'aksrt'),
'name' => __('分类页 - 底部全宽', 'aksrt'),
'description' => __('分类归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -252,7 +221,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'cat_sidebar',
'name' => __('分类页 · 侧栏', 'aksrt'),
'name' => __('分类页 - 侧栏', 'aksrt'),
'description' => __('分类归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -263,7 +232,7 @@ trait SetupPart1 {
// ===== 标签页 Widget 区域 =====
register_sidebar([
'id' => 'tag_top_fluid',
'name' => __('标签页 · 顶部全宽', 'aksrt'),
'name' => __('标签页 - 顶部全宽', 'aksrt'),
'description' => __('标签归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -273,7 +242,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'tag_top_content',
'name' => __('标签页 · 内容上方', 'aksrt'),
'name' => __('标签页 - 内容上方', 'aksrt'),
'description' => __('标签归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -283,7 +252,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'tag_bottom_content',
'name' => __('标签页 · 内容下方', 'aksrt'),
'name' => __('标签页 - 内容下方', 'aksrt'),
'description' => __('标签归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -293,7 +262,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'tag_bottom_fluid',
'name' => __('标签页 · 底部全宽', 'aksrt'),
'name' => __('标签页 - 底部全宽', 'aksrt'),
'description' => __('标签归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -303,7 +272,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'tag_sidebar',
'name' => __('标签页 · 侧栏', 'aksrt'),
'name' => __('标签页 - 侧栏', 'aksrt'),
'description' => __('标签归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -314,7 +283,7 @@ trait SetupPart1 {
// ===== 搜索页 Widget 区域 =====
register_sidebar([
'id' => 'search_top_fluid',
'name' => __('搜索页 · 顶部全宽', 'aksrt'),
'name' => __('搜索页 - 顶部全宽', 'aksrt'),
'description' => __('搜索结果页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -324,7 +293,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'search_top_content',
'name' => __('搜索页 · 内容上方', 'aksrt'),
'name' => __('搜索页 - 内容上方', 'aksrt'),
'description' => __('搜索结果页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -334,7 +303,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'search_bottom_content',
'name' => __('搜索页 · 内容下方', 'aksrt'),
'name' => __('搜索页 - 内容下方', 'aksrt'),
'description' => __('搜索结果页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -344,7 +313,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'search_bottom_fluid',
'name' => __('搜索页 · 底部全宽', 'aksrt'),
'name' => __('搜索页 - 底部全宽', 'aksrt'),
'description' => __('搜索结果页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -354,7 +323,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'search_sidebar',
'name' => __('搜索页 · 侧栏', 'aksrt'),
'name' => __('搜索页 - 侧栏', 'aksrt'),
'description' => __('搜索结果页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -364,7 +333,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'archive_bottom_fluid',
'name' => __('归档页 · 底部全宽', 'aksrt'),
'name' => __('归档页 - 底部全宽', 'aksrt'),
'description' => __('分类/标签/归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
@@ -374,7 +343,7 @@ trait SetupPart1 {
register_sidebar([
'id' => 'author-sidebar',
'name' => __('作者页 · 侧栏', 'aksrt'),
'name' => __('作者页 - 侧栏', 'aksrt'),
'description' => __('作者归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
@@ -441,7 +410,7 @@ trait SetupPart1 {
}
$id = 'page_' . $key . '_' . (int) $page->ID;
register_sidebar([
'name' => sprintf(__('[页面:%1$s]-%2$s', 'aksrt'), $name, $area_labels[$key]),
'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,
-21
View File
@@ -27,7 +27,6 @@ class WidgetAjax {
$routes = [
'/widgets/posts' => 'rest_main_posts',
'/widgets/tab-posts' => 'rest_tab_posts',
'/widgets/paid-posts' => 'rest_paid_posts',
'/widgets/category-tabs' => 'rest_category_tabs',
'/widgets/category-block' => 'rest_category_block',
'/widgets/category-recommend' => 'rest_category_recommend',
@@ -123,26 +122,6 @@ class WidgetAjax {
], 200);
}
public function rest_paid_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetPaidPosts')) {
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') ?? 6))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'mini')),
'paginate' => $paginate,
];
$page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1));
return $this->payload(\AKSRT\Widgets\WidgetPaidPosts::query_payload($instance, $page), $page);
}
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)));
-383
View File
@@ -1,383 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class AboutWidget extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_about',
esc_html__('AKSRT 用户中心', 'aksrt'),
[
'description' => esc_html__('登录状态面板:未登录显示登录/注册按钮,已登录显示用户信息与快捷入口', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* 前端渲染
*/
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'];
}
// 判断登录状态
if (is_user_logged_in()) {
$this->render_logged_in();
} else {
$this->render_logged_out($instance);
}
// 输出内联样式 & JS(仅在本 widget 实例加载时注入一次)
$this->inline_assets();
echo $args['after_widget'];
}
// ==========================================
// 已登录状态渲染(Zibll 风格用户卡片)
// ==========================================
private function render_logged_in(): void {
$u = wp_get_current_user();
$avatar = get_avatar_url($u->ID, ['size' => 160]);
$profile = aksrt_get_user_center_url();
$logout = aksrt_get_logout_url(home_url('/'));
// 封面图:用户在用户中心上传的背景图 > 默认背景图
$cover = get_user_meta($u->ID, 'aksrt_background_image', true);
if (! $cover) {
$cover = get_theme_file_uri('assets/img/person_background.png');
}
?>
<div class="aksrt-user-card">
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
<img class="aksrt-user-card__cover-img aksrt-lazy lazyload" data-src="<?php echo esc_url($cover); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" loading="lazy">
</div>
<div class="aksrt-user-card__content">
<div class="aksrt-user-card__avatar">
<img class="aksrt-lazy lazyload" data-src="<?php echo esc_url($avatar); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" width="80" height="80" loading="lazy">
</div>
<div class="aksrt-user-card__info">
<div class="aksrt-user-card__name"><?php if (function_exists('aksrt_get_user_name')) { echo aksrt_get_user_name(['id' => (int) $u->ID, 'class' => 'flex ac', 'follow' => false, 'medal' => true, 'auth' => true, 'level' => true]); } else { echo esc_html($u->display_name); } ?></div>
<div class="aksrt-user-card__desc"><?php $bio = trim((string) get_user_meta($u->ID, 'description', true)); echo esc_html($bio !== '' ? $bio : $u->user_email); ?></div>
</div>
<div class="aksrt-user-card__btns">
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="<?php echo esc_url($profile); ?>">
<i class="fas fa-user" aria-hidden="true"></i> <?php esc_html_e('个人主页', 'aksrt'); ?>
</a>
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline" href="<?php echo esc_url($logout); ?>">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i> <?php esc_html_e('退出', 'aksrt'); ?>
</a>
</div>
</div>
</div>
<?php
}
// ==========================================
// 未登录状态渲染(Zibll 风格用户卡片)
// ==========================================
private function render_logged_out(array $instance = []): void {
$login_url = aksrt_get_login_url();
$register_url = aksrt_get_register_url();
// 封面图:后台配置 > 默认
$cover = ! empty($instance['cover']) ? $instance['cover'] : get_theme_file_uri('assets/img/widgets-original.png');
$avatar = get_avatar_url(0, ['size' => 160, 'default' => 'mystery']);
$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();
}
}
?>
<div class="aksrt-user-card">
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
<?php if ($cover) : ?>
<img class="aksrt-user-card__cover-img aksrt-lazy lazyload" data-src="<?php echo esc_url($cover); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" loading="lazy">
<?php endif; ?>
</div>
<div class="aksrt-user-card__content">
<div class="aksrt-user-card__avatar">
<img class="aksrt-lazy lazyload" data-src="<?php echo esc_url($avatar); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" width="80" height="80" loading="lazy">
</div>
<div class="aksrt-user-card__info">
<div class="aksrt-user-card__desc"><?php esc_html_e('Hi! 请登录', 'aksrt'); ?></div>
</div>
<div class="aksrt-user-card__btns">
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary signin-loader" href="<?php echo esc_url($login_url); ?>"><?php esc_html_e('登 录', 'aksrt'); ?></a>
<?php if (get_option('users_can_register')) : ?>
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline signup-loader" href="<?php echo esc_url($register_url); ?>"><?php esc_html_e('注 册', 'aksrt'); ?></a>
<?php endif; ?>
</div>
<?php if ($oauth_html) : ?>
<div class="aksrt-user-card__oauth">
<div class="aksrt-user-card__divider"><?php esc_html_e('社交账号登录', 'aksrt'); ?></div>
<div class="aksrt-user-card__social-icons">
<?php echo $oauth_html; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
// ==========================================
// 内联样式(Zibll 风格用户卡片,只输出一次)
// ==========================================
private function inline_assets(): void {
if (did_action('aksrt_user_card_assets')) {
return;
}
do_action('aksrt_user_card_assets');
?>
<style>
/* ========== AKSRT 用户卡片(Zibll 风格) ========== */
/*
* 消除"卡片套卡片":
* 外层 .widget-card 提供 background/shadow/border-radius/padding:15px
* 内层 .aksrt-user-card 用负 margin 把封面图扩展到外层边缘,
* 使封面图与外层卡片融为一体,不再有 15px 间距。
*/
.widget-card > .aksrt-user-card {
margin: -15px -15px 0;
overflow: hidden;
border-radius: var(--radius-card, 8px) var(--radius-card, 8px) 0 0;
}
/* 有标题时:取消标题下方的 margin-bottom,因为卡片已无额外间距 */
.widget.aksrt_about.widget-card > .widget-title + .aksrt-user-card {
margin-top: -5px;
}
/* ---- 封面图 ---- */
.aksrt-user-card__cover {
position: relative;
overflow: hidden;
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark, #4f46e5));
}
.aksrt-user-card__cover-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* ---- 内容区 — 悬浮在封面上方 ---- */
.aksrt-user-card__content {
padding: 15px;
margin-top: -55px;
position: relative;
text-align: center;
}
/* ---- 头像(绝对居中) ---- */
.aksrt-user-card__avatar {
text-align: center;
line-height: 0;
}
.aksrt-user-card__avatar img {
display: block;
width: 80px;
height: 80px;
margin: 0 auto;
border-radius: 50%;
border: 3px solid var(--color-primary);
object-fit: cover;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* ---- 用户信息 ---- */
.aksrt-user-card__info {
margin-top: 12px;
text-align: center;
}
.aksrt-user-card__name {
font-size: 1.1em;
font-weight: bold;
color: var(--color-ink, var(--color-text));
}
.aksrt-user-card__desc {
font-size: 13px;
color: var(--color-text-muted, #9ca3af);
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ---- 按钮组 ---- */
.aksrt-user-card__btns {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 16px;
}
.aksrt-user-card__btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 20px;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: opacity 0.2s ease, transform 0.15s ease;
flex: 1;
max-width: 140px;
}
.aksrt-user-card__btn:hover {
opacity: 0.88;
transform: translateY(-1px);
}
.aksrt-user-card__btn:active {
transform: translateY(0);
}
.aksrt-user-card__btn--primary {
background: var(--color-primary);
color: #fff;
}
.aksrt-user-card__btn--outline {
background: var(--color-primary-softer, rgba(136,171,218,0.05));
color: var(--color-primary);
border: 1px solid var(--color-primary-50, rgba(136,171,218,0.12));
}
/* ---- OAuth 社交登录 ---- */
.aksrt-user-card__oauth {
margin-top: 12px;
}
.aksrt-user-card__divider {
font-size: 12px;
color: var(--color-text-muted, #9ca3af);
margin-bottom: 10px;
position: relative;
}
.aksrt-user-card__divider::before,
.aksrt-user-card__divider::after {
content: '';
position: absolute;
top: 50%;
width: 28%;
height: 1px;
background: var(--color-border, #e5e7eb);
}
.aksrt-user-card__divider::before { left: 0; }
.aksrt-user-card__divider::after { right: 0; }
.aksrt-user-card__social-icons {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.aksrt-user-card__social-icons a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 50%;
font-size: 15px;
color: var(--color-text-muted, #6b7280);
background: var(--color-bg-tertiary, #f3f4f6);
text-decoration: none;
transition: background 0.18s ease, color 0.18s ease;
}
.aksrt-user-card__social-icons a:hover {
background: var(--color-primary);
color: #fff;
}
/* ---- 暗色适配 ---- */
html.dark .aksrt-user-card__avatar img,
body.theme-dark .aksrt-user-card__avatar img {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
html.dark .aksrt-user-card__social-icons a,
body.theme-dark .aksrt-user-card__social-icons a {
background: rgba(255, 255, 255, 0.08);
}
html.dark .aksrt-user-card__social-icons a:hover,
body.theme-dark .aksrt-user-card__social-icons a:hover {
background: var(--color-primary);
}
/* ---- 移动端 ---- */
@media screen and (max-width: 480px) {
.aksrt-user-card__content {
margin-top: -45px;
}
.aksrt-user-card__avatar img {
width: 68px;
height: 68px;
}
}
</style>
<?php
}
// ==========================================
// 后台表单
// ==========================================
public function form($instance): void {
$title = $instance['title'] ?? '';
$cover = $instance['cover'] ?? '';
$default_cover = get_theme_file_uri('assets/img/widgets-original.png');
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('cover'); ?>"><?php esc_html_e('未登录背景图 URL:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('cover'); ?>"
name="<?php echo $this->get_field_name('cover'); ?>"
type="url" value="<?php echo esc_attr($cover); ?>"
placeholder="<?php echo esc_attr($default_cover); ?>">
</p>
<p style="font-size:12px;color:#666;">
<?php esc_html_e('留空则使用默认背景图。已登录用户可使用个人封面图。', 'aksrt'); ?>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'cover' => esc_url_raw($new_instance['cover'] ?? ''),
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ class WidgetAnnouncement extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_announcement',
__('公告跑马灯', 'aksrt'),
__('AKSRT 公告跑马灯', 'aksrt'),
[
'description' => __('滚动显示公告/通知(共享垂直 Swiper)。', 'aksrt'),
'classname' => 'widget_aksrt_announcement',
+1 -1
View File
@@ -18,7 +18,7 @@ class WidgetCoverCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_cover_cards',
__('图文封面卡片', 'aksrt'),
__('AKSRT 图文封面卡片', 'aksrt'),
['description' => __('图片/视频封面 + 文字叠加的图文卡片(共享 graphic_card)。', 'aksrt')]
);
}
-230
View File
@@ -1,230 +0,0 @@
<?php
/**
* Feature intro with media tabs (zibll zib_widget_feature_intro lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetFeatureIntro extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_feature_intro',
esc_html__('AKSRT 功能介绍', 'aksrt'),
[
'description' => esc_html__('左文右图/视频的 TAB 功能介绍模块。', 'aksrt'),
'classname' => 'widget_aksrt_feature_intro',
]
);
}
public function widget($args, $instance): void {
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
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;
}
$heading = trim((string) ($instance['heading'] ?? ''));
$desc = trim((string) ($instance['description'] ?? ''));
$media_pos = (($instance['media_position'] ?? 'right') === 'left') ? 'left' : 'right';
$chrome = !empty($instance['browser_chrome']) || !isset($instance['browser_chrome']);
$ratio = max(30, min(100, (int) ($instance['media_ratio'] ?? 66)));
$show_stat = !empty($instance['show_stat']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$uid = 'aksrt-fi-' . esc_attr($this->id);
echo '<div class="aksrt-fi media-' . esc_attr($media_pos) . '" id="' . $uid . '" data-aksrt-feature-intro="1">';
echo '<div class="aksrt-fi__text">';
if ($heading !== '') {
echo '<h2 class="aksrt-fi__title">' . esc_html($heading) . '</h2>';
}
if ($desc !== '') {
echo '<div class="aksrt-fi__desc">' . wp_kses_post(wpautop($desc)) . '</div>';
}
if ($show_stat) {
$sn = sanitize_text_field((string) ($instance['stat_number'] ?? ''));
$ss = sanitize_text_field((string) ($instance['stat_suffix'] ?? '+'));
$sl = sanitize_text_field((string) ($instance['stat_label'] ?? ''));
if ($sn !== '') {
$num_attr = is_numeric($sn) ? ' data-aksrt-countup data-target="' . esc_attr((string) (float) $sn) . '" data-duration="1600"' : '';
echo '<div class="aksrt-fi__stat"><span class="aksrt-fi__stat-num"' . $num_attr . '>' . esc_html(is_numeric($sn) ? '0' : $sn) . '</span>';
if ($ss !== '') {
echo '<span class="aksrt-fi__stat-plus">' . esc_html($ss) . '</span>';
}
if ($sl !== '') {
echo '<span class="aksrt-fi__stat-label">' . esc_html($sl) . '</span>';
}
echo '</div>';
}
}
if (count($slides) > 1) {
echo '<div class="aksrt-fi__tabs" role="tablist">';
foreach ($slides as $i => $s) {
$cls = $i === 0 ? ' is-active' : '';
$label = $s['tab_label'] !== '' ? $s['tab_label'] : sprintf(__('面板 %d', 'aksrt'), $i + 1);
echo '<button type="button" class="aksrt-fi__tab' . esc_attr($cls) . '" data-index="' . (int) $i . '" role="tab">' . esc_html($label) . '</button>';
}
echo '</div>';
}
echo '</div>';
echo '<div class="aksrt-fi__media">';
foreach ($slides as $i => $s) {
$cls = $i === 0 ? ' is-active' : '';
echo '<div class="aksrt-fi__panel' . esc_attr($cls) . '" data-index="' . (int) $i . '">';
$box_cls = $chrome ? 'aksrt-fi__browser' : 'aksrt-fi__browser is-plain';
echo '<div class="' . esc_attr($box_cls) . '">';
if ($chrome) {
echo '<div class="aksrt-fi__browser-bar" aria-hidden="true"><span></span><span></span><span></span></div>';
}
echo '<div class="aksrt-fi__browser-body" style="padding-bottom:' . esc_attr((string) $ratio) . '%">';
if ($s['media_type'] === 'video' && $s['video_url'] !== '') {
$poster = $s['video_poster'] !== '' ? ' poster="' . esc_url($s['video_poster']) . '"' : '';
$auto = $i === 0 ? ' autoplay' : '';
if ($i === 0) {
echo '<video class="aksrt-fi__video" src="' . esc_url($s['video_url']) . '"' . $poster . $auto . ' muted loop playsinline></video>';
} else {
echo '<video class="aksrt-fi__video" data-src="' . esc_url($s['video_url']) . '"' . $poster . ' muted loop playsinline></video>';
}
} elseif ($s['image'] !== '') {
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<img class="aksrt-fi__img aksrt-lazy lazyload" data-src="' . esc_url($s['image']) . '" src="' . esc_url($ph) . '" alt="' . esc_attr($s['tab_label']) . '" loading="lazy" />';
}
echo '</div></div></div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,string>>
*/
private function normalize_slides($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$type = sanitize_key((string) ($row['media_type'] ?? 'image'));
if ($type !== 'video') {
$type = 'image';
}
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
$video = esc_url_raw(trim((string) ($row['video_url'] ?? '')));
if ($type === 'video' && $video === '') {
continue;
}
if ($type === 'image' && $image === '') {
continue;
}
$out[] = [
'tab_label' => sanitize_text_field((string) ($row['tab_label'] ?? '')),
'media_type' => $type,
'image' => $image,
'video_url' => $video,
'video_poster' => esc_url_raw(trim((string) ($row['video_poster'] ?? ''))),
];
if (count($out) >= 8) {
break;
}
}
return $out;
}
public function form($instance): void {
$json = is_string($instance['slides'] ?? null)
? (string) $instance['slides']
: wp_json_encode($this->normalize_slides($instance['slides'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('主标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('heading')); ?>" value="<?php echo esc_attr((string) ($instance['heading'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('描述', 'aksrt'); ?>
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('description')); ?>"><?php echo esc_textarea((string) ($instance['description'] ?? '')); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('媒体 TAB JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('slides')); ?>" placeholder='[{"tab_label":"面板1","media_type":"image","image":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('媒体位置', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('media_position')); ?>">
<option value="right" <?php selected(($instance['media_position'] ?? 'right'), 'right'); ?>><?php esc_html_e('右侧', 'aksrt'); ?></option>
<option value="left" <?php selected(($instance['media_position'] ?? ''), 'left'); ?>><?php esc_html_e('左侧', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('媒体高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="100" name="<?php echo esc_attr($this->get_field_name('media_ratio')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['media_ratio'] ?? 66))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('browser_chrome')); ?>" value="1" <?php checked(!empty($instance['browser_chrome']) || !isset($instance['browser_chrome'])); ?>> <?php esc_html_e('浏览器外框', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stat')); ?>" value="1" <?php checked(!empty($instance['show_stat'])); ?>> <?php esc_html_e('显示统计数字', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('统计数字', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_number')); ?>" value="<?php echo esc_attr((string) ($instance['stat_number'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('统计后缀', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_suffix')); ?>" value="<?php echo esc_attr((string) ($instance['stat_suffix'] ?? '+')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('统计标签', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_label')); ?>" value="<?php echo esc_attr((string) ($instance['stat_label'] ?? '')); ?>">
</label>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'heading' => sanitize_text_field((string) ($new_instance['heading'] ?? '')),
'description' => wp_kses_post((string) ($new_instance['description'] ?? '')),
'slides' => $this->normalize_slides($new_instance['slides'] ?? []),
'media_position' => ($new_instance['media_position'] ?? '') === 'left' ? 'left' : 'right',
'media_ratio' => max(30, min(100, (int) ($new_instance['media_ratio'] ?? 66))),
'browser_chrome' => !empty($new_instance['browser_chrome']) ? 1 : 0,
'show_stat' => !empty($new_instance['show_stat']) ? 1 : 0,
'stat_number' => sanitize_text_field((string) ($new_instance['stat_number'] ?? '')),
'stat_suffix' => sanitize_text_field((string) ($new_instance['stat_suffix'] ?? '')),
'stat_label' => sanitize_text_field((string) ($new_instance['stat_label'] ?? '')),
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
-128
View File
@@ -1,128 +0,0 @@
<?php
/**
* Language switcher widget (Polylang / WPML / filter).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetLangSwitch extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_lang_switch',
__('语言切换器', 'aksrt'),
[
'description' => __('在侧栏显示多语言切换按钮。', 'aksrt'),
'classname' => 'widget_aksrt_lang_switch',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$style = sanitize_key((string) ($instance['style'] ?? 'dropdown'));
if (!in_array($style, ['dropdown', 'flags', 'inline'], true)) {
$style = 'dropdown';
}
$show_name = !isset($instance['show_name']) || !empty($instance['show_name']);
$hide_current = !empty($instance['hide_current']);
if (!function_exists('aksrt_has_multilingual') || !aksrt_has_multilingual()) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="c-red muted-box mb10"><b>' . esc_html__('语言切换:', 'aksrt') . '</b>' .
esc_html__('未检测到多语言插件(Polylang/WPML)或可用语言不足。', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
// inline style: treat as flags row without dropdown chrome.
$render_style = $style === 'inline' ? 'flags' : $style;
$html = function_exists('aksrt_render_language_switcher') ? aksrt_render_language_switcher($render_style) : '';
if ($html === '') {
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;
}
// Optional modifiers via wrapper classes.
$wrap = 'aksrt-lang-switch-widget style-' . esc_attr($style);
if (!$show_name) {
$wrap .= ' hide-name';
}
if ($hide_current) {
$wrap .= ' hide-current';
}
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
}
echo '<div class="' . esc_attr($wrap) . '">' . $html . '</div>'; // 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 {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$style = !empty($instance['style']) ? (string) $instance['style'] : 'dropdown';
?>
<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('style')); ?>"><?php esc_html_e('样式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('style')); ?>" name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="dropdown" <?php selected($style, 'dropdown'); ?>><?php esc_html_e('下拉菜单', 'aksrt'); ?></option>
<option value="flags" <?php selected($style, 'flags'); ?>><?php esc_html_e('国旗图标', 'aksrt'); ?></option>
<option value="inline" <?php selected($style, 'inline'); ?>><?php esc_html_e('横向排列', 'aksrt'); ?></option>
</select>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_name')); ?>" value="1" <?php checked(!isset($instance['show_name']) || !empty($instance['show_name'])); ?>> <?php esc_html_e('显示语言名', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_current')); ?>" value="1" <?php checked(!empty($instance['hide_current'])); ?>> <?php esc_html_e('隐藏当前语言', 'aksrt'); ?></label>
</p>
<p><small><?php esc_html_e('需要安装 Polylang 或 WPML 等多语言插件才能生效。', 'aksrt'); ?></small></p>
<?php
$this->render_common_form_section($instance);
}
/**
* @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'] ?? 'dropdown'));
if (!in_array($style, ['dropdown', 'flags', 'inline'], true)) {
$style = 'dropdown';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'style' => $style,
'show_name' => !empty($new_instance['show_name']) ? 1 : 0,
'hide_current' => !empty($new_instance['hide_current']) ? 1 : 0,
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
-273
View File
@@ -1,273 +0,0 @@
<?php
/**
* Paid posts list (zibll zib_widget_ui_posts_pay + ajax).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetPaidPosts extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_paid_posts',
esc_html__('AKSRT 付费内容列表', 'aksrt'),
[
'description' => esc_html__('展示开启付费/积分可见的文章(支持 AJAX)。', 'aksrt'),
'classname' => 'widget_aksrt_paid_posts',
]
);
}
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'order' => 'DESC',
'meta_query' => [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_credits_cost',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
],
];
if ($orderby === 'views') {
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
} elseif ($orderby === 'price') {
$query['meta_key'] = '_aksrt_pay_price';
$query['orderby'] = 'meta_value_num';
} else {
$query['orderby'] = 'date';
}
$cat = max(0, (int) ($instance['cat'] ?? 0));
if ($cat > 0) {
$query['cat'] = $cat;
}
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
return $query;
}
public static function render_items_html(\WP_Query $q, string $style = 'mini'): string {
if (!$q->have_posts()) {
return '';
}
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
ob_start();
while ($q->have_posts()) {
$q->the_post();
$pid = get_the_ID();
$price = (float) get_post_meta($pid, '_aksrt_pay_price', true);
$credits = (int) get_post_meta($pid, '_aksrt_credits_cost', true);
$thumb = get_the_post_thumbnail_url($pid, 'thumbnail');
if ($price > 0) {
$badge = '¥' . number_format($price, 2, '.', '');
} elseif ($credits > 0) {
$badge = sprintf(__('%d 积分', 'aksrt'), $credits);
} else {
$badge = __('付费', 'aksrt');
}
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'mini';
if (function_exists('aksrt_render_post')) {
$item = aksrt_render_post([
'mode' => $mode,
'context' => 'widget',
'show_thumb' => true,
'show_meta' => true,
'show_excerpt' => $mode === 'list',
'extra_class' => 'ajax-item aksrt-paid-posts__' . $mode,
]);
if ($item !== '') {
echo '<div class="aksrt-paid-posts__item is-' . esc_attr($mode) . ' ajax-item">';
echo $item; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="aksrt-paid-posts__price badge">' . esc_html($badge) . '</span>';
echo '</div>';
continue;
}
}
echo '<li class="aksrt-paid-posts__item ajax-item">';
if ($thumb) {
echo '<a class="aksrt-paid-posts__thumb" href="' . esc_url(get_permalink()) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" /></a>';
}
echo '<div class="aksrt-paid-posts__body">';
echo '<a class="aksrt-paid-posts__title" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
echo '<div class="aksrt-paid-posts__meta"><span class="aksrt-paid-posts__price">' . esc_html($badge) . '</span></div>';
echo '</div></li>';
}
wp_reset_postdata();
return (string) ob_get_clean();
}
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$q = new \WP_Query(self::build_query_args($instance, $page));
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
if (!in_array($style, ['mini', 'list', 'card'], true)) {
$style = 'mini';
}
$items = self::render_items_html($q, $style);
$wrap_tag = (strpos($items, 'posts-mini') !== false || strpos($items, 'aksrt-paid-posts__mini') !== false) ? 'div' : 'ul';
$html = $items !== '' ? '<' . $wrap_tag . ' class="aksrt-paid-posts style-' . esc_attr($style) . '">' . $items . '</' . $wrap_tag . '>' : '';
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_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,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'cat' => max(0, (int) ($instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($instance['limit_day'] ?? 0)),
'style' => $style,
'paginate' => $paginate,
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/paid-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 {
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1);
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
}
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
$rest = rest_url('aksrt/v1/widgets/paid-posts');
$wrap = 'aksrt-paid-posts-wrap ajaxpager' . $affix['class'];
echo '<div class="' . esc_attr($wrap) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '"' . $affix['attr'] . '>';
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'] ?? __('付费精选', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('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('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="price" <?php selected(($instance['orderby'] ?? ''), 'price'); ?>><?php esc_html_e('价格', 'aksrt'); ?></option>
</select>
</label>
</p>
<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>
<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>
<?php $this->render_common_form_section($instance); ?>
<?php
}
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'views', 'price'], true)) {
$orderby = 'date';
}
return $this->update_common_fields($new_instance, $old_instance, [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))),
'orderby' => $orderby,
'style' => in_array(sanitize_key((string) ($new_instance['style'] ?? 'mini')), ['mini', 'list', 'card'], true) ? sanitize_key((string) ($new_instance['style'] ?? 'mini')) : 'mini',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
]);
}
}
-360
View File
@@ -1,360 +0,0 @@
<?php
/**
* Horizontal partners / friends scroll (zibll zib_widget_ui_partners_scroll).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetPartnersScroll extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_partners_scroll',
esc_html__('AKSRT 合作伙伴滚动', 'aksrt'),
[
'description' => esc_html__('横向无缝滚动的合作伙伴/友情链接条。', 'aksrt'),
'classname' => 'widget_aksrt_partners_scroll',
]
);
}
public function widget($args, $instance): void {
$partners = $this->resolve_partners($instance);
if (!$partners) {
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;
}
$rows = max(1, min(4, (int) ($instance['rows'] ?? 1)));
$m_rows = max(1, min(4, (int) ($instance['m_rows'] ?? 1)));
$style_type = sanitize_key((string) ($instance['style_type'] ?? 'card'));
if (!in_array($style_type, ['card', 'logo', 'chip'], true)) {
$style_type = 'card';
}
$card_w = max(60, min(400, (int) ($instance['card_width'] ?? 220)));
$card_h = max(40, min(200, (int) ($instance['card_height'] ?? 90)));
$speed = max(1, min(20, (int) ($instance['speed'] ?? 3)));
$direction = sanitize_key((string) ($instance['direction'] ?? 'alternate'));
$pause = !empty($instance['pause_hover']) || !isset($instance['pause_hover']);
$card_bg = !empty($instance['card_bg']) || !isset($instance['card_bg']);
$end_fade = !empty($instance['end_fade']) || !isset($instance['end_fade']);
$go_link = !empty($instance['go_link']);
$show_bg = !empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg']);
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 10)));
$use_rows = wp_is_mobile() ? $m_rows : $rows;
$rows_data = array_fill(0, $use_rows, []);
foreach (array_values($partners) as $i => $p) {
$rows_data[$i % $use_rows][] = $p;
}
$rows_data = array_values(array_filter($rows_data, static fn($r) => !empty($r)));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$wrap_class = 'aksrt-partners-scroll aksrt-ps-style-' . $style_type;
if ($pause) {
$wrap_class .= ' aksrt-ps-pause-hover';
}
if ($card_bg) {
$wrap_class .= ' aksrt-ps-card-bg';
}
if ($end_fade) {
$wrap_class .= ' aksrt-ps-end-fade';
}
$style = sprintf(
'--aksrt-ps-w:%dpx;--aksrt-ps-h:%dpx;--aksrt-ps-radius:%dpx;--aksrt-ps-speed:%d;',
$card_w,
$card_h,
$radius,
$speed
);
$outer = $show_bg ? 'aksrt-partners-scroll-wrap' : 'aksrt-partners-scroll-wrap is-bare';
echo '<div class="' . esc_attr($outer) . '">';
echo '<div class="' . esc_attr($wrap_class) . '" style="' . esc_attr($style) . '">';
foreach ($rows_data as $row_index => $row_partners) {
switch ($direction) {
case 'left':
$dir = 'left';
break;
case 'right':
$dir = 'right';
break;
default:
$dir = ($row_index % 2 === 0) ? 'left' : 'right';
}
echo '<div class="aksrt-ps-row aksrt-ps-dir-' . esc_attr($dir) . '">';
echo '<div class="aksrt-ps-track">';
// Duplicate group for seamless CSS marquee.
for ($dup = 0; $dup < 2; $dup++) {
echo '<div class="aksrt-ps-group">';
foreach ($row_partners as $partner) {
echo $this->render_card($partner, $style_type, $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div></div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $partner
*/
private function render_card(array $partner, string $style_type, bool $go_link): string {
$title = trim((string) ($partner['title'] ?? ''));
$desc = trim((string) ($partner['desc'] ?? ''));
$image = (string) ($partner['image'] ?? '');
$url = (string) ($partner['url'] ?? '');
$target = (string) ($partner['target'] ?? '');
if ($image === '' && $title === '') {
return '';
}
$alt = esc_attr($title !== '' ? $title : __('合作伙伴', 'aksrt'));
$img_tag = $image !== ''
? '<img class="aksrt-ps-img aksrt-lazy lazyload" data-src="' . esc_url($image) . '" src="' . esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg') . '" alt="' . $alt . '" loading="lazy" />'
: '';
$inner = '';
if ($style_type === 'logo') {
if ($img_tag === '') {
return '';
}
$inner = $img_tag;
} elseif ($style_type === 'chip') {
if ($img_tag !== '') {
$inner .= '<span class="aksrt-ps-chip-icon">' . $img_tag . '</span>';
}
$inner .= '<span class="aksrt-ps-chip-title">' . esc_html($title) . '</span>';
} else {
if ($img_tag !== '') {
$inner .= '<div class="aksrt-ps-card-icon">' . $img_tag . '</div>';
}
$inner .= '<div class="aksrt-ps-card-text">';
if ($title !== '') {
$inner .= '<div class="aksrt-ps-card-title">' . esc_html($title) . '</div>';
}
if ($desc !== '') {
$inner .= '<div class="aksrt-ps-card-desc">' . esc_html($desc) . '</div>';
}
$inner .= '</div>';
}
if ($url !== '') {
if ($go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
return '<a class="aksrt-ps-item" href="' . esc_url($url) . '"' . $tgt . '>' . $inner . '</a>';
}
return '<div class="aksrt-ps-item">' . $inner . '</div>';
}
/**
* @param array<string,mixed> $instance
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
*/
private function resolve_partners(array $instance): array {
$source = sanitize_key((string) ($instance['link_type'] ?? 'manual'));
if ($source === 'link') {
$args = [
'orderby' => sanitize_key((string) ($instance['link_orderby'] ?? 'name')) ?: 'name',
'order' => strtoupper((string) ($instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
'limit' => max(1, min(100, (int) ($instance['link_limit'] ?? 50))),
];
$cats = $instance['link_cats'] ?? '';
if (is_array($cats)) {
$cats = implode(',', array_map('intval', $cats));
}
$cats = trim((string) $cats);
if ($cats !== '') {
$args['category'] = $cats;
}
$bookmarks = get_bookmarks($args);
$out = [];
foreach ($bookmarks as $b) {
$out[] = [
'title' => (string) $b->link_name,
'desc' => (string) $b->link_description,
'image' => (string) $b->link_image,
'url' => (string) $b->link_url,
'target' => (string) $b->link_target,
];
}
return $out;
}
return $this->normalize_manual($instance['partners'] ?? []);
}
/**
* @param mixed $raw
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
*/
private function normalize_manual($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? ''));
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
if ($title === '' && $image === '') {
continue;
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'image' => $image,
'url' => $url,
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
];
if (count($out) >= 40) {
break;
}
}
return $out;
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$link_type = (string) ($instance['link_type'] ?? 'manual');
$partners_json = is_string($instance['partners'] ?? null)
? (string) $instance['partners']
: wp_json_encode($this->normalize_manual($instance['partners'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数据来源', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('link_type')); ?>">
<option value="manual" <?php selected($link_type, 'manual'); ?>><?php esc_html_e('手动 JSON', 'aksrt'); ?></option>
<option value="link" <?php selected($link_type, 'link'); ?>><?php esc_html_e('WordPress 链接(书签)', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('合作伙伴 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('partners')); ?>" placeholder='[{"title":"Name","image":"https://...","url":"https://...","desc":""}]'><?php echo esc_textarea((string) $partners_json); ?></textarea>
</label>
<small><?php esc_html_e('字段: title, image, url, desc, target', 'aksrt'); ?></small>
</p>
<p>
<label><?php esc_html_e('链接分类 ID(逗号分隔,书签模式)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('link_cats')); ?>" value="<?php echo esc_attr((string) ($instance['link_cats'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('书签数量上限', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('link_limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['link_limit'] ?? 50))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style_type')); ?>">
<option value="card" <?php selected(($instance['style_type'] ?? 'card'), 'card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
<option value="logo" <?php selected(($instance['style_type'] ?? ''), 'logo'); ?>><?php esc_html_e('纯 Logo', 'aksrt'); ?></option>
<option value="chip" <?php selected(($instance['style_type'] ?? ''), 'chip'); ?>><?php esc_html_e('胶囊', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('PC 行数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['rows'] ?? 1))); ?>">
</label>
<label><?php esc_html_e('移动行数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('m_rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_rows'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片宽', 'aksrt'); ?>
<input class="tiny-text" type="number" min="60" max="400" name="<?php echo esc_attr($this->get_field_name('card_width')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_width'] ?? 220))); ?>">
</label>
<label><?php esc_html_e('高', 'aksrt'); ?>
<input class="tiny-text" type="number" min="40" max="200" name="<?php echo esc_attr($this->get_field_name('card_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_height'] ?? 90))); ?>">
</label>
<label><?php esc_html_e('速度', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['speed'] ?? 3))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('方向', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
<option value="alternate" <?php selected(($instance['direction'] ?? 'alternate'), 'alternate'); ?>><?php esc_html_e('交替', 'aksrt'); ?></option>
<option value="left" <?php selected(($instance['direction'] ?? ''), 'left'); ?>><?php esc_html_e('向左', 'aksrt'); ?></option>
<option value="right" <?php selected(($instance['direction'] ?? ''), 'right'); ?>><?php esc_html_e('向右', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pause_hover')); ?>" value="1" <?php checked(!empty($instance['pause_hover']) || !isset($instance['pause_hover'])); ?>> <?php esc_html_e('悬停暂停', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('card_bg')); ?>" value="1" <?php checked(!empty($instance['card_bg']) || !isset($instance['card_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('end_fade')); ?>" value="1" <?php checked(!empty($instance['end_fade']) || !isset($instance['end_fade'])); ?>> <?php esc_html_e('两端渐隐', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg'])); ?>> <?php esc_html_e('显示背景盒子', 'aksrt'); ?></label>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'link_type' => in_array(($new_instance['link_type'] ?? ''), ['manual', 'link'], true) ? $new_instance['link_type'] : 'manual',
'partners' => $this->normalize_manual($new_instance['partners'] ?? []),
'link_cats' => sanitize_text_field((string) ($new_instance['link_cats'] ?? '')),
'link_limit' => max(1, min(100, (int) ($new_instance['link_limit'] ?? 50))),
'link_orderby' => sanitize_key((string) ($new_instance['link_orderby'] ?? 'name')),
'link_order' => strtoupper((string) ($new_instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
'style_type' => in_array(($new_instance['style_type'] ?? ''), ['card', 'logo', 'chip'], true) ? $new_instance['style_type'] : 'card',
'rows' => max(1, min(4, (int) ($new_instance['rows'] ?? 1))),
'm_rows' => max(1, min(4, (int) ($new_instance['m_rows'] ?? 1))),
'card_width' => max(60, min(400, (int) ($new_instance['card_width'] ?? 220))),
'card_height' => max(40, min(200, (int) ($new_instance['card_height'] ?? 90))),
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 10))),
'speed' => max(1, min(20, (int) ($new_instance['speed'] ?? 3))),
'direction' => in_array(($new_instance['direction'] ?? ''), ['alternate', 'left', 'right'], true) ? $new_instance['direction'] : 'alternate',
'pause_hover' => !empty($new_instance['pause_hover']) ? 1 : 0,
'card_bg' => !empty($new_instance['card_bg']) ? 1 : 0,
'end_fade' => !empty($new_instance['end_fade']) ? 1 : 0,
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
+86 -83
View File
@@ -1,6 +1,6 @@
<?php
/**
* Singular post pay purchase sidebar card (zibll zib_widget_ui_posts_pay).
* Compact paid-content purchase card for the article sidebar.
*
* @package AKSRT
*/
@@ -14,13 +14,12 @@ defined('ABSPATH') || exit;
class WidgetPostPayBox extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_post_pay_box',
esc_html__('AKSRT 付费购买', 'aksrt'),
[
'description' => esc_html__('显示当前文章/帖子的付费购买模块,推荐放在详情侧栏。', 'aksrt'),
'description' => esc_html__('仅在文章页 - 侧边栏展示当前文章的紧凑购买卡。', 'aksrt'),
'classname' => 'widget_aksrt_post_pay_box',
]
);
@@ -30,17 +29,12 @@ class WidgetPostPayBox extends \WP_Widget {
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance = []): string {
if (!is_singular(['post', 'page'])) {
if (!is_single() || get_post_type() !== 'post') {
return '';
}
$post = get_queried_object();
if (!$post instanceof \WP_Post) {
return '';
}
if (!function_exists('aksrt_get_pay_meta') || !function_exists('aksrt_is_paid_post')) {
return '';
}
if (!aksrt_is_paid_post($post)) {
if (!($post instanceof \WP_Post) || !function_exists('aksrt_get_pay_meta') || !function_exists('aksrt_is_paid_post') || !aksrt_is_paid_post($post)) {
return '';
}
@@ -51,23 +45,39 @@ class WidgetPostPayBox extends \WP_Widget {
}
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
if ($theme === '') {
$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';
}
$has_access = function_exists('aksrt_has_paid_access') ? aksrt_has_paid_access($post) : false;
$priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null;
$pay_modo = is_array($priced) && (($priced['modo'] ?? '') === 'points') ? 'points' : '0';
$amount = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0);
$currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
$cash_symbol = '¥';
if (class_exists('\AKSRT\Settings\Settings')) {
$paid_s = \AKSRT\Settings\Settings::get_paid_content_settings();
$cash_symbol = (string) ($paid_s['display']['currency_symbol'] ?? '¥');
$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));
}
$price_text = $pay_modo === 'points'
? (function_exists('aksrt_format_price') ? aksrt_format_price($amount) : (string) $amount) . ' ' . $currency
: $cash_symbol . (function_exists('aksrt_format_price') ? aksrt_format_price($amount) : number_format($amount, 2, '.', ''));
$type_labels = [
'reading' => __('付费阅读', 'aksrt'),
@@ -77,65 +87,56 @@ class WidgetPostPayBox extends \WP_Widget {
'vip' => __('会员专属', 'aksrt'),
];
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
$cta_map = [
$action_labels = [
'reading' => __('查看内容', 'aksrt'),
'download' => __('获取下载', 'aksrt'),
'download' => __('前往下载', 'aksrt'),
'gallery' => __('查看图片', 'aksrt'),
'video' => __('查看视频', 'aksrt'),
'vip' => __('查看内容', 'aksrt'),
];
$cta_label = $cta_map[$pay_type] ?? __('查看内容', 'aksrt');
$sales = 0;
if (function_exists('aksrt_get_download_box_meta')) {
$box = aksrt_get_download_box_meta($post);
$sales = (int) ($box['sales_count'] ?? 0);
}
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post));
$is_logged_in = is_user_logged_in();
$demo = trim((string) ($meta['demo_url'] ?? ''));
$action_label = $action_labels[$pay_type] ?? __('查看内容', 'aksrt');
$show_original = $original > $amount && $original > 0;
$show_member_discount = !$has_access && $amount > 0 && $base > $amount;
ob_start();
echo '<div class="zib-widget pay-box pay-widget aksrt-post-pay-box order-type-' . esc_attr($pay_type) . ($has_access ? ' is-paid' : ' is-unpaid') . '" style="padding:0;">';
echo '<badge class="pay-tag abs-center">' . esc_html($type_label) . '</badge>';
echo '<div class="relative-h ' . esc_attr($theme) . ' aksrt-post-pay-box__hero">';
echo '<div class="absolute radius ' . esc_attr($theme) . ' aksrt-post-pay-box__blob aksrt-post-pay-box__blob--a"></div>';
echo '<div class="absolute radius ' . esc_attr($theme) . ' aksrt-post-pay-box__blob aksrt-post-pay-box__blob--b"></div>';
echo '<div class="relative box-body text-center">';
if ($has_access) {
echo '<p class="mt10 mb6"><i class="fa fa-shopping-bag fa-3x" aria-hidden="true"></i></p>';
echo '<b class="em14">' . esc_html__('已解锁', 'aksrt') . '</b>';
} else {
echo '<div class="text-center mt10 aksrt-post-pay-box__price">';
if ($amount > 0) {
echo '<div class="em14 font-bold">' . esc_html($price_text) . '</div>';
} else {
echo '<div class="em14 font-bold">' . esc_html__('免费', 'aksrt') . '</div>';
}
echo '</div>';
}
echo '</div></div>';
echo '<div class="box-body">';
if ($has_access) {
echo '<div class="mt10"><a href="#posts-pay" class="but padding-lg btn-block ' . esc_attr($theme) . '"><i class="fa fa-dot-circle-o fa-fw" aria-hidden="true"></i> ' . esc_html($cta_label) . '</a></div>';
} elseif (!$is_logged_in) {
echo '<div class="mt10"><a href="' . esc_url($login_url) . '" class="but padding-lg btn-block jb-blue"><i class="fa fa-sign-in" aria-hidden="true"></i> ' . esc_html__('登录后购买', 'aksrt') . '</a></div>';
if ($amount > 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) {
echo '<div class="mt10"><button type="button" class="but padding-lg btn-block ' . esc_attr($theme) . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fa-shopping-cart fa-fw" aria-hidden="true"></i> ' . esc_html(sprintf(/* translators: price */ __('游客购买(%s)', 'aksrt'), $price_text)) . '</button></div>';
}
} else {
echo '<div class="mt10"><button type="button" class="but padding-lg btn-block ' . esc_attr($theme) . ' cashier-link" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fa-shopping-cart fa-fw" aria-hidden="true"></i> ' . esc_html(sprintf(/* translators: price */ __('立即购买(%s)', 'aksrt'), $price_text)) . '</button></div>';
}
if ($demo !== '') {
echo '<div class="mt10"><a class="but hollow btn-block" href="' . esc_url($demo) . '" target="_blank" rel="noopener noreferrer"><i class="fa fa-external-link" aria-hidden="true"></i> ' . esc_html__('演示地址', 'aksrt') . '</a></div>';
}
echo '</div>';
if ($sales > 0) {
echo '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(/* translators: sales count */ __('已售 %s', 'aksrt'), (string) $sales)) . '</badge>';
}
echo '</div>';
?>
<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();
}
@@ -144,6 +145,7 @@ class WidgetPostPayBox extends \WP_Widget {
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
@@ -151,7 +153,7 @@ class WidgetPostPayBox extends \WP_Widget {
public function form($instance): void {
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
$opts = [
$options = [
'jb-red' => __('红', 'aksrt'),
'jb-yellow' => __('橙', 'aksrt'),
'jb-blue' => __('蓝', 'aksrt'),
@@ -161,19 +163,19 @@ class WidgetPostPayBox extends \WP_Widget {
'jb-vip2' => __('会员灰', 'aksrt'),
];
?>
<p class="description"><?php esc_html_e('仅在文章/帖子详情侧栏显示当前内容的付费购买卡。', 'aksrt'); ?></p>
<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 ($opts as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected($theme, $k); ?>><?php echo esc_html($label); ?></option>
<?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 $this->render_common_form_section($instance); ?>
<?php
$this->render_common_form_section($instance);
}
}
public function update($new_instance, $old_instance): array {
$theme = sanitize_html_class((string) ($new_instance['theme'] ?? 'jb-red'));
@@ -181,6 +183,7 @@ class WidgetPostPayBox extends \WP_Widget {
if (!in_array($theme, $allowed, true)) {
$theme = 'jb-red';
}
$business = ['theme' => $theme]; return $this->update_common_fields($new_instance, $old_instance, $business);
return $this->update_common_fields($new_instance, $old_instance, ['theme' => $theme]);
}
}
-95
View File
@@ -1,95 +0,0 @@
<?php
/**
* In-article / topic TOC shell (zibll widget_ui_posts_navs parity).
*
* Shell matches the built-in single sidebar TOC so page-template.js can fill
* `.posts-nav-box.toc-widget > .toc-list` from `[data-nav]` headings.
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetPostsNavs extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_posts_navs',
esc_html__('AKSRT 文章目录树', 'aksrt'),
[
'description' => esc_html__('显示当前文章/帖子标题目录,非详情页不输出。标题超过 2 个才填充。', 'aksrt'),
'classname' => 'widget_aksrt_posts_navs',
]
);
}
public function widget($args, $instance): void {
if (!is_singular(['post', 'page'])) {
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;
}
$title = sanitize_text_field((string) ($instance['title'] ?? __('文章目录', 'aksrt')));
$mini_title = sanitize_text_field((string) ($instance['mini_title'] ?? ''));
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
$data_title = $title;
if ($mini_title !== '') {
$data_title .= ($data_title !== '' ? ' · ' : '') . $mini_title;
}
if ($data_title === '') {
$data_title = __('文章目录', 'aksrt');
}
// Mirror aksrt-single-render built-in TOC shell so page-template.js binds.
$classes = 'zib-widget toc-widget posts-nav-box' . $affix['class'];
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
printf(
'<div class="%s"%s data-title="%s" aria-label="%s">',
esc_attr($classes),
$affix['attr'],
esc_attr($data_title),
esc_attr($data_title)
);
echo '<h3 class="widget-title toc-widget__title">' . esc_html($data_title) . '</h3>';
echo '<ol class="toc-list"></ol>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? __('文章目录', 'aksrt'));
$mini_title = (string) ($instance['mini_title'] ?? '');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('mini_title')); ?>"><?php esc_html_e('副标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('mini_title')); ?>" name="<?php echo esc_attr($this->get_field_name('mini_title')); ?>" value="<?php echo esc_attr($mini_title); ?>">
</p>
<p class="description"><?php esc_html_e('请添加到文章/帖子侧栏。正文标题 ≥3 个时由前端填充目录;无标题则自动移除。', 'aksrt'); ?></p>
<?php $this->render_common_form_section($instance); ?>
<?php
}
public function update($new_instance, $old_instance): array {
return $this->update_common_fields($new_instance, $old_instance, [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'mini_title' => sanitize_text_field((string) ($new_instance['mini_title'] ?? '')),
]);
}
}
-251
View File
@@ -1,251 +0,0 @@
<?php
/**
* Pricing plan cards (zibll zib_widget_pricing_plans lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetPricingPlans extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_pricing_plans',
esc_html__('AKSRT 价格方案', 'aksrt'),
[
'description' => esc_html__('多列定价卡片 + 信任条(落地页/VIP 介绍)。', 'aksrt'),
'classname' => 'widget_aksrt_pricing_plans',
]
);
}
public function widget($args, $instance): void {
$plans = $this->normalize_plans($instance['lists'] ?? $instance['plans'] ?? []);
if (!$plans) {
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;
}
$trust = $this->normalize_trust($instance['trust_items'] ?? []);
$currency = sanitize_text_field((string) ($instance['currency'] ?? '¥'));
if ($currency === '') {
$currency = '¥';
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="aksrt-pricing">';
echo '<div class="aksrt-pricing__cards">';
$pi = 0;
foreach ($plans as $plan) {
$accent = sanitize_key((string) ($plan['color'] ?? 'blue'));
if (!in_array($accent, ['pink', 'purple', 'blue', 'green', 'yellow', 'cyan'], true)) {
$accent = 'blue';
}
$featured = !empty($plan['is_featured']);
$pack = function_exists('aksrt_obs_animate_attrs') ? aksrt_obs_animate_attrs('slideup', false, $pi * 0.12) : ['class' => '', 'attr' => ''];
$class = 'aksrt-pricing__card accent-' . $accent . ($featured ? ' is-featured' : '') . $pack['class'];
echo '<div class="' . esc_attr(trim($class)) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$pi++;
if ($featured || !empty($plan['tag'])) {
$badge = (string) ($plan['tag'] !== '' ? $plan['tag'] : __('推荐', 'aksrt'));
echo '<span class="aksrt-pricing__badge">' . esc_html($badge) . '</span>';
}
echo '<h3 class="aksrt-pricing__name">' . esc_html($plan['name']) . '</h3>';
if ($plan['desc'] !== '') {
echo '<p class="aksrt-pricing__desc">' . esc_html($plan['desc']) . '</p>';
}
$price = (float) $plan['price'];
$show = (float) $plan['show_price'];
echo '<div class="aksrt-pricing__price">';
echo '<span class="aksrt-pricing__main"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($price)) . '</span>';
if ($show > $price && $show > 0) {
echo '<span class="aksrt-pricing__original"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($show)) . '</span>';
}
echo '</div>';
if ($plan['features']) {
echo '<ul class="aksrt-pricing__features">';
foreach ($plan['features'] as $ft) {
echo '<li><i class="fa fa-check" aria-hidden="true"></i><span>' . esc_html($ft) . '</span></li>';
}
echo '</ul>';
}
if ($plan['btn_text'] !== '' && $plan['btn_url'] !== '') {
$url = $plan['btn_url'];
if (!empty($instance['go_link']) && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
echo '<div class="aksrt-pricing__action"><a class="aksrt-pricing__btn" href="' . esc_url($url) . '">' . esc_html($plan['btn_text']) . '</a></div>';
}
echo '</div>';
}
echo '</div>';
if ($trust) {
echo '<div class="aksrt-pricing__trust">';
foreach ($trust as $ti) {
echo '<div class="aksrt-pricing__trust-item">';
if ($ti['icon'] !== '') {
echo '<i class="' . esc_attr($ti['icon']) . '" aria-hidden="true"></i>';
}
echo '<div><strong>' . esc_html($ti['title']) . '</strong>';
if ($ti['desc'] !== '') {
echo '<span>' . esc_html($ti['desc']) . '</span>';
}
echo '</div></div>';
}
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function fmt(float $n): string {
if (abs($n - round($n)) < 0.001) {
return (string) (int) round($n);
}
return number_format($n, 2, '.', '');
}
/**
* @param mixed $raw
* @return array<int, array<string,mixed>>
*/
private function normalize_plans($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$name = sanitize_text_field((string) ($row['name'] ?? $row['title'] ?? ''));
if ($name === '') {
continue;
}
$intro = (string) ($row['intro'] ?? $row['features'] ?? '');
if (is_array($row['features'] ?? null)) {
$features = array_values(array_filter(array_map('sanitize_text_field', $row['features'])));
} else {
$features = array_values(array_filter(array_map('sanitize_text_field', preg_split('/\r\n|\r|\n/', $intro) ?: [])));
}
$btn = is_array($row['btn'] ?? null) ? $row['btn'] : [];
$out[] = [
'name' => $name,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'price' => (float) ($row['price'] ?? 0),
'show_price' => (float) ($row['show_price'] ?? 0),
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
'is_featured' => !empty($row['is_featured']) ? 1 : 0,
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
'features' => $features,
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? $btn['text'] ?? '')),
'btn_url' => esc_url_raw(trim((string) ($row['btn_url'] ?? $btn['url'] ?? ''))),
];
if (count($out) >= 6) {
break;
}
}
return $out;
}
/**
* @param mixed $raw
* @return array<int, array{title:string,desc:string,icon:string}>
*/
private function normalize_trust($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? ''));
if ($title === '') {
continue;
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'icon' => sanitize_text_field((string) ($row['icon'] ?? 'fa fa-check-circle')),
];
if (count($out) >= 6) {
break;
}
}
return $out;
}
public function form($instance): void {
$plans = is_string($instance['lists'] ?? null)
? (string) $instance['lists']
: wp_json_encode($this->normalize_plans($instance['lists'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$trust = is_string($instance['trust_items'] ?? null)
? (string) $instance['trust_items']
: wp_json_encode($this->normalize_trust($instance['trust_items'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('货币符号', 'aksrt'); ?>
<input class="tiny-text" type="text" name="<?php echo esc_attr($this->get_field_name('currency')); ?>" value="<?php echo esc_attr((string) ($instance['currency'] ?? '¥')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('方案 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('lists')); ?>" placeholder='[{"name":"Pro","price":99,"show_price":199,"desc":"适合创作者","features":["无限下载","优先支持"],"btn_text":"立即开通","btn_url":"#","is_featured":1,"color":"pink"}]'><?php echo esc_textarea((string) $plans); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('信任条 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('trust_items')); ?>"><?php echo esc_textarea((string) $trust); ?></textarea>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'currency' => sanitize_text_field((string) ($new_instance['currency'] ?? '¥')),
'lists' => $this->normalize_plans($new_instance['lists'] ?? []),
'trust_items' => $this->normalize_trust($new_instance['trust_items'] ?? []),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
-166
View File
@@ -1,166 +0,0 @@
<?php
/**
* Testimonials / virtual reviews carousel (zibll zib_widget_reviews lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetReviews extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_reviews',
esc_html__('AKSRT 用户评价', 'aksrt'),
[
'description' => esc_html__('虚拟用户评价卡片横向滚动。', 'aksrt'),
'classname' => 'widget_aksrt_reviews',
]
);
}
public function widget($args, $instance): void {
$reviews = $this->normalize($instance['reviews'] ?? []);
if (!$reviews) {
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 = max(1, min(6, (float) ($instance['desktop_per_view'] ?? 3)));
$m = max(1, min(3, (float) ($instance['mobile_per_view'] ?? 1.2)));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$style = sprintf('--aksrt-rev-pc:%s;--aksrt-rev-m:%s;', rtrim(rtrim(number_format($pc, 2, '.', ''), '0'), '.'), rtrim(rtrim(number_format($m, 2, '.', ''), '0'), '.'));
echo '<div class="aksrt-reviews" style="' . esc_attr($style) . '">';
echo '<div class="swiper-container swiper-scroll aksrt-reviews-swiper">';
echo '<div class="swiper-wrapper aksrt-reviews__track">';
foreach ($reviews as $r) {
echo '<div class="swiper-slide aksrt-reviews__slide"><div class="aksrt-reviews__card" data-aksrt-tilt data-tilt-max="8">';
echo '<div class="aksrt-reviews__head">';
if ($r['avatar'] !== '') {
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<img class="aksrt-reviews__avatar aksrt-lazy lazyload" data-src="' . esc_url($r['avatar']) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="44" height="44" />';
} else {
echo '<span class="aksrt-reviews__avatar is-placeholder"><i class="fa fa-user" aria-hidden="true"></i></span>';
}
echo '<div class="aksrt-reviews__meta">';
if ($r['name'] !== '') {
$name_html = esc_html($r['name']);
if ($r['url'] !== '') {
$name_html = '<a href="' . esc_url($r['url']) . '" target="_blank" rel="noopener noreferrer">' . $name_html . '</a>';
}
echo '<div class="aksrt-reviews__name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($r['role'] !== '') {
echo '<div class="aksrt-reviews__role">' . esc_html($r['role']) . '</div>';
}
echo '</div></div>';
echo '<div class="aksrt-reviews__stars" aria-label="' . esc_attr(sprintf(__('%d 星', 'aksrt'), $r['rating'])) . '">';
for ($i = 1; $i <= 5; $i++) {
echo '<i class="fa fa-star' . ($i <= $r['rating'] ? ' is-on' : ' is-off') . '" aria-hidden="true"></i>';
}
echo '</div>';
if ($r['content'] !== '') {
echo '<p class="aksrt-reviews__content">' . nl2br(esc_html($r['content'])) . '</p>';
}
echo '<i class="fa fa-quote-right aksrt-reviews__quote" aria-hidden="true"></i>';
echo '</div></div>';
}
echo '</div>';
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>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array{name:string,role:string,avatar:string,content:string,rating:int,url:string}>
*/
private function normalize($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$name = sanitize_text_field((string) ($row['name'] ?? ''));
$content = sanitize_textarea_field((string) ($row['content'] ?? ''));
if ($name === '' && $content === '') {
continue;
}
$out[] = [
'name' => $name,
'role' => sanitize_text_field((string) ($row['role'] ?? '')),
'avatar' => esc_url_raw(trim((string) ($row['avatar'] ?? ''))),
'content' => $content,
'rating' => max(0, min(5, (int) ($row['rating'] ?? 5))),
'url' => esc_url_raw(trim((string) ($row['url'] ?? ''))),
];
if (count($out) >= 20) {
break;
}
}
return $out;
}
public function form($instance): void {
$json = is_string($instance['reviews'] ?? null)
? (string) $instance['reviews']
: wp_json_encode($this->normalize($instance['reviews'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('评价 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('reviews')); ?>" placeholder='[{"name":"张三","role":"站长","rating":5,"content":"很好用","avatar":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('PC 每屏', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" step="0.1" name="<?php echo esc_attr($this->get_field_name('desktop_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['desktop_per_view'] ?? '3')); ?>">
</label>
<label><?php esc_html_e('移动每屏', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" step="0.1" name="<?php echo esc_attr($this->get_field_name('mobile_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['mobile_per_view'] ?? '1.2')); ?>">
</label>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'reviews' => $this->normalize($new_instance['reviews'] ?? []),
'desktop_per_view' => max(1, min(6, (float) ($new_instance['desktop_per_view'] ?? 3))),
'mobile_per_view' => max(1, min(3, (float) ($new_instance['mobile_per_view'] ?? 1.2))),
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
-194
View File
@@ -1,194 +0,0 @@
<?php
/**
* Stats number cards (zibll zib_widget_stats lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetStats extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_stats',
esc_html__('AKSRT 数据统计', 'aksrt'),
[
'description' => esc_html__('数字统计卡片网格(站点数据展示)。', 'aksrt'),
'classname' => 'widget_aksrt_stats',
]
);
}
public function widget($args, $instance): void {
$stats = $this->normalize_stats($instance['stats'] ?? []);
if (!$stats) {
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;
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 4)));
$m_cols = max(1, min(3, (int) ($instance['mobile_columns'] ?? 2)));
$intro_title = trim((string) ($instance['intro_title'] ?? ''));
$intro_sub = trim((string) ($instance['intro_subtitle'] ?? ''));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$style = sprintf('--aksrt-stats-pc:%d;--aksrt-stats-m:%d;', $cols, $m_cols);
echo '<div class="aksrt-stats" style="' . esc_attr($style) . '">';
if ($intro_title !== '' || $intro_sub !== '') {
echo '<div class="aksrt-stats__intro">';
if ($intro_title !== '') {
echo '<h2 class="aksrt-stats__intro-title">' . esc_html($intro_title) . '</h2>';
}
if ($intro_sub !== '') {
echo '<p class="aksrt-stats__intro-sub">' . esc_html($intro_sub) . '</p>';
}
echo '</div>';
}
echo '<div class="aksrt-stats__grid">';
$palette = ['pink', 'purple', 'orange', 'blue', 'green'];
$i = 0;
foreach ($stats as $s) {
$color = sanitize_key((string) ($s['color'] ?? 'auto'));
if ($color === '' || $color === 'auto') {
$color = $palette[$i % count($palette)];
}
$i++;
echo '<div class="aksrt-stats__item color-' . esc_attr($color) . '" data-aksrt-animate="fade-up">';
if ($s['icon'] !== '') {
echo '<div class="aksrt-stats__icon"><i class="' . esc_attr($s['icon']) . '" aria-hidden="true"></i></div>';
}
echo '<div class="aksrt-stats__value">';
if ($s['prefix'] !== '') {
echo '<span class="aksrt-stats__prefix">' . esc_html($s['prefix']) . '</span>';
}
// Wire effects.js count-up when number is numeric.
$raw_num = preg_replace('/[^\d.]/', '', (string) $s['number']);
$is_numeric = $raw_num !== '' && is_numeric($raw_num);
if ($is_numeric) {
$dec = 0;
if (str_contains((string) $raw_num, '.')) {
$dec = strlen(substr(strrchr((string) $raw_num, '.'), 1));
}
printf(
'<span class="aksrt-stats__number" data-aksrt-countup data-target="%s" data-duration="1600" data-decimals="%d">0</span>',
esc_attr((string) $raw_num),
(int) $dec
);
} else {
echo '<span class="aksrt-stats__number" data-target="' . esc_attr($s['number']) . '">' . esc_html($s['number']) . '</span>';
}
if ($s['unit'] !== '') {
echo '<span class="aksrt-stats__unit">' . esc_html($s['unit']) . '</span>';
}
echo '</div>';
if ($s['label'] !== '') {
echo '<div class="aksrt-stats__label">' . esc_html($s['label']) . '</div>';
}
echo '</div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array{number:string,label:string,unit:string,prefix:string,icon:string,color:string}>
*/
private function normalize_stats($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$number = sanitize_text_field((string) ($row['number'] ?? ''));
if ($number === '') {
continue;
}
$out[] = [
'number' => $number,
'label' => sanitize_text_field((string) ($row['label'] ?? '')),
'unit' => sanitize_text_field((string) ($row['unit'] ?? '')),
'prefix' => sanitize_text_field((string) ($row['prefix'] ?? '')),
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'color' => sanitize_key((string) ($row['color'] ?? 'auto')),
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
public function form($instance): void {
$stats = is_string($instance['stats'] ?? null)
? (string) $instance['stats']
: wp_json_encode($this->normalize_stats($instance['stats'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数据 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('stats')); ?>" placeholder='[{"number":"1280","label":"用户","unit":"+","icon":"fas fa-users","color":"blue"}]'><?php echo esc_textarea((string) $stats); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 4))); ?>">
</label>
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('mobile_columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mobile_columns'] ?? 2))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('介绍标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_title')); ?>" value="<?php echo esc_attr((string) ($instance['intro_title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('介绍副标题', 'aksrt'); ?>
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['intro_subtitle'] ?? '')); ?></textarea>
</label>
</p>
<?php
$this->render_common_form_section($instance);
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'stats' => $this->normalize_stats($new_instance['stats'] ?? []),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 4))),
'mobile_columns' => max(1, min(3, (int) ($new_instance['mobile_columns'] ?? 2))),
'intro_title' => sanitize_text_field((string) ($new_instance['intro_title'] ?? '')),
'intro_subtitle' => sanitize_text_field((string) ($new_instance['intro_subtitle'] ?? '')),
]; return $this->update_common_fields($new_instance, $old_instance, $business);
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ class WidgetUserCard extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_user_card',
__('用户卡片', 'aksrt'),
__('AKSRT 用户卡片', 'aksrt'),
[
'description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到、快捷按钮与更多文章。', 'aksrt'),
'classname' => 'widget_aksrt_user_card',
+1 -1
View File
@@ -17,7 +17,7 @@ class WidgetUserRanking extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_user_ranking',
__('用户排行榜', 'aksrt'),
__('AKSRT 用户排行榜', 'aksrt'),
[
'description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt'),
'classname' => 'widget_aksrt_user_ranking',
+1 -1
View File
@@ -18,7 +18,7 @@ class WidgetVideoPlayer extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_video_player',
__('视频播放器', 'aksrt'),
__('AKSRT 视频播放器', 'aksrt'),
[
'description' => __('侧栏/流体区嵌入视频(共享 aksrt-video 播放器)。', 'aksrt'),
'classname' => 'widget_aksrt_video_player',
+1 -1
View File
@@ -43,7 +43,7 @@ class WidgetYiyan extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_yiyan',
__('一言轮播', 'aksrt'),
__('AKSRT 一言轮播', 'aksrt'),
[
'description' => __('显示随机一言/每日语录,支持自动轮播。', 'aksrt'),
'classname' => 'widget_aksrt_yiyan yiyan-box',
+1 -1
View File
@@ -30,7 +30,7 @@ $archive_settings = class_exists('\AKSRT\Core\TermMeta')
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
? aksrt_archive_has_sidebar('tag_sidebar', !empty($archive_settings['show_sidebar']))
: (!empty($archive_settings['show_sidebar']) && (is_active_sidebar('tag_sidebar') || is_active_sidebar('sidebar-blog')));
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('tag_sidebar'));
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
$header_style = (string) $archive_settings['header_style'];
$layout_mode = (string) $archive_settings['layout_mode'];