Merge pull request 'Worktree zibll vip ucenter' (#247) from worktree-zibll-vip-ucenter into main
Reviewed-on: CYL/wordpress-theme-aksrt#247
This commit is contained in:
@@ -2,14 +2,24 @@ export const vipSchema = {
|
||||
key: 'payment.vip',
|
||||
title: 'VIP会员',
|
||||
icon: 'fas fa-crown',
|
||||
description: '对齐 Zibll 双级会员:pay_user_vip_1 / pay_user_vip_2 与下载权益。',
|
||||
description: '对齐 Zibll 双级会员:分档套餐、权益、续费/升级与积分兑换。',
|
||||
defaults: {
|
||||
pay_user_vip_1_s: true,
|
||||
pay_user_vip_1_name: '一级会员',
|
||||
pay_user_vip_1_desc: '尊享一级会员特权',
|
||||
pay_user_vip_1_equity: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>',
|
||||
vip_1_product_s: true,
|
||||
payment_vip_1_products: '30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡',
|
||||
vip_1_img_icon: '',
|
||||
pay_user_vip_2_s: true,
|
||||
pay_user_vip_2_name: '二级会员',
|
||||
pay_user_vip_2_desc: '尊享二级会员特权',
|
||||
pay_user_vip_2_equity: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>',
|
||||
vip_2_product_s: true,
|
||||
payment_vip_2_products: '58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡',
|
||||
vip_2_img_icon: '',
|
||||
pay_user_vip_desc: '',
|
||||
payment_vip_packages: '30|30|月卡\n365|365|年卡',
|
||||
pay_user_vip_more: '',
|
||||
pay_download_limit: 0,
|
||||
pay_download_speed: 0,
|
||||
pay_download_limit_vip_1: 0,
|
||||
@@ -19,6 +29,9 @@ export const vipSchema = {
|
||||
pay_vip_pass_charge_s: false,
|
||||
pay_vip_pass_charge_desc: '',
|
||||
pay_vip_pass_charge_only_password: true,
|
||||
pay_vip_points_exchange_s: false,
|
||||
pay_vip_points_exchange_desc: '',
|
||||
payment_vip_points_packages: '1|500|30|积分月卡\n1|5000|0|积分永久',
|
||||
vip_renew: true,
|
||||
vip_renew_desc: '',
|
||||
vip_renew_price_type: 'discount',
|
||||
@@ -26,7 +39,7 @@ export const vipSchema = {
|
||||
vip_renew_reduce: 20,
|
||||
vip_upgrade: true,
|
||||
vip_upgrade_desc: '',
|
||||
vip_upgrade_product: '',
|
||||
vip_upgrade_product: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -36,17 +49,58 @@ export const vipSchema = {
|
||||
fields: [
|
||||
{ key: 'pay_user_vip_1_s', label: '启用一级会员', type: 'switch', default: true },
|
||||
{ key: 'pay_user_vip_1_name', label: '一级会员名称', type: 'input', default: '一级会员', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{ key: 'pay_user_vip_1_desc', label: '一级会员一句话简介', type: 'input', default: '尊享一级会员特权', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{ key: 'pay_user_vip_1_equity', label: '一级会员权益 HTML', type: 'textarea', default: '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_1_s', value: true }, help: '建议使用 <li> 列表项' },
|
||||
{ key: 'vip_1_img_icon', label: '一级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{ key: 'pay_user_vip_2_s', label: '启用二级会员', type: 'switch', default: true },
|
||||
{ key: 'pay_user_vip_2_name', label: '二级会员名称', type: 'input', default: '二级会员', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_user_vip_desc', label: '会员说明', type: 'textarea', default: '' },
|
||||
{ key: 'pay_user_vip_2_desc', label: '二级会员一句话简介', type: 'input', default: '尊享二级会员特权', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_user_vip_2_equity', label: '二级会员权益 HTML', type: 'textarea', default: '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'vip_2_img_icon', label: '二级会员图标 URL', type: 'input', default: '', dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{ key: 'pay_user_vip_desc', label: '弹窗顶部会员说明', type: 'textarea', default: '' },
|
||||
{ key: 'pay_user_vip_more', label: '弹窗底部补充说明 HTML', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'packages',
|
||||
title: '会员套餐',
|
||||
title: '分档套餐',
|
||||
icon: 'fas fa-gem',
|
||||
fields: [
|
||||
{ key: 'payment_vip_packages', label: '套餐列表', type: 'textarea', default: '30|30|月卡\n365|365|年卡', help: '每行一个套餐:价格|时长(天)|标题' },
|
||||
{ key: 'vip_1_product_s', label: '允许购买一级会员', type: 'switch', default: true, dependsOn: { key: 'pay_user_vip_1_s', value: true } },
|
||||
{
|
||||
key: 'payment_vip_1_products',
|
||||
label: '一级会员套餐',
|
||||
type: 'textarea',
|
||||
default: '30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡',
|
||||
dependsOn: { key: 'vip_1_product_s', value: true },
|
||||
help: '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久',
|
||||
},
|
||||
{ key: 'vip_2_product_s', label: '允许购买二级会员', type: 'switch', default: true, dependsOn: { key: 'pay_user_vip_2_s', value: true } },
|
||||
{
|
||||
key: 'payment_vip_2_products',
|
||||
label: '二级会员套餐',
|
||||
type: 'textarea',
|
||||
default: '58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡',
|
||||
dependsOn: { key: 'vip_2_product_s', value: true },
|
||||
help: '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'points_exchange',
|
||||
title: '积分兑换会员',
|
||||
icon: 'fas fa-coins',
|
||||
fields: [
|
||||
{ key: 'pay_vip_points_exchange_s', label: '启用积分兑换', type: 'switch', default: false },
|
||||
{
|
||||
key: 'payment_vip_points_packages',
|
||||
label: '兑换套餐',
|
||||
type: 'textarea',
|
||||
default: '1|500|30|积分月卡\n1|5000|0|积分永久',
|
||||
dependsOn: { key: 'pay_vip_points_exchange_s', value: true },
|
||||
help: '每行:等级|积分|天数|标题(天数 0=永久;仅未开通会员可兑)',
|
||||
},
|
||||
{ key: 'pay_vip_points_exchange_desc', label: '兑换说明', type: 'textarea', default: '', dependsOn: { key: 'pay_vip_points_exchange_s', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -73,7 +127,7 @@ export const vipSchema = {
|
||||
{ key: 'vip_renew', label: '允许续费', type: 'switch', default: true },
|
||||
{ key: 'vip_renew_desc', label: '续费说明', type: 'textarea', default: '', dependsOn: { key: 'vip_renew', value: true } },
|
||||
{ key: 'vip_renew_price_type', label: '续费价格类型', type: 'radio', default: 'discount', options: [{ label: '相同价格', value: 'same' }, { label: '折扣价格', value: 'discount' }, { label: '优惠立减', value: 'reduce' }], dependsOn: { key: 'vip_renew', value: true } },
|
||||
{ key: 'vip_renew_discount', label: '续费折扣(%)', type: 'number', default: 8, min: 1, max: 100, dependsOn: { key: 'vip_renew_price_type', value: 'discount' } },
|
||||
{ key: 'vip_renew_discount', label: '续费折扣(折,如 8=八折)', type: 'number', default: 8, min: 1, max: 100, dependsOn: { key: 'vip_renew_price_type', value: 'discount' } },
|
||||
{ key: 'vip_renew_reduce', label: '续费减免金额', type: 'number', default: 20, min: 0, max: 9999, dependsOn: { key: 'vip_renew_price_type', value: 'reduce' } },
|
||||
],
|
||||
},
|
||||
@@ -88,9 +142,9 @@ export const vipSchema = {
|
||||
key: 'vip_upgrade_product',
|
||||
label: '升级产品',
|
||||
type: 'textarea',
|
||||
default: '',
|
||||
default: 'unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级',
|
||||
dependsOn: { key: 'vip_upgrade', value: true },
|
||||
help: '每行一条。永久VIP1升级:permanent|价格|标题;按剩余天:unit|每日单价|标题;限时跳转永久:jump|价格|标题;兼容旧格式:价格|天数|标题',
|
||||
help: '每行一条。永久VIP1升级:permanent|价格|标题;按剩余天:unit|每日单价|标题;限时跳转永久:jump|价格|标题',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* VIP pay modal + hover cards (zibll payvip-modal / vip-card).
|
||||
*/
|
||||
|
||||
.aksrt-vip-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1200;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-overlay.is-open {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-dialog {
|
||||
width: min(560px, 100%);
|
||||
max-height: min(90vh, 760px);
|
||||
overflow: auto;
|
||||
background: var(--main-bg-color, #fff);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.payvip-modal {
|
||||
position: relative;
|
||||
padding: 18px 16px 16px;
|
||||
}
|
||||
|
||||
.payvip-modal .close,
|
||||
.aksrt-vip-modal-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 12px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.vip-cardminis {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vip-cardminis > li {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vip-cardminis a.aksrt-vip-tab {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 10px 8px;
|
||||
border-radius: 8px;
|
||||
background: var(--surface-2, rgba(0, 0, 0, 0.05));
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vip-cardminis > li.active a.aksrt-vip-tab {
|
||||
background: linear-gradient(90deg, #ff6b6b, #f04494);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pay-vip-form-row {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.pay-vip-form-row {
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
}
|
||||
}
|
||||
|
||||
.pay-vip-info {
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
background: var(--surface-2, rgba(0, 0, 0, 0.04));
|
||||
}
|
||||
|
||||
.pay-vip-info.level-1 {
|
||||
background: linear-gradient(135deg, rgba(255, 193, 7, 0.12), rgba(255, 152, 0, 0.08));
|
||||
}
|
||||
|
||||
.pay-vip-info.level-2 {
|
||||
background: linear-gradient(135deg, rgba(156, 39, 176, 0.12), rgba(63, 81, 181, 0.08));
|
||||
}
|
||||
|
||||
.v-equity {
|
||||
margin: 0;
|
||||
padding-left: 1.1em;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.aksrt-vip-products {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-vip-product-label {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aksrt-vip-product-label input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.vip-product {
|
||||
position: relative;
|
||||
padding: 12px 8px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
background: var(--main-bg-color, #fff);
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-vip-product-label input:checked + .vip-product {
|
||||
border-color: #f04494;
|
||||
box-shadow: 0 0 0 1px #f04494;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.vip-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 0 10px 0 8px;
|
||||
background: #ffc107;
|
||||
color: #5c3b00;
|
||||
}
|
||||
|
||||
.aksrt-vip-submit {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 12px 16px;
|
||||
background: linear-gradient(90deg, #ff6b6b, #f04494);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-vip-submit.is-loading,
|
||||
.aksrt-vip-submit:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.aksrt-vip-modal-tip.is-error,
|
||||
.uc-vip-shop__tip.is-error {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
/* Header hover VIP cards */
|
||||
.navbar-vip.hover-show {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sub-vip-card {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
width: min(420px, 90vw);
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
background: var(--main-bg-color, #fff);
|
||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.16);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.navbar-vip.hover-show:hover .sub-vip-card,
|
||||
.navbar-vip.hover-show:focus-within .sub-vip-card {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.vip-card {
|
||||
position: relative;
|
||||
flex: 1 1 160px;
|
||||
min-width: 150px;
|
||||
padding: 14px 12px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, #2b2f3a, #1b1e27);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.vip-card.vip-theme1 {
|
||||
background: linear-gradient(135deg, #f6c453, #f39c12);
|
||||
color: #3b2a00;
|
||||
}
|
||||
|
||||
.vip-card.vip-theme2 {
|
||||
background: linear-gradient(135deg, #8e44ad, #3f51b5);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.vip-card .payvip-button {
|
||||
display: inline-block;
|
||||
margin-top: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vipbg-v2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #ffb347, #ff6b6b);
|
||||
}
|
||||
|
||||
.vipbg-v2.level-2 {
|
||||
background: linear-gradient(90deg, #9b59b6, #3f51b5);
|
||||
}
|
||||
|
||||
.vip-expdate-tag {
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -1075,3 +1075,13 @@ body.uc-modal-open {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #22c55e, #16a34a);
|
||||
}
|
||||
|
||||
/* VIP tab */
|
||||
.uc-vip-tab .user-content-myvip{position:relative;padding:16px;margin-bottom:16px}
|
||||
.uc-vip-level-cards{display:flex;flex-wrap:wrap;gap:12px}
|
||||
.uc-vip-level-card{position:relative;flex:1 1 240px;padding:18px 14px;overflow:hidden}
|
||||
.uc-vip-level-card .v-equity{text-align:left;margin:10px 0 14px;padding-left:1.1em;font-size:13px;line-height:1.55}
|
||||
.uc-vip-level-card .pay-vip{display:inline-block;margin-top:4px}
|
||||
.uc-vip-assets-entry{gap:12px}
|
||||
.jb-vip1{background:linear-gradient(90deg,#f6c453,#f39c12);color:#3b2a00;border:0}
|
||||
.jb-vip2{background:linear-gradient(90deg,#8e44ad,#3f51b5);color:#fff;border:0}
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* AKSRT VIP pay modal — zibll .pay-vip parity.
|
||||
* Loads modal HTML from GET /aksrt/v1/pay/vip/modal then purchases via POST /pay/vip.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function restBase() {
|
||||
var aksrt = window.aksrtData || window.aksrt || {};
|
||||
var siteUrl = aksrt.siteUrl || aksrt.homeUrl || (window.location.origin + "/");
|
||||
if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") {
|
||||
siteUrl = "https:" + siteUrl.substring(5);
|
||||
}
|
||||
if (aksrt.restUrl) {
|
||||
return String(aksrt.restUrl).replace(/\/$/, "");
|
||||
}
|
||||
return siteUrl.replace(/\/$/, "") + "/wp-json/aksrt/v1";
|
||||
}
|
||||
|
||||
function nonceHeaders() {
|
||||
var aksrt = window.aksrtData || window.aksrt || {};
|
||||
var n = aksrt.restNonce || aksrt.nonce || "";
|
||||
return n ? { "X-WP-Nonce": n } : {};
|
||||
}
|
||||
|
||||
function api(path, options) {
|
||||
var merged = Object.assign(
|
||||
{
|
||||
credentials: "same-origin",
|
||||
headers: Object.assign({ "Content-Type": "application/json" }, nonceHeaders()),
|
||||
},
|
||||
options || {}
|
||||
);
|
||||
return fetch(restBase() + path, merged).then(function (r) {
|
||||
return r
|
||||
.json()
|
||||
.catch(function () {
|
||||
return { message: "Invalid JSON (HTTP " + r.status + ")" };
|
||||
})
|
||||
.then(function (j) {
|
||||
return { ok: r.ok, status: r.status, json: j };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function ensureModalShell() {
|
||||
var el = document.getElementById("modal_pay_uservip");
|
||||
if (el) return el;
|
||||
el = document.createElement("div");
|
||||
el.id = "modal_pay_uservip";
|
||||
el.className = "aksrt-vip-modal-overlay";
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
el.innerHTML =
|
||||
'<div class="aksrt-vip-modal-dialog" role="dialog" aria-modal="true">' +
|
||||
'<div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body></div></div>';
|
||||
document.body.appendChild(el);
|
||||
el.addEventListener("click", function (e) {
|
||||
if (e.target === el || e.target.closest(".aksrt-vip-modal-close")) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") closeModal();
|
||||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
var el = ensureModalShell();
|
||||
el.classList.add("is-open");
|
||||
el.setAttribute("aria-hidden", "false");
|
||||
document.body.classList.add("aksrt-vip-modal-open");
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
var el = document.getElementById("modal_pay_uservip");
|
||||
if (!el) return;
|
||||
el.classList.remove("is-open");
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
document.body.classList.remove("aksrt-vip-modal-open");
|
||||
}
|
||||
|
||||
function bindModalUi(root) {
|
||||
if (!root || root.dataset.aksrtVipBound === "1") return;
|
||||
root.dataset.aksrtVipBound = "1";
|
||||
|
||||
root.addEventListener("click", function (e) {
|
||||
var tab = e.target.closest("[data-vip-tab]");
|
||||
if (tab) {
|
||||
e.preventDefault();
|
||||
var id = tab.getAttribute("data-vip-tab");
|
||||
root.querySelectorAll(".aksrt-vip-tab").forEach(function (a) {
|
||||
a.parentElement && a.parentElement.classList.remove("active");
|
||||
});
|
||||
tab.parentElement && tab.parentElement.classList.add("active");
|
||||
root.querySelectorAll(".aksrt-vip-pane").forEach(function (p) {
|
||||
var on = p.id === id;
|
||||
p.hidden = !on;
|
||||
p.classList.toggle("active", on);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var submit = e.target.closest("[data-vip-submit]");
|
||||
if (submit) {
|
||||
e.preventDefault();
|
||||
purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function selectedMethod(root) {
|
||||
var fixed = root.querySelector("[data-aksrt-vip-method-fixed]");
|
||||
if (fixed && fixed.value) return fixed.value;
|
||||
var checked = root.querySelector('input[name="aksrt_vip_pay_method"]:checked');
|
||||
if (checked) return checked.value;
|
||||
var first = root.querySelector('input[name="aksrt_vip_pay_method"]');
|
||||
return first ? first.value : "balance";
|
||||
}
|
||||
|
||||
function selectedPackage(root) {
|
||||
var checked = root.querySelector('input[name="vip_product_id"]:checked');
|
||||
return checked ? parseInt(checked.value, 10) : 0;
|
||||
}
|
||||
|
||||
function setTip(root, text, isError) {
|
||||
var tip = root.querySelector("[data-aksrt-vip-tip]");
|
||||
if (!tip) return;
|
||||
tip.textContent = text || "";
|
||||
tip.classList.toggle("is-error", !!isError);
|
||||
}
|
||||
|
||||
function pollOrder(orderNumber) {
|
||||
if (!orderNumber) return;
|
||||
var tries = 0;
|
||||
var timer = setInterval(function () {
|
||||
tries += 1;
|
||||
api("/pay/content/order/" + encodeURIComponent(orderNumber))
|
||||
.then(function (r) {
|
||||
if (r.ok && r.json && r.json.paid) {
|
||||
clearInterval(timer);
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
if (tries >= 40) clearInterval(timer);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function purchaseFromPane(pane, btn) {
|
||||
var packageId = selectedPackage(pane);
|
||||
if (!packageId) {
|
||||
setTip(pane, "请选择套餐", true);
|
||||
return;
|
||||
}
|
||||
if (btn.disabled) return;
|
||||
btn.disabled = true;
|
||||
btn.classList.add("is-loading");
|
||||
setTip(pane, "正在创建订单…");
|
||||
|
||||
api("/pay/vip", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
package_id: packageId,
|
||||
payment_method: selectedMethod(pane),
|
||||
}),
|
||||
})
|
||||
.then(function (r) {
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("is-loading");
|
||||
if (!r.ok || (r.json && r.json.code && !r.json.success && r.json.success !== true)) {
|
||||
var msg = (r.json && r.json.message) || "开通失败";
|
||||
if (r.status === 401) {
|
||||
msg = "请先登录";
|
||||
document.querySelector(".signin-loader") && document.querySelector(".signin-loader").click();
|
||||
}
|
||||
setTip(pane, msg, true);
|
||||
return;
|
||||
}
|
||||
var data = r.json && r.json.data ? r.json.data : r.json;
|
||||
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
||||
if (data.payment_url) window.open(data.payment_url, "_blank");
|
||||
setTip(pane, data.message || "请完成支付,支付成功后将自动刷新。");
|
||||
if (data.order_number) pollOrder(data.order_number);
|
||||
return;
|
||||
}
|
||||
setTip(pane, (data && data.message) || "开通成功,正在刷新…");
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 400);
|
||||
})
|
||||
.catch(function () {
|
||||
btn.disabled = false;
|
||||
btn.classList.remove("is-loading");
|
||||
setTip(pane, "网络错误,请重试", true);
|
||||
});
|
||||
}
|
||||
|
||||
var loading = false;
|
||||
|
||||
function vipPay(trigger) {
|
||||
var level = 1;
|
||||
if (trigger) {
|
||||
level = parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1;
|
||||
}
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
var shell = ensureModalShell();
|
||||
var body = shell.querySelector("[data-aksrt-vip-modal-body]");
|
||||
if (body) body.innerHTML = '<div class="text-center padding-20 muted-2-color">加载中…</div>';
|
||||
openModal();
|
||||
|
||||
api("/pay/vip/modal?vip_level=" + encodeURIComponent(String(level)))
|
||||
.then(function (r) {
|
||||
loading = false;
|
||||
if (r.status === 401) {
|
||||
closeModal();
|
||||
var login = document.querySelector(".signin-loader");
|
||||
if (login) login.click();
|
||||
return;
|
||||
}
|
||||
if (!r.ok || !r.json || !r.json.html) {
|
||||
if (body) {
|
||||
body.innerHTML =
|
||||
'<div class="text-center padding-20 c-red">' +
|
||||
((r.json && r.json.message) || "加载失败") +
|
||||
"</div>";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (body) {
|
||||
body.innerHTML = r.json.html;
|
||||
bindModalUi(body);
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
loading = false;
|
||||
if (body) {
|
||||
body.innerHTML = '<div class="text-center padding-20 c-red">网络错误</div>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onDocClick(e) {
|
||||
var t = e.target.closest(".pay-vip");
|
||||
if (!t) return;
|
||||
// Allow real links without intercept if they point elsewhere and lack pay-vip intent.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
vipPay(t);
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.addEventListener("click", onDocClick, true);
|
||||
}
|
||||
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") {
|
||||
window.AksrtFrontend.register("aksrt-pay-vip", init);
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
window.aksrtVipPay = vipPay;
|
||||
})();
|
||||
+4
-4
@@ -276,8 +276,8 @@ if (!empty($aksrt_bg_styles)) {
|
||||
?>
|
||||
|
||||
<?php if (in_array('pc_nav', $aksrt_theme_toggle_positions, true)) : ?>
|
||||
<button type="button" class="btn-outline theme-toggle-desktop" id="theme-toggle-desktop" title="<?php esc_attr_e('切换亮暗色', 'aksrt'); ?>">
|
||||
<i class="fas fa-adjust" aria-hidden="true"></i>
|
||||
<button type="button" class="btn-outline theme-toggle-desktop toggle-theme toggle-radius" id="theme-toggle-desktop" title="<?php esc_attr_e('切换亮暗色', 'aksrt'); ?>">
|
||||
<i class="fas fa-adjust fa-toggle-theme" aria-hidden="true"></i>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -513,8 +513,8 @@ if (!empty($aksrt_bg_styles)) {
|
||||
|
||||
<?php if (in_array('m_menu', $aksrt_theme_toggle_positions, true)) : ?>
|
||||
<div class="nav-drawer__theme-toggle">
|
||||
<button type="button" class="btn-outline theme-toggle-mobile" id="theme-toggle-mobile" title="<?php esc_attr_e('切换亮暗色', 'aksrt'); ?>">
|
||||
<i class="fas fa-adjust" aria-hidden="true"></i>
|
||||
<button type="button" class="btn-outline theme-toggle-mobile toggle-theme toggle-radius" id="theme-toggle-mobile" title="<?php esc_attr_e('切换亮暗色', 'aksrt'); ?>">
|
||||
<i class="fas fa-adjust fa-toggle-theme" aria-hidden="true"></i>
|
||||
<span><?php esc_html_e('切换主题', 'aksrt'); ?></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -161,13 +161,12 @@ if (!function_exists('aksrt_render_header_menu_buttons')) :
|
||||
}
|
||||
}
|
||||
|
||||
// VIP open CTA for guests / non-VIP.
|
||||
$user_id = get_current_user_id();
|
||||
$vip_url = function_exists('aksrt_get_vip_purchase_url') ? aksrt_get_vip_purchase_url() : (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#');
|
||||
$show_vip = true;
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']) || !empty($vs['vip1_enabled']) || !empty($vs['vip2_enabled']);
|
||||
// VIP open CTA for guests / non-VIP (zibll: .pay-vip modal).
|
||||
$user_id = get_current_user_id();
|
||||
$show_vip = false;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']);
|
||||
}
|
||||
if ($show_vip) {
|
||||
$is_vip = false;
|
||||
@@ -175,13 +174,22 @@ if (!function_exists('aksrt_render_header_menu_buttons')) :
|
||||
$is_vip = (int) aksrt_get_user_vip_level($user_id) > 0;
|
||||
}
|
||||
if (!$is_vip) {
|
||||
$cls = $user_id ? 'pay-vip' : 'signin-loader';
|
||||
$cls = $user_id > 0 ? 'pay-vip' : 'signin-loader';
|
||||
$hover = '';
|
||||
if (class_exists('\AKSRT\Content\VipService')) {
|
||||
$svc = \AKSRT\Content\VipService::get_instance();
|
||||
if (method_exists($svc, 'render_vip_card')) {
|
||||
$cards = $svc->render_vip_card(1, $user_id) . $svc->render_vip_card(2, $user_id);
|
||||
if ($cards !== '') {
|
||||
$hover = '<div class="sub-menu hover-show-con sub-vip-card">' . $cards . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
$html .= '<div class="navbar-form navbar-right navbar-vip hover-show inline-block">';
|
||||
$html .= '<a class="but jb-red radius payvip-icon ml10 ' . esc_attr($cls) . '" href="' . ($user_id ? esc_url($vip_url) : 'javascript:;') . '">';
|
||||
$html .= '<a class="but jb-red radius payvip-icon ml10 ' . esc_attr($cls) . '" href="javascript:;"' . ($user_id > 0 ? ' vip-level="1"' : '') . '>';
|
||||
$html .= '<i class="fa fa-diamond em12 mr3" aria-hidden="true"></i>' . esc_html__('开通会员', 'aksrt');
|
||||
$html .= '</a></div>';
|
||||
}
|
||||
}
|
||||
$html .= '</a>' . $hover . '</div>';
|
||||
} }
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ if (!function_exists('aksrt_header_user_wallet_mini')) :
|
||||
if (class_exists('\AKSRT\Credits\Balance')) {
|
||||
$wallet = (float) \AKSRT\Credits\Balance::balance($user_id);
|
||||
}
|
||||
$center = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('assets') : '#';
|
||||
$center = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('credits') : '#';
|
||||
|
||||
$html = '<a class="aksrt-header-wallet flex ac jsb" href="' . esc_url($center) . '">';
|
||||
$html .= '<span class="aksrt-header-wallet__item"><i class="fas fa-coins" aria-hidden="true"></i> '
|
||||
@@ -116,29 +116,44 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_header_payvip_bar')) :
|
||||
/**
|
||||
* VIP status / open CTA bar.
|
||||
* VIP status / open CTA bar (zibll zib_get_header_payvip_icon parity).
|
||||
*/
|
||||
function aksrt_header_payvip_bar(int $user_id = 0): string {
|
||||
$user_id = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
if ($user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
$badge = function_exists('aksrt_render_vip_badge') ? aksrt_render_vip_badge($user_id, 'md') : '';
|
||||
if ($badge !== '') {
|
||||
return '<div class="mt10 em09 aksrt-header-payvip">' . $badge . '</div>';
|
||||
}
|
||||
// Guest-style open VIP for non-members if VIP feature on.
|
||||
$vip_url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#';
|
||||
$enabled = true;
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_vip_settings();
|
||||
$enabled = !empty($vs['enabled']) || !empty($vs['vip1_enabled']) || !empty($vs['vip2_enabled']);
|
||||
}
|
||||
if (!$enabled) {
|
||||
|
||||
$vip = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_payment_vip_settings()
|
||||
: ['enabled' => false, 'levels' => []];
|
||||
if (empty($vip['enabled'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($user_id) : 0;
|
||||
if ($level > 0) {
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
$name = (string) ($levels[$level]['name'] ?? ($level === 2 ? __('二级会员', 'aksrt') : __('一级会员', 'aksrt')));
|
||||
$exp_label = '';
|
||||
if (function_exists('aksrt_get_vip_info')) {
|
||||
$info = aksrt_get_vip_info($user_id);
|
||||
$exp_label = (string) ($info['expires_label'] ?? '');
|
||||
}
|
||||
$html = '<div class="mt10 em09 aksrt-header-payvip">';
|
||||
$html .= '<span class="radius4 payvip-icon btn-block text-center vipbg-v2 level-' . (int) $level . '">';
|
||||
$html .= '<i class="fa fa-diamond em12 mr6" aria-hidden="true"></i>';
|
||||
$html .= '<span>' . esc_html($name) . '</span>';
|
||||
if ($exp_label !== '') {
|
||||
$html .= '<span class="ml10 badg jb-yellow vip-expdate-tag"><i class="fa fa-clock-o mr3" aria-hidden="true"></i>'
|
||||
. esc_html($exp_label) . '</span>';
|
||||
}
|
||||
$html .= '</span></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
return '<div class="mt10 em09 aksrt-header-payvip">'
|
||||
. '<a class="but jb-red radius4 payvip-icon btn-block" href="' . esc_url($vip_url) . '">'
|
||||
. '<a class="pay-vip but radius4 payvip-icon btn-block" href="javascript:;" vip-level="1">'
|
||||
. '<i class="fa fa-diamond mr6" aria-hidden="true"></i>'
|
||||
. esc_html__('开通会员 尊享会员权益', 'aksrt')
|
||||
. '</a></div>';
|
||||
|
||||
@@ -57,10 +57,10 @@ if (!function_exists('aksrt_get_float_pay_vip_html')) :
|
||||
* Floating VIP open button.
|
||||
*/
|
||||
function aksrt_get_float_pay_vip_html(array $item = []): string {
|
||||
$show_vip = true;
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_vip_settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']) || !empty($vs['vip1_enabled']) || !empty($vs['vip2_enabled']);
|
||||
$show_vip = false;
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vs = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$show_vip = !empty($vs['enabled']);
|
||||
}
|
||||
if (!$show_vip) {
|
||||
return '';
|
||||
@@ -74,13 +74,11 @@ if (!function_exists('aksrt_get_float_pay_vip_html')) :
|
||||
if ($level >= 2) {
|
||||
return '';
|
||||
}
|
||||
$vip_url = function_exists('aksrt_get_vip_purchase_url')
|
||||
? aksrt_get_vip_purchase_url()
|
||||
: (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#');
|
||||
$title = $level === 1 ? __('升级会员', 'aksrt') : __('开通会员', 'aksrt');
|
||||
$icon = !empty($item['icon']) ? (string) $item['icon'] : 'fa fa-diamond';
|
||||
$cls = $user_id > 0 ? 'float-btn pay-vip' : 'float-btn signin-loader';
|
||||
$href = $user_id > 0 ? $vip_url : 'javascript:;';
|
||||
// Modal path: .pay-vip opens VIP modal (Batch 2+); guests use signin-loader.
|
||||
$href = 'javascript:;';
|
||||
$style = [];
|
||||
if (!empty($item['color'])) {
|
||||
$style[] = '--this-color:' . esc_attr((string) $item['color']);
|
||||
@@ -89,7 +87,8 @@ if (!function_exists('aksrt_get_float_pay_vip_html')) :
|
||||
$style[] = '--this-bg:' . esc_attr((string) $item['bg']);
|
||||
}
|
||||
$style_attr = $style ? ' style="' . implode(';', $style) . '"' : '';
|
||||
return '<a class="' . esc_attr($cls) . '" href="' . esc_url($href) . '" title="' . esc_attr($title) . '"' . $style_attr . '>'
|
||||
$vip_level_attr = $user_id > 0 ? ' vip-level="' . esc_attr((string) max(1, min(2, $level + 1))) . '"' : '';
|
||||
return '<a class="' . esc_attr($cls) . '" href="' . esc_attr($href) . '" title="' . esc_attr($title) . '"' . $style_attr . $vip_level_attr . '>'
|
||||
. '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i>'
|
||||
. '</a>';
|
||||
}
|
||||
@@ -152,28 +151,3 @@ if (!function_exists('aksrt_get_win_runtime_config')) :
|
||||
];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_print_win_var')) :
|
||||
/**
|
||||
* Echo window._win + alias window.aksrtData bridge.
|
||||
*/
|
||||
function aksrt_print_win_var(): void {
|
||||
static $done = false;
|
||||
if ($done) {
|
||||
return;
|
||||
}
|
||||
$done = true;
|
||||
$cfg = aksrt_get_win_runtime_config();
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<script type="text/javascript">window._win=' . wp_json_encode($cfg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . ';'
|
||||
. 'window.aksrtData=window.aksrtData||{};'
|
||||
. 'window.aksrtData.ajaxUrl=window.aksrtData.ajaxUrl||window._win.ajax_url;'
|
||||
. 'window.aksrtData.siteUrl=window.aksrtData.siteUrl||window._win.www;'
|
||||
. 'window.aksrtData.themeUri=window.aksrtData.themeUri||window._win.uri;'
|
||||
. 'window.aksrtData.restUrl=window.aksrtData.restUrl||window._win.rest_url;'
|
||||
. 'window.aksrtData.restNonce=window.aksrtData.restNonce||window._win.rest_nonce;'
|
||||
. 'window.aksrtData.nonce=window.aksrtData.nonce||window._win.nonce;'
|
||||
. 'window.aksrtData.imgbox=window._win;'
|
||||
. '</script>' . "\n";
|
||||
}
|
||||
endif;
|
||||
|
||||
@@ -169,6 +169,7 @@ if (!function_exists('aksrt_capture_user_center_view')) {
|
||||
'comments' => 'comments',
|
||||
'favorites' => 'favorites',
|
||||
'credits' => 'credits',
|
||||
'vip' => 'vip',
|
||||
'settings' => 'settings',
|
||||
'header' => 'header',
|
||||
'sidebar' => 'sidebar',
|
||||
@@ -193,7 +194,7 @@ if (!function_exists('aksrt_capture_user_center_panel')) {
|
||||
* Capture a built-in or registered user-center panel body.
|
||||
*/
|
||||
function aksrt_capture_user_center_panel(string $tab_key, array $tab, array $context): string {
|
||||
$built_in_tabs = ['profile', 'posts', 'comments', 'favorites', 'credits', 'settings'];
|
||||
$built_in_tabs = ['profile', 'posts', 'comments', 'favorites', 'credits', 'vip', 'settings'];
|
||||
if (in_array($tab_key, $built_in_tabs, true)) {
|
||||
return aksrt_capture_user_center_view($tab_key, $context);
|
||||
}
|
||||
@@ -296,10 +297,13 @@ if (!function_exists('aksrt_user_center_resolve_tabs')) {
|
||||
$keys = array_keys($tabs);
|
||||
$default = in_array('profile', $keys, true) ? 'profile' : ($keys[0] ?? 'profile');
|
||||
$active = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : $default;
|
||||
if ('signin' === $active) {
|
||||
if ('signin' === $active || 'assets' === $active) {
|
||||
$active = 'credits';
|
||||
} elseif ('invite' === $active) {
|
||||
$active = 'settings';
|
||||
} elseif ('vip' === $active) {
|
||||
// Prefer dedicated vip tab when registered; else credits (VIP shop).
|
||||
$active = in_array('vip', $keys, true) ? 'vip' : 'credits';
|
||||
}
|
||||
if (!in_array($active, $keys, true)) {
|
||||
$active = $default;
|
||||
@@ -353,7 +357,13 @@ if (!function_exists('aksrt_user_center_runtime_config')) {
|
||||
'userId' => $user_id,
|
||||
'defaultTab' => $default_tab,
|
||||
'activeTab' => $active_tab,
|
||||
'tabAliases' => ['signin' => 'credits', 'invite' => 'settings'],
|
||||
'tabAliases' => [
|
||||
'signin' => 'credits',
|
||||
'invite' => 'settings',
|
||||
'assets' => 'credits',
|
||||
// vip → vip when panel exists; SPA may still map missing keys via server resolve.
|
||||
'vip' => 'vip',
|
||||
],
|
||||
'tabUrls' => $tab_urls,
|
||||
'i18n' => [
|
||||
'contactUrlPlaceholder' => __('链接 URL', 'aksrt'),
|
||||
|
||||
+42
-15
@@ -65,7 +65,8 @@ function aksrt_get_user_vip_level(int $user_id = 0): int {
|
||||
|
||||
if ($meta_level > 0) {
|
||||
if ($expires > 0 && $expires < time()) {
|
||||
// 过期:lazy 清 level,与 capabilities 读 meta 对齐。
|
||||
// 过期:lazy 清 level,记录上次等级(zibll vip_level_expired)。
|
||||
update_user_meta($user_id, 'aksrt_vip_level_expired', min(2, max(1, $meta_level)));
|
||||
update_user_meta($user_id, 'aksrt_vip_level', 0);
|
||||
$candidate = 0;
|
||||
} else {
|
||||
@@ -82,16 +83,36 @@ function aksrt_get_user_vip_level(int $user_id = 0): int {
|
||||
: null;
|
||||
|
||||
if (is_array($valid_order) && !empty($valid_order)) {
|
||||
$order_expires = trim((string) ($valid_order['expires_at'] ?? ''));
|
||||
$order_expired = false;
|
||||
if ($order_expires !== '') {
|
||||
$ts = strtotime($order_expires);
|
||||
if ($ts !== false && $ts < time()) {
|
||||
$order_expired = true;
|
||||
// Order.expires_at is payment checkout deadline, not VIP membership expiry.
|
||||
// Prefer note/meta_payload level + aksrt_vip_expires if present.
|
||||
$meta = [];
|
||||
if (!empty($valid_order['meta_payload']) && is_array($valid_order['meta_payload'])) {
|
||||
$meta = $valid_order['meta_payload'];
|
||||
} elseif (!empty($valid_order['note']) && is_string($valid_order['note'])) {
|
||||
$decoded = json_decode($valid_order['note'], true);
|
||||
if (is_array($decoded)) {
|
||||
$meta = $decoded;
|
||||
}
|
||||
}
|
||||
if (!$order_expired) {
|
||||
// 套餐无 level 列:有效 VIP 订单至少视为 VIP1。
|
||||
|
||||
$order_level = (int) ($meta['level'] ?? 0);
|
||||
if ($order_level <= 0 && !empty($valid_order['product_id']) && class_exists('\AKSRT\Content\VipService')) {
|
||||
// package_id encodes level: decode if possible via service helpers.
|
||||
$pkg_id = (int) $valid_order['product_id'];
|
||||
// Encode scheme: level * 1000 + index (see VipServicePackages).
|
||||
if ($pkg_id >= 1000) {
|
||||
$order_level = (int) floor($pkg_id / 1000);
|
||||
} elseif ($pkg_id > 0) {
|
||||
$order_level = 1;
|
||||
}
|
||||
}
|
||||
$order_level = max(0, min(2, $order_level));
|
||||
|
||||
// Membership still valid if user meta expires is in future or permanent (0 with prior grant).
|
||||
// Without restored meta, treat paid VIP order as at least the ordered level.
|
||||
if ($order_level > 0) {
|
||||
$candidate = $order_level;
|
||||
} else {
|
||||
$candidate = 1;
|
||||
}
|
||||
}
|
||||
@@ -140,12 +161,16 @@ function aksrt_is_vip2(int $user_id = 0): bool {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_vip_badge_text(): string {
|
||||
function aksrt_get_vip_badge_text(int $user_id = 0): string {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return 'VIP';
|
||||
}
|
||||
|
||||
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
$level = $user_id > 0 ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
if ($level >= 1 && !empty($vip_settings['levels'][$level]['name'])) {
|
||||
return sanitize_text_field((string) $vip_settings['levels'][$level]['name']);
|
||||
}
|
||||
return sanitize_text_field($vip_settings['badge_text'] ?? 'VIP');
|
||||
}
|
||||
|
||||
@@ -323,10 +348,12 @@ function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
$badge_text = aksrt_get_vip_badge_text();
|
||||
$level = aksrt_get_user_vip_level($user_id);
|
||||
$badge_text = aksrt_get_vip_badge_text($user_id);
|
||||
$size_class = 'aksrt-vip-badge--' . $size;
|
||||
$level_class = $level >= 2 ? 'aksrt-vip-badge--vip2' : 'aksrt-vip-badge--vip1';
|
||||
|
||||
return '<span class="aksrt-vip-badge aksrt-vip-badge--gold ' . esc_attr($size_class) . '"><i class="fas fa-crown"></i> ' . esc_html($badge_text) . '</span>';
|
||||
return '<span class="aksrt-vip-badge aksrt-vip-badge--gold ' . esc_attr($size_class . ' ' . $level_class) . '"><i class="fas fa-crown"></i> ' . esc_html($badge_text) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,7 +365,7 @@ function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string {
|
||||
function aksrt_get_vip_info(int $user_id = 0): array {
|
||||
$level = aksrt_get_user_vip_level($user_id);
|
||||
$is_vip = $level >= 1;
|
||||
$badge_text = aksrt_get_vip_badge_text();
|
||||
$badge_text = aksrt_get_vip_badge_text($user_id);
|
||||
$expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0;
|
||||
$expires_label = __('未开通', 'aksrt');
|
||||
if ($level > 0) {
|
||||
@@ -380,10 +407,10 @@ function aksrt_get_vip_info(int $user_id = 0): array {
|
||||
if (!function_exists('aksrt_get_vip_purchase_url')) {
|
||||
function aksrt_get_vip_purchase_url(): string {
|
||||
if (function_exists('aksrt_get_user_center_tab_url')) {
|
||||
return aksrt_get_user_center_tab_url('credits');
|
||||
return aksrt_get_user_center_tab_url('vip');
|
||||
}
|
||||
if (function_exists('aksrt_get_user_center_url')) {
|
||||
return aksrt_get_user_center_url();
|
||||
return aksrt_get_user_center_url('vip');
|
||||
}
|
||||
return home_url('/');
|
||||
}
|
||||
|
||||
@@ -39,7 +39,15 @@ if ($show_wallet || $show_credits) :
|
||||
endif;
|
||||
|
||||
if (!empty($show_vip)) {
|
||||
require $dir . '/credits-vip.php';
|
||||
// Slim entry to dedicated VIP tab (full shop lives under tab=vip).
|
||||
$vip_url = function_exists('aksrt_get_user_center_tab_url')
|
||||
? aksrt_get_user_center_tab_url('vip')
|
||||
: '#';
|
||||
echo '<div class="zib-widget flex ac jsb padding-15 mb15 uc-vip-assets-entry">';
|
||||
echo '<div class="flex ac gap10"><i class="fas fa-crown c-yellow"></i><div><div class="font-bold">' . esc_html__('我的会员', 'aksrt') . '</div>';
|
||||
echo '<div class="muted-2-color em09">' . esc_html__('查看权益、续费与升级', 'aksrt') . '</div></div></div>';
|
||||
echo '<a class="but jb-yellow radius" href="' . esc_url($vip_url) . '">' . esc_html__('进入会员中心', 'aksrt') . '</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
if ($show_credits) {
|
||||
require $dir . '/credits-signin.php';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* User center VIP panel entry (maps tab=vip).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$context = is_array($args['context'] ?? null) ? $args['context'] : [];
|
||||
extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
require __DIR__ . '/vip-tab.php';
|
||||
@@ -59,7 +59,7 @@ if (empty($orders_url) && empty($show_vip) && empty($show_credit_tabs) && empty(
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($show_vip)) : ?>
|
||||
<a class="uc-sidebar__btn<?php echo (!empty($active_tab) && $active_tab === 'credits') ? ' is-active' : ''; ?>" href="<?php echo esc_url(aksrt_get_user_center_tab_url('credits')); ?>" data-tab="credits">
|
||||
<a class="uc-sidebar__btn payvip-icon<?php echo (!empty($active_tab) && $active_tab === 'vip') ? ' is-active' : ''; ?>" href="<?php echo esc_url(aksrt_get_user_center_tab_url('vip')); ?>" data-tab="vip">
|
||||
<i class="fas fa-crown" aria-hidden="true"></i>
|
||||
<span><?php esc_html_e('VIP 会员', 'aksrt'); ?></span>
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* User center VIP tab (zibll 我的会员 1:1 structure).
|
||||
*
|
||||
* Expects extracted $context vars via profile-vip.php / capture path.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$vip_settings = is_array($vip_settings ?? null) ? $vip_settings : [];
|
||||
if (empty($vip_settings['enabled']) && class_exists('\AKSRT\Settings\Settings')) {
|
||||
$vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
|
||||
}
|
||||
if (empty($vip_settings['enabled'])) {
|
||||
echo '<div class="zib-widget padding-20 muted-2-color">' . esc_html__('会员功能未启用。', 'aksrt') . '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = (int) ($user_id ?? get_current_user_id());
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
$expires = (int) get_user_meta($user_id, 'aksrt_vip_expires', true);
|
||||
$expired_level = (int) get_user_meta($user_id, 'aksrt_vip_level_expired', true);
|
||||
$levels = is_array($vip_settings['levels'] ?? null) ? $vip_settings['levels'] : [];
|
||||
$desc_top = (string) ($vip_settings['desc'] ?? '');
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
$display = $user ? $user->display_name : '';
|
||||
$avatar = get_avatar($user_id, 56);
|
||||
|
||||
// Status header box.
|
||||
$status_html = '';
|
||||
$btn_type = 'pay';
|
||||
if ($level > 0) {
|
||||
$lv_name = (string) ($levels[$level]['name'] ?? ('VIP' . $level));
|
||||
$lv_desc = (string) ($levels[$level]['desc'] ?? '');
|
||||
if ($expires <= 0) {
|
||||
$badge = '<span class="badg c-green p2-10 em09"><i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html__('永久会员', 'aksrt') . '</span>';
|
||||
if ($level < 2) {
|
||||
$btn_type = 'upgrade';
|
||||
}
|
||||
} else {
|
||||
$badge = '<span class="badg c-yellow p2-10 em09"><i class="fa fa-clock-o mr3" aria-hidden="true"></i>'
|
||||
. esc_html(sprintf(__('到期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires))) . '</span>';
|
||||
$btn_type = 'renew_' . $level;
|
||||
}
|
||||
$status_html = '<div class="flex xx gap6">'
|
||||
. '<div class="em16 mr10 flex ac"><i class="fas fa-crown mr6"></i><div class="font-bold ml6">' . esc_html($lv_name) . '</div></div>'
|
||||
. $badge
|
||||
. ($lv_desc !== '' ? '<div class="muted-color em09">' . esc_html($lv_desc) . '</div>' : '')
|
||||
. '</div>';
|
||||
} elseif ($expired_level > 0) {
|
||||
$exp_label = $expires > 0
|
||||
? sprintf(__('过期时间:%s', 'aksrt'), wp_date('Y年m月d日', $expires))
|
||||
: __('已过期', 'aksrt');
|
||||
$status_html = '<div class="flex xx gap6">'
|
||||
. '<div class="font-bold c-red">' . esc_html__('会员已过期', 'aksrt') . '</div>'
|
||||
. '<div class="badg c-red em09"><i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($exp_label) . '</div>'
|
||||
. ($desc_top !== '' ? '<div class="muted-color em09">' . esc_html($desc_top) . '</div>' : '')
|
||||
. '</div>';
|
||||
$btn_type = 'pay';
|
||||
} else {
|
||||
$status_html = '<div class="flex xx gap6">'
|
||||
. '<div class="font-bold">' . esc_html__('开通会员', 'aksrt') . '</div>'
|
||||
. ($desc_top !== '' ? '<div class="muted-color em09">' . esc_html($desc_top) . '</div>' : '<div class="muted-color em09">' . esc_html__('开通会员,尊享专属特权', 'aksrt') . '</div>')
|
||||
. '</div>';
|
||||
$btn_type = 'pay';
|
||||
}
|
||||
|
||||
// Level cards.
|
||||
$cards = '';
|
||||
foreach ([1, 2] as $lv) {
|
||||
if (empty($levels[$lv]['enabled'])) {
|
||||
continue;
|
||||
}
|
||||
$lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
|
||||
$lv_desc = (string) ($levels[$lv]['desc'] ?? '');
|
||||
$equity = (string) ($levels[$lv]['equity'] ?? '');
|
||||
$cta = sprintf(__('开通%s', 'aksrt'), $lv_name);
|
||||
$vip_attr = $lv;
|
||||
if (str_starts_with($btn_type, 'renew_')) {
|
||||
$renew_lv = (int) substr($btn_type, 6);
|
||||
if ($lv === $renew_lv) {
|
||||
$cta = sprintf(__('续费%s', 'aksrt'), $lv_name);
|
||||
} elseif ($lv > $renew_lv) {
|
||||
$cta = sprintf(__('升级%s', 'aksrt'), $lv_name);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} elseif ($btn_type === 'upgrade') {
|
||||
if ($lv <= $level) {
|
||||
continue;
|
||||
}
|
||||
$cta = sprintf(__('升级%s', 'aksrt'), $lv_name);
|
||||
$vip_attr = 2;
|
||||
} elseif ($level > 0 && $lv < $level) {
|
||||
continue;
|
||||
} elseif ($level > 0 && $lv === $level) {
|
||||
$cta = sprintf(__('续费%s', 'aksrt'), $lv_name);
|
||||
}
|
||||
|
||||
$cards .= '<div class="zib-widget text-center pay-vip-info flex1 level-' . $lv . ' uc-vip-level-card">';
|
||||
$cards .= '<div class="abs-center vip-baicon"><i class="fa fa-diamond" aria-hidden="true"></i></div>';
|
||||
$cards .= '<div class="relative">';
|
||||
$cards .= '<div class="v-iconbox mb10 flex jc gap10"><div class="v-icon flex jc"><i class="fas fa-crown"></i></div>';
|
||||
$cards .= '<div class="v-info text-left"><div class="v-name font-bold">' . esc_html($lv_name) . '</div>';
|
||||
if ($lv_desc !== '') {
|
||||
$cards .= '<div class="v-desc em09 muted-2-color">' . esc_html($lv_desc) . '</div>';
|
||||
}
|
||||
$cards .= '</div></div>';
|
||||
if ($equity !== '') {
|
||||
$cards .= '<ul class="v-equity text-left">' . $equity . '</ul>';
|
||||
}
|
||||
$cards .= '<a class="padding-lg but jb-vip' . $lv . ' radius pay-vip" href="javascript:;" vip-level="' . (int) $vip_attr . '">'
|
||||
. esc_html($cta) . '<span class="ml10"><i class="fa fa-chevron-circle-right" aria-hidden="true"></i></span></a>';
|
||||
$cards .= '</div></div>';
|
||||
}
|
||||
|
||||
// Points exchange (non-VIP only) — slim link; full shop stays optional under credits.
|
||||
$points_html = '';
|
||||
if ($level <= 0 && !empty($vip_settings['points_exchange']['enabled'])) {
|
||||
$points_html = '<div class="c-yellow zib-widget flex ac jsb uc-vip-points-link mt10">'
|
||||
. '<div class="flex1 em12"><i class="fas fa-coins mr6"></i>' . esc_html__('积分兑换会员', 'aksrt') . '</div>'
|
||||
. '<a class="badg radius c-yellow pay-vip" href="javascript:;" vip-level="1">' . esc_html__('立即兑换', 'aksrt')
|
||||
. '<i class="fa fa-chevron-circle-right ml6" aria-hidden="true"></i></a></div>';
|
||||
}
|
||||
?>
|
||||
<div class="uc-vip-tab" data-aksrt-vip-tab>
|
||||
<div class="zib-widget flex ac gap20 user-content-myvip overflow-hidden mb15">
|
||||
<div class="vip-img abs-right opacity3"><i class="fas fa-crown em3x" aria-hidden="true"></i></div>
|
||||
<div class="flex ac gap12 flex1">
|
||||
<div class="avatar-img"><?php echo $avatar; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<div class="flex1">
|
||||
<div class="font-bold mb3"><?php echo esc_html($display); ?></div>
|
||||
<?php echo $status_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap20 gap10-sm uc-vip-level-cards">
|
||||
<?php echo $cards; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
|
||||
<?php echo $points_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
|
||||
<?php if (!empty($vip_settings['more'])) : ?>
|
||||
<div class="muted-2-color em09 muted-box padding-10 mini-radius mt15">
|
||||
<?php echo $vip_settings['more']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -26,6 +26,17 @@ function aksrt_get_user_center_url(string $tab = ''): string {
|
||||
}
|
||||
|
||||
if ($tab !== '') {
|
||||
$tab = sanitize_key($tab);
|
||||
// Deep-link aliases used by header wallet / VIP CTA.
|
||||
$aliases = [
|
||||
'assets' => 'credits',
|
||||
'vip' => 'vip', // dedicated vip tab when registered; resolver falls back if missing
|
||||
'signin' => 'credits',
|
||||
'invite' => 'settings',
|
||||
];
|
||||
if (isset($aliases[$tab])) {
|
||||
$tab = $aliases[$tab];
|
||||
}
|
||||
$url = add_query_arg('tab', $tab, $url);
|
||||
}
|
||||
return $url;
|
||||
|
||||
@@ -100,18 +100,28 @@ class SchemaPayment {
|
||||
'vip' => [
|
||||
'key' => 'vip',
|
||||
'title' => 'VIP会员',
|
||||
'version' => '1.0.0',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '会员等级', 'fields' => [
|
||||
['key' => 'pay_user_vip_1_s', 'label' => '启用一级会员', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'pay_user_vip_1_name', 'label' => '一级会员名称', 'type' => 'input', 'default' => '一级会员', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_1_desc', 'label' => '一级会员一句话简介', 'type' => 'input', 'default' => '尊享一级会员特权', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_1_equity', 'label' => '一级会员权益 HTML', 'type' => 'textarea', 'default' => '<li>免费下载部分资源</li><li>会员专属身份标识</li><li>付费内容会员价</li>', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
|
||||
['key' => 'vip_1_img_icon', 'label' => '一级会员图标 URL', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_2_s', 'label' => '启用二级会员', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'pay_user_vip_2_name', 'label' => '二级会员名称', 'type' => 'input', 'default' => '二级会员', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_desc', 'label' => '会员说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'pay_user_vip_2_desc', 'label' => '二级会员一句话简介', 'type' => 'input', 'default' => '尊享二级会员特权', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_2_equity', 'label' => '二级会员权益 HTML', 'type' => 'textarea', 'default' => '<li>更高免费下载额度</li><li>更高等级身份标识</li><li>更多付费内容会员价</li>', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
|
||||
['key' => 'vip_2_img_icon', 'label' => '二级会员图标 URL', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
|
||||
['key' => 'pay_user_vip_desc', 'label' => '弹窗顶部会员说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'pay_user_vip_more', 'label' => '弹窗底部补充说明 HTML', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'packages', 'title' => '会员套餐', 'fields' => [
|
||||
['key' => 'payment_vip_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|月卡\n365|365|年卡", 'help' => '每行:现金价|天数|标题(天数为 0 表示永久)'],
|
||||
['key' => 'packages', 'title' => '分档套餐', 'fields' => [
|
||||
['key' => 'vip_1_product_s', 'label' => '允许购买一级会员', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]],
|
||||
['key' => 'payment_vip_1_products', 'label' => '一级会员套餐', 'type' => 'textarea', 'default' => "30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡", 'dependsOn' => ['key' => 'vip_1_product_s', 'value' => true], 'help' => '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久'],
|
||||
['key' => 'vip_2_product_s', 'label' => '允许购买二级会员', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]],
|
||||
['key' => 'payment_vip_2_products', 'label' => '二级会员套餐', 'type' => 'textarea', 'default' => "58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡", 'dependsOn' => ['key' => 'vip_2_product_s', 'value' => true], 'help' => '每行:执行价|原价|时长|单位(day|month)|标签|标题;时长 0=永久'],
|
||||
]],
|
||||
['key' => 'points_exchange', 'title' => '积分兑换会员', 'fields' => [
|
||||
['key' => 'pay_vip_points_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false],
|
||||
@@ -137,13 +147,13 @@ class SchemaPayment {
|
||||
['label' => '折扣价格', 'value' => 'discount'],
|
||||
['label' => '优惠立减', 'value' => 'reduce'],
|
||||
]],
|
||||
['key' => 'vip_renew_discount', 'label' => '续费折扣(%)', 'type' => 'number', 'default' => 8, 'min' => 1, 'max' => 100, 'dependsOn' => ['key' => 'vip_renew_price_type', 'value' => 'discount']],
|
||||
['key' => 'vip_renew_discount', 'label' => '续费折扣(折,如 8=八折)', 'type' => 'number', 'default' => 8, 'min' => 1, 'max' => 100, 'dependsOn' => ['key' => 'vip_renew_price_type', 'value' => 'discount']],
|
||||
['key' => 'vip_renew_reduce', 'label' => '续费减免金额', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 9999, 'dependsOn' => ['key' => 'vip_renew_price_type', 'value' => 'reduce']],
|
||||
]],
|
||||
['key' => 'upgrade', 'title' => '升级设置', 'fields' => [
|
||||
['key' => 'vip_upgrade', 'label' => '允许升级', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'vip_upgrade_desc', 'label' => '升级说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]],
|
||||
['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '每行一条。永久VIP1:permanent|价格|标题;剩余天:unit|每日单价|标题;跳转永久:jump|价格|标题;兼容:价格|天数|标题'],
|
||||
['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'textarea', 'default' => "unit|1|按剩余天数升级\njump|199|升级永久二级会员\npermanent|299|永久一级升永久二级", 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true], 'help' => '每行一条。永久VIP1:permanent|价格|标题;剩余天:unit|每日单价|标题;跳转永久:jump|价格|标题'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@ class VipService {
|
||||
use VipServiceRest;
|
||||
use VipServicePurchase;
|
||||
use VipServicePackages;
|
||||
use VipServiceModal;
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
/**
|
||||
* VIP modal + card HTML (zibll pay-vip modal / vip-card parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Content;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait VipServiceModal {
|
||||
|
||||
/**
|
||||
* Build pay / renew / upgrade modal HTML for a user.
|
||||
*/
|
||||
public function render_modal_html(int $user_id, int $prefer_level = 1): string {
|
||||
if ($user_id <= 0) {
|
||||
return '<div class="box-body payvip-modal text-center padding-20">'
|
||||
. esc_html__('请先登录后再开通会员。', 'aksrt')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$vip = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_payment_vip_settings()
|
||||
: ['enabled' => false];
|
||||
if (empty($vip['enabled'])) {
|
||||
return '<div class="box-body payvip-modal text-center padding-20">'
|
||||
. esc_html__('会员功能未启用。', 'aksrt')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$catalog = $this->get_catalog($user_id);
|
||||
$user_level = (int) ($catalog['user_level'] ?? 0);
|
||||
$prefer_level = max(1, min(2, $prefer_level));
|
||||
|
||||
if ($user_level > 0) {
|
||||
return $this->render_modal_viped($user_id, $catalog, $vip, $prefer_level);
|
||||
}
|
||||
return $this->render_modal_pay($user_id, $catalog, $vip, $prefer_level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mini hover card for header (zibll zibpay_get_vip_card).
|
||||
*/
|
||||
public function render_vip_card(int $level, int $user_id = 0): string {
|
||||
$level = max(1, min(2, $level));
|
||||
$vip = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_payment_vip_settings()
|
||||
: [];
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
if (empty($levels[$level]['enabled'])) {
|
||||
return '';
|
||||
}
|
||||
$name = (string) ($levels[$level]['name'] ?? ('VIP' . $level));
|
||||
$desc = (string) ($levels[$level]['desc'] ?? '');
|
||||
$equity = (string) ($levels[$level]['equity'] ?? '');
|
||||
$cls = $user_id > 0 ? 'pay-vip' : 'signin-loader';
|
||||
$html = '<div class="vip-card pointer level-' . $level . ' vip-theme' . $level . ' ' . esc_attr($cls) . '" vip-level="' . $level . '">';
|
||||
$html .= '<div class="abs-center vip-baicon"><i class="fa fa-diamond" aria-hidden="true"></i></div>';
|
||||
$html .= '<div class="relative">';
|
||||
$html .= '<div class="vip-name font-bold">' . esc_html($name) . '</div>';
|
||||
if ($desc !== '') {
|
||||
$html .= '<div class="muted-2-color em09 mb6">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
if ($equity !== '') {
|
||||
$html .= '<ul class="mb10 relative em09 v-equity">' . $equity . '</ul>';
|
||||
}
|
||||
$html .= '<a class="but jb-blue radius payvip-button" href="javascript:;">'
|
||||
. esc_html(sprintf(__('开通%s', 'aksrt'), $name))
|
||||
. '</a>';
|
||||
$html .= '</div></div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $catalog
|
||||
* @param array<string,mixed> $vip
|
||||
*/
|
||||
private function render_modal_pay(int $user_id, array $catalog, array $vip, int $prefer_level): string {
|
||||
$user = get_userdata($user_id);
|
||||
$name = $user ? $user->display_name : '';
|
||||
$avatar = get_avatar($user_id, 42);
|
||||
$desc = (string) ($vip['desc'] ?? $catalog['desc'] ?? '');
|
||||
$more = (string) ($vip['more'] ?? '');
|
||||
$groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : [];
|
||||
$methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : [];
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
|
||||
$active_lv = !empty($levels[$prefer_level]['enabled']) ? $prefer_level : 0;
|
||||
if ($active_lv <= 0) {
|
||||
foreach ([1, 2] as $lv) {
|
||||
if (!empty($levels[$lv]['enabled'])) {
|
||||
$active_lv = $lv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tabs = '';
|
||||
$panes = '';
|
||||
foreach ([1, 2] as $lv) {
|
||||
if (empty($levels[$lv]['enabled'])) {
|
||||
continue;
|
||||
}
|
||||
$is_active = ($lv === $active_lv);
|
||||
$lv_name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
|
||||
$tabs .= '<li class="relative' . ($is_active ? ' active' : '') . '">'
|
||||
. '<a class="aksrt-vip-tab" href="javascript:;" data-vip-tab="tab-payvip-' . $lv . '">'
|
||||
. esc_html($lv_name)
|
||||
. '</a></li>';
|
||||
|
||||
$group_items = [];
|
||||
foreach ($groups as $g) {
|
||||
if ((int) ($g['level'] ?? 0) === $lv) {
|
||||
$group_items = is_array($g['items'] ?? null) ? $g['items'] : [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$products = $this->render_product_radios($group_items);
|
||||
$equity = (string) ($levels[$lv]['equity'] ?? '');
|
||||
$lv_desc = (string) ($levels[$lv]['desc'] ?? '');
|
||||
|
||||
$panes .= '<div class="tab-pane aksrt-vip-pane' . ($is_active ? ' active' : '') . '" id="tab-payvip-' . $lv . '"' . ($is_active ? '' : ' hidden') . '>';
|
||||
$panes .= '<div class="pay-vip-form-row">';
|
||||
$panes .= '<div class="pay-vip-info level-' . $lv . '">';
|
||||
$panes .= '<div class="v-name font-bold">' . esc_html($lv_name) . '</div>';
|
||||
if ($lv_desc !== '') {
|
||||
$panes .= '<div class="muted-2-color em09 mb6">' . esc_html($lv_desc) . '</div>';
|
||||
}
|
||||
if ($equity !== '') {
|
||||
$panes .= '<ul class="v-equity">' . $equity . '</ul>';
|
||||
}
|
||||
$panes .= '</div><div class="pay-vip-products">';
|
||||
$panes .= $products;
|
||||
$panes .= $this->render_pay_methods($methods);
|
||||
$panes .= '<button type="button" class="but jb-red btn-block padding-lg radius aksrt-vip-submit" data-vip-submit>'
|
||||
. esc_html__('立即开通', 'aksrt') . '</button>';
|
||||
$panes .= '<p class="aksrt-vip-modal-tip muted-2-color em09 mt10" data-aksrt-vip-tip></p>';
|
||||
$panes .= '</div></div></div>';
|
||||
}
|
||||
|
||||
$points = '';
|
||||
if (!empty($catalog['points_exchange']['enabled']) && !empty($catalog['points_exchange']['packages'])) {
|
||||
$points = '<div class="mt10"><a class="but c-green btn-block" href="'
|
||||
. esc_url(function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url('vip') : '#')
|
||||
. '">' . esc_html__('积分兑换会员', 'aksrt') . '</a></div>';
|
||||
}
|
||||
|
||||
$html = '<div class="box-body payvip-modal" data-aksrt-vip-modal>';
|
||||
$html .= '<button type="button" class="close aksrt-vip-modal-close" aria-label="Close">×</button>';
|
||||
$html .= '<ul class="list-inline user-box flex ac mb10"><li class="avatar-img">' . $avatar . '</li>';
|
||||
$html .= '<li><b class="em12">' . esc_html($name) . '</b>';
|
||||
if ($desc !== '') {
|
||||
$html .= '<div class="muted-2-color em09">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$html .= '</li></ul>';
|
||||
$html .= '<ul class="flex jsb mt10 mb20 vip-cardminis gutters-5 nav">' . $tabs . '</ul>';
|
||||
$html .= '<div class="tab-content mt10">' . $panes . '</div>';
|
||||
if ($more !== '') {
|
||||
$html .= '<div class="muted-2-color mb10 em09 muted-box padding-10 mini-radius">' . $more . '</div>';
|
||||
}
|
||||
$html .= $points;
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $catalog
|
||||
* @param array<string,mixed> $vip
|
||||
*/
|
||||
private function render_modal_viped(int $user_id, array $catalog, array $vip, int $prefer_level): string {
|
||||
$user = get_userdata($user_id);
|
||||
$name = $user ? $user->display_name : '';
|
||||
$avatar = get_avatar($user_id, 42);
|
||||
$exp_label = (string) ($catalog['user_expires_label'] ?? '');
|
||||
$groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : [];
|
||||
$methods = is_array($catalog['payment_methods'] ?? null) ? $catalog['payment_methods'] : [];
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
$more = (string) ($vip['more'] ?? '');
|
||||
|
||||
$tabs = '';
|
||||
$panes = '';
|
||||
$active_set = false;
|
||||
foreach ($groups as $g) {
|
||||
$lv = (int) ($g['level'] ?? 0);
|
||||
$action = (string) ($g['action'] ?? 'renew');
|
||||
$items = is_array($g['items'] ?? null) ? $g['items'] : [];
|
||||
if ($lv <= 0 || $items === []) {
|
||||
continue;
|
||||
}
|
||||
$is_active = !$active_set;
|
||||
if ($is_active) {
|
||||
$active_set = true;
|
||||
}
|
||||
$lv_name = (string) ($g['name'] ?? ($levels[$lv]['name'] ?? ('VIP' . $lv)));
|
||||
$badge = $action === 'upgrade' ? __('升级', 'aksrt') : __('续费', 'aksrt');
|
||||
$tab_id = 'tab-payvip-' . $lv . '-' . $action;
|
||||
$tabs .= '<li class="relative' . ($is_active ? ' active' : '') . '">'
|
||||
. '<a class="aksrt-vip-tab" href="javascript:;" data-vip-tab="' . esc_attr($tab_id) . '">'
|
||||
. esc_html($badge . $lv_name)
|
||||
. '</a></li>';
|
||||
$products = $this->render_product_radios($items);
|
||||
$equity = (string) ($levels[$lv]['equity'] ?? '');
|
||||
$panes .= '<div class="tab-pane aksrt-vip-pane' . ($is_active ? ' active' : '') . '" id="' . esc_attr($tab_id) . '"' . ($is_active ? '' : ' hidden') . '>';
|
||||
$panes .= '<div class="pay-vip-form-row">';
|
||||
$panes .= '<div class="pay-vip-info level-' . $lv . '">';
|
||||
$panes .= '<div class="v-name font-bold">' . esc_html($lv_name) . '</div>';
|
||||
if ($equity !== '') {
|
||||
$panes .= '<ul class="v-equity">' . $equity . '</ul>';
|
||||
}
|
||||
$panes .= '</div><div class="pay-vip-products">';
|
||||
$panes .= $products;
|
||||
$panes .= $this->render_pay_methods($methods);
|
||||
$cta = $action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即续费', 'aksrt');
|
||||
$panes .= '<button type="button" class="but jb-red btn-block padding-lg radius aksrt-vip-submit" data-vip-submit>'
|
||||
. esc_html($cta) . '</button>';
|
||||
$panes .= '<p class="aksrt-vip-modal-tip muted-2-color em09 mt10" data-aksrt-vip-tip></p>';
|
||||
$panes .= '</div></div></div>';
|
||||
}
|
||||
|
||||
if ($tabs === '') {
|
||||
return '<div class="box-body payvip-modal text-center padding-20">'
|
||||
. esc_html__('当前没有可续费或升级的套餐。', 'aksrt')
|
||||
. '</div>';
|
||||
}
|
||||
|
||||
$html = '<div class="box-body payvip-modal" data-aksrt-vip-modal>';
|
||||
$html .= '<button type="button" class="close aksrt-vip-modal-close" aria-label="Close">×</button>';
|
||||
$html .= '<ul class="list-inline user-box flex ac mb10"><li class="avatar-img">' . $avatar . '</li>';
|
||||
$html .= '<li><b class="em12">' . esc_html($name) . '</b>';
|
||||
$html .= '<div class="muted-2-color em09"><span class="badg c-yellow vip-expdate-tag">'
|
||||
. esc_html($exp_label) . '</span></div></li></ul>';
|
||||
$html .= '<ul class="flex jsb mt10 mb20 vip-cardminis gutters-5 nav">' . $tabs . '</ul>';
|
||||
$html .= '<div class="tab-content mt10">' . $panes . '</div>';
|
||||
if ($more !== '') {
|
||||
$html .= '<div class="muted-2-color mb10 em09 muted-box padding-10 mini-radius">' . $more . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,array<string,mixed>> $items
|
||||
*/
|
||||
private function render_product_radios(array $items): string {
|
||||
if ($items === []) {
|
||||
return '<div class="muted-2-color text-center padding-10">'
|
||||
. esc_html__('暂无可用套餐', 'aksrt') . '</div>';
|
||||
}
|
||||
$html = '<div class="mb10 label-box charge-box aksrt-vip-products">';
|
||||
$i = 0;
|
||||
foreach ($items as $item) {
|
||||
$pid = (int) ($item['package_id'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$i++;
|
||||
$price = (string) ($item['price_label'] ?? '');
|
||||
$show = (string) ($item['show_price_label'] ?? '');
|
||||
$tag = (string) ($item['discount_tag'] ?? '');
|
||||
$time = (string) ($item['days_label'] ?? '');
|
||||
$title = (string) ($item['title'] ?? '');
|
||||
$checked = $i === 1 ? ' checked' : '';
|
||||
$html .= '<label class="aksrt-vip-product-label">';
|
||||
$html .= '<input class="hide vip-product-input" type="radio" name="vip_product_id" value="'
|
||||
. esc_attr((string) $pid) . '"' . $checked . '>';
|
||||
$html .= '<div class="zib-widget vip-product relative text-center product-box">';
|
||||
if ($tag !== '') {
|
||||
$html .= '<div class="abs-right vip-tag badg jb-yellow">' . esc_html($tag) . '</div>';
|
||||
}
|
||||
if ($title !== '') {
|
||||
$html .= '<div class="em09 mb3">' . esc_html($title) . '</div>';
|
||||
}
|
||||
$html .= '<div class="flex abl gap6 jc"><div class="product-price c-red">' . esc_html($price) . '</div>';
|
||||
if ($show !== '') {
|
||||
$html .= '<div class="original-price relative muted-2-color"><s>' . esc_html($show) . '</s></div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
if ($time !== '') {
|
||||
$html .= '<div class="muted-2-color c-blue em09 p2-10 badg radius">' . esc_html($time) . '</div>';
|
||||
}
|
||||
$html .= '</div></label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,array{id?:string,name?:string}> $methods
|
||||
*/
|
||||
private function render_pay_methods(array $methods): string {
|
||||
if ($methods === []) {
|
||||
return '<input type="hidden" name="aksrt_vip_pay_method" value="balance" data-aksrt-vip-method-fixed>';
|
||||
}
|
||||
if (count($methods) === 1) {
|
||||
$id = (string) ($methods[0]['id'] ?? 'balance');
|
||||
return '<input type="hidden" name="aksrt_vip_pay_method" value="' . esc_attr($id) . '" data-aksrt-vip-method-fixed>';
|
||||
}
|
||||
$html = '<div class="uc-vip-shop__methods mb10" data-aksrt-vip-methods>';
|
||||
$html .= '<span class="uc-vip-shop__methods-label">' . esc_html__('支付方式', 'aksrt') . '</span>';
|
||||
foreach ($methods as $i => $m) {
|
||||
$mid = (string) ($m['id'] ?? '');
|
||||
$mname = (string) ($m['name'] ?? $mid);
|
||||
$html .= '<label class="uc-vip-shop__method"><input type="radio" name="aksrt_vip_pay_method" value="'
|
||||
. esc_attr($mid) . '"' . ($i === 0 ? ' checked' : '') . '> <span>'
|
||||
. esc_html($mname) . '</span></label>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,14 @@ trait VipServicePackages {
|
||||
|
||||
|
||||
/**
|
||||
* Parse packages_raw lines: price|days|title
|
||||
* Parse per-level product lines (zibll vip_product).
|
||||
*
|
||||
* @return array<int, array{price:float,days:int,title:string}>
|
||||
* Preferred: price|show_price|time|unit|tag|title
|
||||
* Legacy: price|days|title
|
||||
*
|
||||
* @return array<int, array{price:float,show_price:float,time:int,unit:string,tag:string,tag_class:string,title:string,days:int}>
|
||||
*/
|
||||
public function parse_packages(string $raw): array {
|
||||
public function parse_level_products(string $raw): array {
|
||||
$lines = preg_split('/\r\n|\r|\n/', $raw) ?: [];
|
||||
$out = [];
|
||||
foreach ($lines as $line) {
|
||||
@@ -26,20 +29,90 @@ trait VipServicePackages {
|
||||
}
|
||||
$parts = array_map('trim', explode('|', $line));
|
||||
$price = isset($parts[0]) ? (float) $parts[0] : 0.0;
|
||||
$days = isset($parts[1]) ? (int) $parts[1] : 0;
|
||||
$title = isset($parts[2]) ? sanitize_text_field($parts[2]) : '';
|
||||
if ($price <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($title === '') {
|
||||
$title = $days > 0
|
||||
? sprintf(__('%d天会员', 'aksrt'), $days)
|
||||
: __('永久会员', 'aksrt');
|
||||
|
||||
$n = count($parts);
|
||||
// New format: price|show_price|time|unit|tag|title (6+)
|
||||
// Or: price|show_price|time|unit|title (5)
|
||||
if ($n >= 4 && (in_array(strtolower((string) ($parts[3] ?? '')), ['day', 'month', 'd', 'm'], true)
|
||||
|| (isset($parts[3]) && $parts[3] !== '' && !is_numeric($parts[3])))) {
|
||||
$show_price = isset($parts[1]) ? (float) $parts[1] : 0.0;
|
||||
$time = isset($parts[2]) ? (int) $parts[2] : 0;
|
||||
$unit_raw = strtolower((string) ($parts[3] ?? 'month'));
|
||||
$unit = ($unit_raw === 'day' || $unit_raw === 'd') ? 'day' : 'month';
|
||||
$tag = '';
|
||||
$title = '';
|
||||
if ($n >= 6) {
|
||||
$tag = sanitize_text_field((string) ($parts[4] ?? ''));
|
||||
$title = sanitize_text_field((string) ($parts[5] ?? ''));
|
||||
} elseif ($n === 5) {
|
||||
// price|show|time|unit|title
|
||||
$title = sanitize_text_field((string) ($parts[4] ?? ''));
|
||||
}
|
||||
} else {
|
||||
// Legacy: price|days|title
|
||||
$show_price = 0.0;
|
||||
$days_legacy = isset($parts[1]) ? (int) $parts[1] : 0;
|
||||
$time = $days_legacy;
|
||||
$unit = 'day';
|
||||
$tag = '';
|
||||
$title = isset($parts[2]) ? sanitize_text_field($parts[2]) : '';
|
||||
}
|
||||
|
||||
$time = max(0, $time);
|
||||
$days = $this->duration_to_days($time, $unit);
|
||||
if ($title === '') {
|
||||
if ($time <= 0) {
|
||||
$title = __('永久会员', 'aksrt');
|
||||
} elseif ($unit === 'day') {
|
||||
$title = sprintf(__('%d天会员', 'aksrt'), $time);
|
||||
} else {
|
||||
$title = sprintf(__('%d个月会员', 'aksrt'), $time);
|
||||
}
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'price' => round($price, 2),
|
||||
'days' => max(0, $days),
|
||||
'title' => $title,
|
||||
'price' => round($price, 2),
|
||||
'show_price' => max(0.0, round($show_price, 2)),
|
||||
'time' => $time,
|
||||
'unit' => $unit,
|
||||
'tag' => $tag,
|
||||
'tag_class' => '',
|
||||
'title' => $title,
|
||||
'days' => $days,
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Approximate days for catalog labels / order meta; activate prefers time+unit calendar math.
|
||||
*/
|
||||
public function duration_to_days(int $time, string $unit = 'day'): int {
|
||||
if ($time <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if ($unit === 'month') {
|
||||
return $time * 30;
|
||||
}
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use parse_level_products — kept as thin adapter for upgrade fixed lines.
|
||||
*
|
||||
* @return array<int, array{price:float,days:int,title:string}>
|
||||
*/
|
||||
public function parse_packages(string $raw): array {
|
||||
$parsed = $this->parse_level_products($raw);
|
||||
$out = [];
|
||||
foreach ($parsed as $p) {
|
||||
$out[] = [
|
||||
'price' => (float) $p['price'],
|
||||
'days' => (int) $p['days'],
|
||||
'title' => (string) $p['title'],
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
|
||||
@@ -17,12 +17,11 @@ trait VipServicePurchase {
|
||||
public function get_catalog(int $user_id = 0): array {
|
||||
$vip = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_payment_vip_settings()
|
||||
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []];
|
||||
: ['enabled' => false, 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []];
|
||||
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0;
|
||||
$expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0;
|
||||
$is_permanent = ($level > 0 && $expires <= 0);
|
||||
$packages = $this->parse_packages((string) ($vip['packages_raw'] ?? ''));
|
||||
$levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : [];
|
||||
$renew = is_array($vip['renew'] ?? null) ? $vip['renew'] : [];
|
||||
$upgrade = is_array($vip['upgrade'] ?? null) ? $vip['upgrade'] : [];
|
||||
@@ -37,6 +36,8 @@ trait VipServicePurchase {
|
||||
if (empty($levels[$lv]['enabled'])) {
|
||||
continue;
|
||||
}
|
||||
// Cash products can be disabled per level.
|
||||
$product_s = !isset($levels[$lv]['product_s']) || !empty($levels[$lv]['product_s']);
|
||||
if ($is_permanent && $lv <= $level) {
|
||||
continue;
|
||||
}
|
||||
@@ -49,6 +50,9 @@ trait VipServicePurchase {
|
||||
|
||||
$name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv));
|
||||
$items = [];
|
||||
$packages = $product_s
|
||||
? $this->parse_level_products((string) ($levels[$lv]['products_raw'] ?? ''))
|
||||
: [];
|
||||
foreach ($packages as $idx => $pkg) {
|
||||
$base_price = (float) $pkg['price'];
|
||||
$days = (int) $pkg['days'];
|
||||
@@ -64,6 +68,22 @@ trait VipServicePurchase {
|
||||
continue;
|
||||
}
|
||||
|
||||
$show_price = (float) ($priced['show_price'] ?? 0);
|
||||
if ($show_price <= 0 && !empty($pkg['show_price']) && $action === 'pay') {
|
||||
$show_price = (float) $pkg['show_price'];
|
||||
}
|
||||
$discount_tag = (string) ($priced['tag'] ?? '');
|
||||
if ($discount_tag === '' && $action === 'pay' && !empty($pkg['tag'])) {
|
||||
$discount_tag = (string) $pkg['tag'];
|
||||
}
|
||||
|
||||
$days_label = __('永久', 'aksrt');
|
||||
if ((int) $pkg['time'] > 0) {
|
||||
$days_label = ((string) ($pkg['unit'] ?? 'day') === 'month')
|
||||
? sprintf(__('%d 个月', 'aksrt'), (int) $pkg['time'])
|
||||
: sprintf(__('%d 天', 'aksrt'), (int) $pkg['time']);
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'package_id' => $this->encode_package_id($lv, $idx + 1),
|
||||
'level' => $lv,
|
||||
@@ -71,17 +91,17 @@ trait VipServicePurchase {
|
||||
'action' => $action,
|
||||
'price' => (float) $priced['price'],
|
||||
'base_price' => $base_price,
|
||||
'show_price' => (float) ($priced['show_price'] ?? 0),
|
||||
'discount_tag' => (string) ($priced['tag'] ?? ''),
|
||||
'show_price' => $show_price,
|
||||
'discount_tag' => $discount_tag,
|
||||
'days' => $days,
|
||||
'time' => (int) ($pkg['time'] ?? 0),
|
||||
'unit' => (string) ($pkg['unit'] ?? 'day'),
|
||||
'title' => (string) $pkg['title'],
|
||||
'price_label' => $this->format_cash_label((float) $priced['price']),
|
||||
'show_price_label' => !empty($priced['show_price'])
|
||||
? $this->format_cash_label((float) $priced['show_price'])
|
||||
'show_price_label' => $show_price > 0
|
||||
? $this->format_cash_label($show_price)
|
||||
: '',
|
||||
'days_label' => ($days <= 0)
|
||||
? __('永久', 'aksrt')
|
||||
: sprintf(__('%d 天', 'aksrt'), $days),
|
||||
'days_label' => $days_label,
|
||||
'cta_label' => $action === 'renew'
|
||||
? __('立即续费', 'aksrt')
|
||||
: ($action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即开通', 'aksrt')),
|
||||
@@ -163,7 +183,7 @@ trait VipServicePurchase {
|
||||
|
||||
$vip = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_payment_vip_settings()
|
||||
: ['enabled' => false, 'packages_raw' => '', 'levels' => [], 'renew' => [], 'upgrade' => []];
|
||||
: ['enabled' => false, 'levels' => [], 'renew' => [], 'upgrade' => []];
|
||||
if (empty($vip['enabled'])) {
|
||||
return new \WP_Error('aksrt_vip_disabled', __('会员功能未启用', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
@@ -203,6 +223,8 @@ trait VipServicePurchase {
|
||||
// Dedicated upgrade product:
|
||||
// 901 permanent/fixed, 902 unit remaining days, 903 jump permanent.
|
||||
$upgrade_mode = '';
|
||||
$pkg_time = 0;
|
||||
$pkg_unit = 'day';
|
||||
if (in_array($index, [901, 902, 903], true)) {
|
||||
$upgrade_items = $this->build_upgrade_items($user_id, $expires, $is_permanent, (string) ($upgrade['product'] ?? ''));
|
||||
$match = null;
|
||||
@@ -222,18 +244,30 @@ trait VipServicePurchase {
|
||||
];
|
||||
$amount = round((float) $match['price'], 2);
|
||||
$days = (int) $match['days'];
|
||||
$pkg_time = $days; // unit upgrade keeps days as remaining; jump/permanent use 0
|
||||
$pkg_unit = 'day';
|
||||
$base_price = (float) ($match['base_price'] ?? $amount);
|
||||
$discount_tag = (string) ($match['discount_tag'] ?? __('升级专享', 'aksrt'));
|
||||
$upgrade_mode = sanitize_key((string) ($match['upgrade_mode'] ?? ''));
|
||||
$action = 'upgrade';
|
||||
if ($upgrade_mode === 'jump' || $upgrade_mode === 'permanent') {
|
||||
$pkg_time = 0;
|
||||
$days = 0;
|
||||
}
|
||||
} else {
|
||||
$packages = $this->parse_packages((string) ($vip['packages_raw'] ?? ''));
|
||||
$level_cfg = is_array($levels[$level] ?? null) ? $levels[$level] : [];
|
||||
if (isset($level_cfg['product_s']) && empty($level_cfg['product_s'])) {
|
||||
return new \WP_Error('aksrt_vip_product_disabled', __('该等级暂不支持购买', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$packages = $this->parse_level_products((string) ($level_cfg['products_raw'] ?? ''));
|
||||
$pkg = $packages[$index - 1] ?? null;
|
||||
if (!$pkg) {
|
||||
return new \WP_Error('aksrt_invalid_package', __('套餐不存在', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$base_price = (float) $pkg['price'];
|
||||
$days = (int) $pkg['days'];
|
||||
$pkg_time = (int) ($pkg['time'] ?? 0);
|
||||
$pkg_unit = (string) ($pkg['unit'] ?? 'day');
|
||||
$priced = $this->apply_action_price($base_price, $action, $renew, $upgrade, $level, $user_level, $expires, $is_permanent, $days);
|
||||
if ($priced === null) {
|
||||
return new \WP_Error('aksrt_invalid_package', __('当前无法购买该套餐', 'aksrt'), ['status' => 400]);
|
||||
@@ -259,6 +293,8 @@ trait VipServicePurchase {
|
||||
'vip_pay' => 1,
|
||||
'level' => $level,
|
||||
'days' => $days,
|
||||
'time' => $pkg_time,
|
||||
'unit' => $pkg_unit,
|
||||
'package_index' => $index,
|
||||
'package_title' => (string) ($pkg['title'] ?? ''),
|
||||
'package_id' => $package_id,
|
||||
@@ -401,6 +437,11 @@ trait VipServicePurchase {
|
||||
|
||||
$level = (int) ($meta['level'] ?? 0);
|
||||
$days = (int) ($meta['days'] ?? 0);
|
||||
$time = (int) ($meta['time'] ?? 0);
|
||||
$unit = sanitize_key((string) ($meta['unit'] ?? 'day'));
|
||||
if (!in_array($unit, ['day', 'month'], true)) {
|
||||
$unit = 'day';
|
||||
}
|
||||
$upgrade_mode = sanitize_key((string) ($meta['upgrade_mode'] ?? ''));
|
||||
if ($level <= 0) {
|
||||
$decoded = $this->decode_package_id((int) ($paid_order['product_id'] ?? 0));
|
||||
@@ -418,6 +459,9 @@ trait VipServicePurchase {
|
||||
// unit upgrade: raise level only, keep current expiry (zibll).
|
||||
if ($upgrade_mode === 'unit') {
|
||||
$this->activate_vip($user_id, $level, 0, true);
|
||||
} elseif ($time > 0 || (isset($meta['time']) && (int) $meta['time'] === 0 && $days === 0)) {
|
||||
// Prefer calendar time+unit when present (including permanent time=0).
|
||||
$this->activate_vip_duration($user_id, $level, $time, $unit);
|
||||
} else {
|
||||
$this->activate_vip($user_id, $level, $days, false);
|
||||
}
|
||||
@@ -439,6 +483,8 @@ trait VipServicePurchase {
|
||||
$current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true);
|
||||
$new_level = max($current_level, $level);
|
||||
update_user_meta($user_id, 'aksrt_vip_level', $new_level);
|
||||
// Clear expired memory when re-granting.
|
||||
delete_user_meta($user_id, 'aksrt_vip_level_expired');
|
||||
|
||||
// Unit upgrade (zibll): keep vip_exp_date, only raise level.
|
||||
if ($keep_expiry) {
|
||||
@@ -460,6 +506,41 @@ trait VipServicePurchase {
|
||||
update_user_meta($user_id, 'aksrt_vip_expires', $base + ($days * DAY_IN_SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate VIP using calendar duration (zibll month/day).
|
||||
* time=0 → permanent. Extends from max(now, current_exp).
|
||||
*/
|
||||
public function activate_vip_duration(int $user_id, int $level, int $time, string $unit = 'day'): void {
|
||||
if ($user_id <= 0) {
|
||||
return;
|
||||
}
|
||||
$level = max(1, min(2, $level));
|
||||
$unit = $unit === 'month' ? 'month' : 'day';
|
||||
$current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true);
|
||||
$new_level = max($current_level, $level);
|
||||
update_user_meta($user_id, 'aksrt_vip_level', $new_level);
|
||||
delete_user_meta($user_id, 'aksrt_vip_level_expired');
|
||||
|
||||
if ($time <= 0) {
|
||||
update_user_meta($user_id, 'aksrt_vip_expires', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
$current_exp = (int) get_user_meta($user_id, 'aksrt_vip_expires', true);
|
||||
if ($current_exp === 0 && $current_level > 0) {
|
||||
return; // keep permanent
|
||||
}
|
||||
$base = max($current_exp, time());
|
||||
$modifier = $unit === 'month'
|
||||
? ('+' . $time . ' months')
|
||||
: ('+' . $time . ' days');
|
||||
$new_exp = strtotime($modifier, $base);
|
||||
if ($new_exp === false) {
|
||||
$new_exp = $base + ($this->duration_to_days($time, $unit) * DAY_IN_SECONDS);
|
||||
}
|
||||
update_user_meta($user_id, 'aksrt_vip_expires', (int) $new_exp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,40 @@ trait VipServiceRest {
|
||||
'callback' => [$this, 'rest_points_exchange'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/pay/vip/modal', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_modal'],
|
||||
'permission_callback' => [$this, 'check_rest_nonce'],
|
||||
'args' => [
|
||||
'vip_level' => [
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /pay/vip/modal?vip_level=1
|
||||
*/
|
||||
public function rest_modal(\WP_REST_Request $request) {
|
||||
$uid = get_current_user_id();
|
||||
if ($uid <= 0) {
|
||||
return new \WP_Error('aksrt_need_login', __('请先登录后再开通会员。', 'aksrt'), [
|
||||
'status' => 401,
|
||||
'login_url' => function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(),
|
||||
]);
|
||||
}
|
||||
$level = (int) $request->get_param('vip_level');
|
||||
if ($level < 1 || $level > 2) {
|
||||
$level = 1;
|
||||
}
|
||||
$html = $this->render_modal_html($uid, $level);
|
||||
return new \WP_REST_Response([
|
||||
'html' => $html,
|
||||
'user_level' => function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($uid) : 0,
|
||||
], 200);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -161,6 +161,31 @@ trait ScriptsPart1b {
|
||||
);
|
||||
}
|
||||
|
||||
// VIP modal (zibll .pay-vip)
|
||||
$vip_on = class_exists('\AKSRT\Settings\Settings')
|
||||
&& !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']);
|
||||
if ($vip_on) {
|
||||
$vip_css = get_template_directory() . '/assets/css/frontend/vip-modal.css';
|
||||
if (file_exists($vip_css)) {
|
||||
wp_enqueue_style(
|
||||
'aksrt-vip-modal',
|
||||
$base_uri . '/assets/css/frontend/vip-modal.css',
|
||||
[$core_dep],
|
||||
(string) filemtime($vip_css)
|
||||
);
|
||||
}
|
||||
$vip_js = get_template_directory() . '/assets/js/pay-vip.js';
|
||||
if (file_exists($vip_js)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-pay-vip',
|
||||
$base_uri . '/assets/js/pay-vip.js',
|
||||
['aksrt-script'],
|
||||
(string) filemtime($vip_js),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Print stylesheet — only load on singular pages
|
||||
if (is_singular()) {
|
||||
|
||||
@@ -29,6 +29,18 @@ trait OrderRepositoryCore {
|
||||
|
||||
$order_number = $data['order_number'] ?? $this->generate_order_number();
|
||||
|
||||
// Preserve JSON pay meta (VIP/content/coupon). Only strip tags for plain text notes.
|
||||
$note_raw = (string) ($data['note'] ?? '');
|
||||
$note_trim = trim($note_raw);
|
||||
if ($note_trim !== '' && ($note_trim[0] === '{' || $note_trim[0] === '[')) {
|
||||
$decoded = json_decode($note_trim, true);
|
||||
$note = is_array($decoded)
|
||||
? (string) wp_json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||
: sanitize_textarea_field($note_raw);
|
||||
} else {
|
||||
$note = sanitize_textarea_field($note_raw);
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), [
|
||||
'order_number' => $order_number,
|
||||
'user_id' => (int) ($data['user_id'] ?? 0),
|
||||
@@ -42,7 +54,7 @@ trait OrderRepositoryCore {
|
||||
'created_at' => $data['created_at'] ?? current_time('mysql'),
|
||||
'paid_at' => $data['paid_at'] ?? null,
|
||||
'expires_at' => $data['expires_at'] ?? null,
|
||||
'note' => sanitize_textarea_field((string) ($data['note'] ?? '')),
|
||||
'note' => $note,
|
||||
], ['%s', '%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']);
|
||||
|
||||
if ($wpdb->insert_id) {
|
||||
|
||||
@@ -153,12 +153,23 @@ trait CreditsPaymentGroups {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{enabled:bool,name:string,badge_text:string,packages_raw:string,package_count:int,help_text:string}
|
||||
* Dual-level VIP settings (Zibll pay_user_vip_1 / pay_user_vip_2).
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_payment_vip_settings(): array {
|
||||
return PaymentRuntime::get_payment_vip_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for callers that expect get_vip_settings() (header CTA, float buttons).
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_vip_settings(): array {
|
||||
return self::get_payment_vip_settings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{enabled:bool,share_ratio:int,settlement_threshold:int,cycle_days:int,note:string}
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,8 @@ trait FacadeBulk {
|
||||
$s = self::get_all();
|
||||
|
||||
return [
|
||||
'enabled' => !empty($s['header_newposts_enabled']),
|
||||
// Default on like zibll nav_newposts when key unset.
|
||||
'enabled' => !isset($s['header_newposts_enabled']) || !empty($s['header_newposts_enabled']),
|
||||
'text' => trim((string) ($s['header_newposts_text'] ?? '<i class="fa fa-pencil"></i> 发布')),
|
||||
'radius' => max(0, min(30, (int) ($s['header_newposts_radius'] ?? 20))),
|
||||
'class' => sanitize_html_class((string) ($s['header_newposts_class'] ?? '')),
|
||||
|
||||
@@ -150,27 +150,60 @@ class PaymentRuntime {
|
||||
$renew_price_type = 'discount';
|
||||
}
|
||||
|
||||
$packages = (string) ($settings['payment_vip_packages'] ?? "30|30|月卡\n365|365|年卡");
|
||||
$default_l1 = "30|59|1|month|限时|月卡\n98|199|3|month|特惠|季卡\n365|599|12|month|超值|年卡\n999|1299|0|day|永久|永久卡";
|
||||
$default_l2 = "58|99|1|month|限时|月卡\n168|299|3|month|特惠|季卡\n598|999|12|month|超值|年卡\n1299|1999|0|day|永久|永久卡";
|
||||
$products_1 = sanitize_textarea_field((string) ($settings['payment_vip_1_products'] ?? $default_l1));
|
||||
$products_2 = sanitize_textarea_field((string) ($settings['payment_vip_2_products'] ?? $default_l2));
|
||||
// Restore newlines lost only if empty after sanitize — sanitize_textarea_field keeps \n.
|
||||
if (trim($products_1) === '') {
|
||||
$products_1 = $default_l1;
|
||||
}
|
||||
if (trim($products_2) === '') {
|
||||
$products_2 = $default_l2;
|
||||
}
|
||||
|
||||
$desc = sanitize_textarea_field((string) ($settings['pay_user_vip_desc'] ?? ''));
|
||||
$more = (string) ($settings['pay_user_vip_more'] ?? '');
|
||||
$allowed_equity = [
|
||||
'li' => [],
|
||||
'ul' => [],
|
||||
'ol' => [],
|
||||
'strong' => [],
|
||||
'b' => [],
|
||||
'em' => [],
|
||||
'i' => ['class' => true],
|
||||
'span' => ['class' => true],
|
||||
'br' => [],
|
||||
'a' => ['href' => true, 'target' => true, 'rel' => true, 'class' => true],
|
||||
];
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'name' => $vip1_enabled ? $name1 : $badge,
|
||||
'badge_text' => $badge,
|
||||
'desc' => $desc,
|
||||
'packages_raw' => $packages,
|
||||
'package_count' => self::count_structured_lines($packages),
|
||||
'more' => wp_kses($more, $allowed_equity + ['div' => ['class' => true], 'p' => ['class' => true]]),
|
||||
'help_text' => $desc !== '' ? $desc : __('会员权益以免扣下载和身份展示为主,开通后立即生效。', 'aksrt'),
|
||||
'levels' => [
|
||||
1 => [
|
||||
'enabled' => $vip1_enabled,
|
||||
'name' => $name1,
|
||||
'enabled' => $vip1_enabled,
|
||||
'name' => $name1,
|
||||
'desc' => sanitize_text_field((string) ($settings['pay_user_vip_1_desc'] ?? '')),
|
||||
'equity' => wp_kses((string) ($settings['pay_user_vip_1_equity'] ?? ''), $allowed_equity),
|
||||
'img_icon' => esc_url_raw((string) ($settings['vip_1_img_icon'] ?? '')),
|
||||
'product_s' => !isset($settings['vip_1_product_s']) || !empty($settings['vip_1_product_s']),
|
||||
'products_raw' => $products_1,
|
||||
'download_limit' => max(0, min(9999, (int) ($settings['pay_download_limit_vip_1'] ?? 0))),
|
||||
'download_speed' => max(0, min(99999, (int) ($settings['pay_download_speed_vip_1'] ?? 0))),
|
||||
],
|
||||
2 => [
|
||||
'enabled' => $vip2_enabled,
|
||||
'name' => $name2,
|
||||
'enabled' => $vip2_enabled,
|
||||
'name' => $name2,
|
||||
'desc' => sanitize_text_field((string) ($settings['pay_user_vip_2_desc'] ?? '')),
|
||||
'equity' => wp_kses((string) ($settings['pay_user_vip_2_equity'] ?? ''), $allowed_equity),
|
||||
'img_icon' => esc_url_raw((string) ($settings['vip_2_img_icon'] ?? '')),
|
||||
'product_s' => !isset($settings['vip_2_product_s']) || !empty($settings['vip_2_product_s']),
|
||||
'products_raw' => $products_2,
|
||||
'download_limit' => max(0, min(9999, (int) ($settings['pay_download_limit_vip_2'] ?? 0))),
|
||||
'download_speed' => max(0, min(99999, (int) ($settings['pay_download_speed_vip_2'] ?? 0))),
|
||||
],
|
||||
@@ -192,12 +225,13 @@ class PaymentRuntime {
|
||||
'upgrade' => [
|
||||
'enabled' => !isset($settings['vip_upgrade']) || !empty($settings['vip_upgrade']),
|
||||
'desc' => sanitize_textarea_field((string) ($settings['vip_upgrade_desc'] ?? '')),
|
||||
'product' => sanitize_text_field((string) ($settings['vip_upgrade_product'] ?? '')),
|
||||
// Multi-line upgrade product rules — must not collapse newlines.
|
||||
'product' => sanitize_textarea_field((string) ($settings['vip_upgrade_product'] ?? '')),
|
||||
],
|
||||
'points_exchange' => [
|
||||
'enabled' => !empty($settings['pay_vip_points_exchange_s']),
|
||||
'desc' => sanitize_textarea_field((string) ($settings['pay_vip_points_exchange_desc'] ?? '')),
|
||||
'packages_raw' => (string) ($settings['payment_vip_points_packages'] ?? "1|500|30|积分月卡\n1|5000|0|积分永久"),
|
||||
'packages_raw' => sanitize_textarea_field((string) ($settings['payment_vip_points_packages'] ?? "1|500|30|积分月卡\n1|5000|0|积分永久")),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ class Center {
|
||||
'priority' => 50,
|
||||
'type' => 'panel',
|
||||
],
|
||||
'vip' => [
|
||||
'label' => __('我的会员', 'aksrt'),
|
||||
'icon' => 'crown',
|
||||
'priority' => 45,
|
||||
'type' => 'panel',
|
||||
],
|
||||
'notif' => [
|
||||
'label' => __('消息通知', 'aksrt'),
|
||||
'icon' => 'bell',
|
||||
@@ -119,12 +125,26 @@ class Center {
|
||||
$this->panels['credits'] = $panel;
|
||||
}
|
||||
|
||||
// Dedicated VIP tab when membership is enabled.
|
||||
$vip_on = class_exists('\AKSRT\Settings\Settings')
|
||||
&& !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']);
|
||||
if ($vip_on && !isset($this->panels['vip'])) {
|
||||
$this->panels['vip'] = $definitions['vip'];
|
||||
} elseif (!$vip_on && isset($this->panels['vip'])) {
|
||||
unset($this->panels['vip']);
|
||||
}
|
||||
|
||||
if (!isset($this->panels['settings'])) {
|
||||
$this->panels['settings'] = $definitions['settings'];
|
||||
}
|
||||
|
||||
$this->panels['notif'] = $definitions['notif'];
|
||||
|
||||
// Keep stable order by priority.
|
||||
uasort($this->panels, static function (array $a, array $b): int {
|
||||
return ((int) ($a['priority'] ?? 100)) <=> ((int) ($b['priority'] ?? 100));
|
||||
});
|
||||
|
||||
/**
|
||||
* 允许插件/子主题注册额外面板
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user