diff --git a/.gitignore b/.gitignore index 59a5c239..ae6109b2 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ !/tests/widget-system/pay-orders-filter-contract.test.cjs !/tests/widget-system/front-submit-editor-contract.test.cjs !/tests/widget-system/comment-smilies-removal-contract.test.cjs +!/tests/widget-system/pay-card-contract.test.cjs !/tests/asset-ledger/ !/tests/asset-ledger/*.php !/tests/visual/docker-compose.zibll.yml diff --git a/assets/css/frontend/pay-box.css b/assets/css/frontend/pay-box.css new file mode 100644 index 00000000..6159ed25 --- /dev/null +++ b/assets/css/frontend/pay-box.css @@ -0,0 +1,52 @@ +/* Canonical Zibll 9.0 paid-content card shell. */ +.zib-widget.pay-box { + clear: both; + margin-bottom: 20px; + padding: 15px; + background-color: var(--main-bg-color); + border-radius: var(--main-radius); + box-shadow: 0 0 10px var(--main-shadow); +} + +.pay-widget__orb { + position: absolute; + border-radius: 100%; +} + +.pay-widget__orb--right { + top: -34%; + left: 75%; + width: 200px; + height: 200px; +} + +.pay-widget__orb--left { + left: -229px; + width: 337px; + height: 305px; + opacity: .7; +} + +.pay-thumb-placeholder { + position: absolute; + inset: 0; + display: grid; + place-items: center; + color: rgba(255, 255, 255, .82); + background: linear-gradient(135deg, #59616d, #272b31); + font-size: 40px; +} + +.pay-box .pay-attr { + margin-top: 10px; +} + +.pay-box .pay-attr-item { + display: flex; + justify-content: space-between; + gap: 12px; +} + +.pay-box .muted-2-color + .muted-2-color { + margin-left: 10px; +} diff --git a/assets/css/frontend/single-post/cover.css b/assets/css/frontend/single-post/cover.css index 5a24d64c..96bece19 100644 --- a/assets/css/frontend/single-post/cover.css +++ b/assets/css/frontend/single-post/cover.css @@ -226,18 +226,6 @@ .single-head-cover .cover-meta.single-metabox { display: flex; flex-wrap: wrap; gap: 8px 12px; font-size: 12px; color: var(--color-ink-4); } -/* Pay box root — legacy #posts-pay.aksrt-panel.pay-box */ -#posts-pay.aksrt-panel, -#posts-pay.pay-box, -.aksrt-single-pay-box { - margin: 16px 0 20px; -} -#posts-pay .aksrt-paywall, -.aksrt-single-pay-box .pay-box, -.aksrt-single-pay-box .aksrt-paywall { - margin: 0; -} - /* Cover meta pills + title-h-left */ .single-cover-con .article-title.title-h-left, .single-cover .article-title.title-h-left { diff --git a/assets/css/frontend/single-post/hidden-content.css b/assets/css/frontend/single-post/hidden-content.css index f96e8e74..f6db2f4d 100644 --- a/assets/css/frontend/single-post/hidden-content.css +++ b/assets/css/frontend/single-post/hidden-content.css @@ -119,117 +119,5 @@ opacity: .85; } -/* ---- Paid fragment purchase card ---- */ -.aksrt-paywall { - position: relative; - margin: 24px 0; -} - -.aksrt-paywall__card { - position: relative; - z-index: 0; - display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; - align-items: center; - gap: 18px; - padding: 22px 24px; - background: linear-gradient(135deg, var(--surface-1, #fff) 0%, var(--surface-2, #f8fafc) 100%); - border: 1px solid var(--color-line, #e5e7eb); - border-radius: var(--radius-lg, 14px); - box-shadow: 0 10px 28px rgba(15, 23, 42, .08); -} - -.aksrt-paywall--fragment .aksrt-paywall__card { - border-style: dashed; - border-color: color-mix(in srgb, var(--color-primary, #f04494) 42%, var(--color-line, #e5e7eb)); -} - -.aksrt-paywall__icon { - display: grid; - place-items: center; - width: 44px; - height: 44px; - flex: 0 0 auto; - border-radius: 50%; - color: var(--color-primary, #f04494); - background: color-mix(in srgb, var(--color-primary, #f04494) 12%, transparent); - font-size: 20px; -} - -.aksrt-paywall__body { min-width: 0; } - -.aksrt-paywall__title { - margin: 0 0 4px; - color: var(--color-ink, #15171a); - font-size: 17px; - font-weight: 700; -} - -.aksrt-paywall__desc { - margin: 0; - color: var(--color-ink-3, #525866); - font-size: 14px; - line-height: 1.6; -} - -.aksrt-paywall__price { margin-top: 8px; } -.aksrt-paywall__price-amount { color: var(--color-error, #ef4444); font-size: 20px; font-weight: 700; } -.aksrt-paywall__price-member { color: var(--color-primary, #f04494); font-size: 16px; font-weight: 700; } -.aksrt-paywall__price-free { color: var(--color-success, #10b981); font-size: 16px; font-weight: 700; } -.aksrt-paywall__price-original { margin-left: 8px; color: var(--color-ink-4, #8a8f98); font-size: 13px; text-decoration: line-through; } - -.aksrt-paywall__actions { - position: relative; - z-index: 1; - display: flex; - flex: 0 0 auto; - flex-direction: column; - align-items: center; - gap: 8px; - text-align: center; -} - -.aksrt-paywall__btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - min-height: 40px; - white-space: nowrap; - cursor: pointer; -} - -.aksrt-paywall__btn:focus-visible, -.aksrt-paywall__login-link:focus-visible { - outline: 3px solid color-mix(in srgb, var(--color-primary, #f04494) 35%, transparent); - outline-offset: 3px; -} - -.aksrt-paywall__hint, -.aksrt-paywall__login-link { - margin: 0; - color: var(--color-ink-4, #8a8f98); - font-size: 12px; - line-height: 1.5; -} - -.aksrt-paywall__login-link { text-decoration: underline; } -.aksrt-gallery-locked { filter: blur(8px); pointer-events: none; user-select: none; } .aksrt-paywall-preview { margin: 0 0 16px; padding: 14px 16px; border-left: 3px solid var(--color-primary, #f04494); background: var(--surface-2, #f8fafc); border-radius: 0 10px 10px 0; color: var(--color-ink-2, #374151); line-height: 1.7; } .aksrt-paywall-preview p:last-child { margin-bottom: 0; } -.aksrt-paid-gallery__overlay .but, .aksrt-paid-video__placeholder .but { margin-top: 10px; } - -@media (max-width: 680px) { - .aksrt-paywall__card { - grid-template-columns: auto minmax(0, 1fr); - padding: 18px; - } - - .aksrt-paywall__actions { - grid-column: 1 / -1; - align-items: stretch; - width: 100%; - } - - .aksrt-paywall__btn { width: 100%; } -} diff --git a/assets/widgets/frontend.css b/assets/widgets/frontend.css index fbaf5b20..ace4b740 100644 --- a/assets/widgets/frontend.css +++ b/assets/widgets/frontend.css @@ -125,7 +125,6 @@ .aksrt-term-aggregation { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 15px; }.aksrt-term-aggregation__card { padding: 15px; border-radius: var(--aksrt-widget-radius); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-term-aggregation header a { display: flex; justify-content: space-between; }.aksrt-term-aggregation header span { color: var(--muted-color,#8a8c98); font-size: 13px; }.aksrt-term-aggregation ul { display: grid; gap: 10px; margin: 14px 0 0; padding: 0; list-style: none; }.aksrt-term-aggregation li a { display: flex; align-items: center; gap: 10px; }.aksrt-term-aggregation li img { width: 55px; height: 42px; border-radius: 6px; object-fit: cover; } .aksrt-widget-items { position: relative; min-height: 40px; }.aksrt-widget-items.is-loading { opacity: .72; pointer-events: none; }.aksrt-widget-skeleton { height: 180px; border-radius: 10px; background: linear-gradient(90deg,rgba(127,127,127,.08),rgba(127,127,127,.17),rgba(127,127,127,.08)); background-size: 200% 100%; animation: aksrt-shimmer 1.2s infinite; }.aksrt-widget-load-more { display: block; min-width: 120px; margin: 18px auto 0; padding: 9px 18px; border: 0; border-radius: 999px; background: rgba(127,127,127,.12); color: inherit; cursor: pointer; }.aksrt-widget-pagination { display:flex;justify-content:center;gap:6px;margin-top:18px; }.aksrt-widget-pagination button { min-width:34px;height:34px;border:0;border-radius:8px;background:rgba(127,127,127,.1);color:inherit;cursor:pointer; }.aksrt-widget-pagination button[aria-current="page"] { background:var(--focus-color,#ff5473);color:#fff; }@keyframes aksrt-shimmer { to { background-position: -200% 0; } } .aksrt-widget-oneline .swiper-slide { width: min(300px,78vw); }.aksrt-widget-tabs__nav { display: flex; gap: 8px; margin-bottom: 15px; overflow-x: auto; }.aksrt-widget-tabs__nav button { flex: none; padding: 7px 14px; border: 0; border-radius: 999px; background: rgba(127,127,127,.1); color: inherit; cursor: pointer; }.aksrt-widget-tabs__nav button[aria-selected="true"] { background: var(--focus-color,#ff5473); color: #fff; } -.aksrt-post-pay { padding: 20px; border-radius: var(--aksrt-widget-radius); text-align: center; }.aksrt-post-pay.is-authorized { background: rgba(34,197,94,.1); }.aksrt-post-pay > i { display: block; margin-bottom: 9px; color: #22c55e; font-size: 30px; }.aksrt-post-pay p { color: var(--muted-color,#8a8c98); } .aksrt-pricing-card { position: relative; display: flex; flex-direction: column; padding: 20px; border: 1px solid var(--main-border-color,rgba(127,127,127,.13)); border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-pricing-card.is-featured { border-color: var(--focus-color,#ff5473); }.aksrt-pricing-card__badge { position: absolute; top: 12px; right: 12px; padding: 4px 9px; border-radius: 999px; background: var(--focus-color,#ff5473); color: #fff; font-size: 12px; }.aksrt-pricing-card h3 { margin: 0 0 12px; }.aksrt-pricing-card__price { font-size: 34px; font-weight: 800; }.aksrt-pricing-card p { color: var(--muted-color,#8a8c98); }.aksrt-pricing-card ul { flex: 1; display: grid; gap: 9px; margin: 15px 0 22px; padding: 0; list-style: none; }.aksrt-pricing-card li i { margin-right: 8px; color: #22c55e; } .aksrt-reviews .swiper-slide { width: min(390px,86vw); height: auto; }.aksrt-review-card { height: 100%; padding: 20px; border-radius: var(--main-radius,8px); background: var(--main-bg-color,#fff); box-shadow: 0 0 10px var(--main-shadow,rgba(116,116,116,.08)); }.aksrt-review-card__rating { color: #f6b73c; }.aksrt-review-card blockquote { min-height: 80px; margin: 15px 0; line-height: 1.75; }.aksrt-review-card footer { display: flex; align-items: center; gap: 11px; }.aksrt-review-card__avatar { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; }.aksrt-review-card footer span { display: flex; flex-direction: column; }.aksrt-review-card footer small { color: var(--muted-color,#8a8c98); } diff --git a/inc/core-hooks/part-3.php b/inc/core-hooks/part-3.php index 9e20d0c3..59879533 100644 --- a/inc/core-hooks/part-3.php +++ b/inc/core-hooks/part-3.php @@ -240,15 +240,10 @@ add_action('init', function () { // ============================================================ /** - * 未付费内容:替换为付费墙。 + * 未购买的图库/视频正文只保留公开预览;购买卡由 single 模板渲染。 */ add_filter('the_content', 'aksrt_paid_content_filter', 20); -/** - * 已付费内容增强:追加图库轮播/视频播放器。 - */ -add_filter('the_content', 'aksrt_paid_content_enhance', 25); - /** * 付费/资源文章使用统一的 single.php。 */ diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index 7d734b30..d7d2552e 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -157,25 +157,6 @@ if (!function_exists('aksrt_get_pay_meta')) { if (!function_exists('aksrt_default_pay_meta')) { function aksrt_default_pay_meta(): array { return \AKSRT\Core\DomainModel::default_pay_meta(); - - return [ - 'pay_type' => AKSRT_PAY_TYPE_NO, - 'price' => 0.0, - 'original_price' => 0.0, - 'vip1_price' => 0.0, - 'vip2_price' => 0.0, - 'version' => '', - 'size' => '', - 'license' => '', - 'compat' => '', - 'demo_url' => '', - 'downloads' => [], - 'require_login' => false, - 'require_captcha' => false, - 'attributes' => [], - 'gallery' => [], - 'video_url' => '', - ]; } } @@ -1195,101 +1176,127 @@ if (!function_exists('aksrt_get_pay_attributes_html')) { // ============================================================ if (!function_exists('aksrt_get_paywall_html')) { - function aksrt_get_paywall_html(WP_Post $post, bool $is_fragment = false): string { + function aksrt_get_paywall_html(WP_Post $post, array $args = []): string { + $is_widget = ($args['layout'] ?? 'article') === 'widget'; + $theme = sanitize_html_class((string) ($args['theme'] ?? 'jb-red')) ?: 'jb-red'; $meta = aksrt_get_pay_meta($post); - $pay_type = $meta['pay_type']; - $priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null; - $pay_modo = is_array($priced) ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); - $price = is_array($priced) - ? ($priced['amount'] ?? ($pay_modo === 'points' ? 0 : '0.00')) - : ($pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00')); - $price_minor = aksrt_asset_price_minor($price, $pay_modo); - $vip_level = is_array($priced) ? (int) ($priced['vip_level'] ?? 0) : 0; - $currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); - $cash_symbol = '¥'; - if (class_exists('\AKSRT\Settings\Settings')) { - $paid = \AKSRT\Settings\Settings::get_paid_content_settings(); - $cash_symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); - } - $price_text = $pay_modo === 'points' - ? (aksrt_format_price($price) . ' ' . $currency) - : ($cash_symbol . aksrt_format_price($price)); + $pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO); + if ($pay_type === AKSRT_PAY_TYPE_NO) return ''; - $type_labels = [ - AKSRT_PAY_TYPE_READING => ['icon' => 'fa-lock', 'label' => __('付费阅读', 'aksrt'), 'desc' => __('付费后即可查看完整内容', 'aksrt')], - AKSRT_PAY_TYPE_GALLERY => ['icon' => 'fa-image', 'label' => __('付费图库', 'aksrt'), 'desc' => __('付费后即可查看全部图片', 'aksrt')], - AKSRT_PAY_TYPE_VIDEO => ['icon' => 'fa-play-circle', 'label' => __('付费视频', 'aksrt'), 'desc' => __('付费后即可观看完整视频', 'aksrt')], - ]; - - $type_info = $type_labels[$pay_type] ?? $type_labels[AKSRT_PAY_TYPE_READING]; - if ($is_fragment && $pay_type === AKSRT_PAY_TYPE_READING) { - $type_info['desc'] = __('付费后即可查看此片段内容', 'aksrt'); - } - $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post)); - $is_logged_in = is_user_logged_in(); - $original_price = $pay_modo === 'points' - ? max(0, (int) ($meta['original_price'] ?? 0)) - : (string) ($meta['original_price'] ?? '0.00'); - $original_minor = aksrt_asset_price_minor($original_price, $pay_modo); - $has_original = $original_minor > 0 && $original_minor > $price_minor; - $pay_limit = (int) ($meta['pay_limit'] ?? 0); + $priced = aksrt_get_user_pay_price($post); + $modo = (string) ($priced['modo'] ?? '0'); + $amount = $priced['amount'] ?? 0; + $amount_minor = aksrt_asset_price_minor($amount, $modo); + $price_label = aksrt_get_pay_price_label($post); + $price_text = (string) ($price_label['text'] ?? __('免费', 'aksrt')); + $pay_limit = (int) ($priced['pay_limit'] ?? ($meta['pay_limit'] ?? 0)); + $vip_level = (int) ($priced['vip_level'] ?? 0); $member_blocked = $pay_limit > 0 && $vip_level < $pay_limit; - $vip_url = function_exists('aksrt_get_user_center_tab_url') - ? aksrt_get_user_center_tab_url('credits') - : (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/')); + $type_labels = [ + AKSRT_PAY_TYPE_READING => __('付费阅读', 'aksrt'), + AKSRT_PAY_TYPE_DOWNLOAD => __('付费下载', 'aksrt'), + AKSRT_PAY_TYPE_GALLERY => __('付费图库', 'aksrt'), + AKSRT_PAY_TYPE_VIDEO => __('付费视频', 'aksrt'), + AKSRT_PAY_TYPE_VIP => __('VIP 专属', 'aksrt'), + ]; + $type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt'); + $order_types = [ + AKSRT_PAY_TYPE_READING => 1, + AKSRT_PAY_TYPE_DOWNLOAD => 2, + AKSRT_PAY_TYPE_GALLERY => 5, + AKSRT_PAY_TYPE_VIDEO => 6, + AKSRT_PAY_TYPE_VIP => 4, + ]; + $order_type = (int) ($order_types[$pay_type] ?? 1); + $title = get_the_title($post); + $doc = trim((string) ($args['message'] ?? '')); + if ($doc === '') { + $doc = sprintf(__('此内容为%s,请付费后查看', 'aksrt'), $type_label); + } + $sales = max(0, (int) ($meta['virtual_sales'] ?? 0)); + $sales += max(0, (int) get_post_meta($post->ID, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true)); + $tag = $is_widget + ? '' . esc_html($type_label) . '' + : '
' . esc_html($type_label) . '
'; + $block_class = $is_widget ? ' btn-block' : ''; + $button = ''; + if (!is_user_logged_in()) { + if ($pay_limit <= 0 && $amount_minor > 0 && $modo === '0' && aksrt_allow_guest_content_pay($post)) { + $button = ''; + } else { + $button = ' ' . esc_html__('登录购买', 'aksrt') . ''; + } + } elseif ($member_blocked) { + $vip_url = aksrt_get_user_center_tab_url('vip'); + $button = ' ' . esc_html__('开通会员后可查看', 'aksrt') . ''; + } elseif ($amount_minor > 0) { + $button = ''; + } else { + $button = ''; + } + $price_color = ($price_label['free'] ?? false) ? 'c-green' : ($modo === 'points' ? 'c-yellow' : 'c-red'); + $price = '
' . esc_html($price_text) . '
'; + $vip_items = []; + $base_minor = aksrt_asset_price_minor($priced['base'] ?? $amount, $modo); + foreach ([1, 2] as $level) { + $key = $modo === 'points' ? 'vip' . $level . '_points' : 'vip' . $level . '_price'; + $value = $meta[$key] ?? 0; + $vip_minor = aksrt_asset_price_minor($value, $modo); + if ($vip_minor > 0 && ($base_minor <= 0 || $vip_minor < $base_minor)) { + $vip_items[] = '' . esc_html(sprintf(__('VIP%d', 'aksrt'), $level)) . '' . esc_html(aksrt_format_price($value)) . ''; + } + } + $vip = implode('', $vip_items); + if (count($vip_items) > 1) { + $vip = '' . $vip . ''; + } + $attrs = aksrt_get_pay_attributes_html($post, 'pay-attr'); + $count = $sales > 0 ? '' . esc_html(sprintf(__('已售 %s', 'aksrt'), $sales)) . '' : ''; + $service = aksrt_get_pay_service_html('muted-2-color'); + $demo = aksrt_get_demo_link_html($post, 'but jb-yellow padding-lg'); + $demo_row = $demo !== '' ? '
' . $demo . '
' : ''; + $discount = $modo === '0' ? aksrt_get_post_rebate_discount($post) : '0.00'; + $discount_text = aksrt_asset_price_minor($discount, '0') > 0 + ? sprintf(__('推广优惠 %s', 'aksrt'), (string) ($priced['currency'] ?? '¥') . aksrt_format_price($discount)) + : ''; + $discount_banner = $discount_text !== '' ? '
' . esc_html($discount_text) . '
' : ''; - ob_start(); - ?> -
-
-
- -
-
-

-

-
- - - 0) : ?> - - - - - - - -
-
-
- - 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) : ?> - - - - - - - - - - - - 0) : ?> - - -

- -

-
-
-
- '' . esc_html__('获取下载地址', 'aksrt'), + AKSRT_PAY_TYPE_GALLERY => '' . esc_html__('查看图片', 'aksrt'), + AKSRT_PAY_TYPE_VIDEO => '' . esc_html__('查看视频', 'aksrt'), + ]; + $view_label = $view_labels[$pay_type] ?? '' . esc_html__('查看内容', 'aksrt'); + return ''; + } + + if ($is_widget) { + return '
' . $tag . '
' . $price . '
' . $vip . '
' . $discount_banner . '
' . $service . '
' . $button . '
' . $demo_row . $attrs . '
' . $count . '
'; + } + + $thumb = get_the_post_thumbnail($post, 'medium', ['class' => 'fit-cover', 'alt' => '']); + if ($thumb === '') { + $thumb = '
'; + } + $graphic_actions = ''; + if ($demo !== '' || $discount_text !== '') { + $graphic_actions = '
'; + $graphic_actions .= $demo !== '' ? '
' . $demo . '
' : ''; + $graphic_actions .= $discount_text !== '' ? '' : ''; + $graphic_actions .= '
'; + } + $discount_mobile = $discount_text !== '' ? '
' . esc_html($discount_text) . '
' : ''; + if ($pay_type === AKSRT_PAY_TYPE_GALLERY) { + $gallery_count = count((array) ($meta['gallery'] ?? [])); + $graphic = '
' . $thumb . '
' . esc_html(sprintf(__('共%s张图片', 'aksrt'), $gallery_count)) . '
' . esc_html__('请付费后查看', 'aksrt') . '
' . $graphic_actions . '
'; + } elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) { + $graphic = '
' . $thumb . '
' . $graphic_actions . '
'; + } else { + $graphic = '
' . $thumb . $graphic_actions . '
'; + } + $demo_mobile = $demo !== '' ? '
' . $demo . '
' : ''; + return '
' . $graphic . '
' . esc_html($title) . '
' . wp_kses_post($doc) . '
' . $price . '
' . $discount_mobile . ($vip !== '' ? '
' . $vip . '
' : '') . '
' . $button . '
' . $service . '
' . $demo_mobile . $attrs . $tag . $count . '
'; } } @@ -1305,51 +1312,23 @@ if (!function_exists('aksrt_render_paid_gallery')) { $images = $meta['gallery'] ?? []; if (empty($images)) return ''; - $has_access = aksrt_has_paid_access($post); - $base_uri = get_template_directory_uri(); + if (!aksrt_has_paid_access($post)) return ''; ob_start(); ?> '; - } - $out .= aksrt_get_paywall_html($post); - - if ($pay_type === AKSRT_PAY_TYPE_GALLERY) { - $out .= aksrt_render_paid_gallery($post); - } elseif ($pay_type === AKSRT_PAY_TYPE_VIDEO) { - $out .= aksrt_render_paid_video($post); - } - - return $out; - } -} - -// 已付费的内容增强 -if (!function_exists('aksrt_paid_content_enhance')) { - function aksrt_paid_content_enhance(string $content): string { - if (is_admin() || !is_singular() || !in_the_loop() || !is_main_query()) { - return $content; - } - global $post; - if (!($post instanceof WP_Post)) { - return $content; - } - - $meta = aksrt_get_pay_meta($post); - $pay_type = (string) ($meta['pay_type'] ?? AKSRT_PAY_TYPE_NO); - if ($pay_type === AKSRT_PAY_TYPE_NO) { - return $content; - } - if (!aksrt_has_paid_access($post)) { - return $content; - } - - if ($pay_type === AKSRT_PAY_TYPE_GALLERY && strpos($content, 'aksrt-paid-gallery') === false) { - $gallery_html = aksrt_render_paid_gallery($post); - if ($gallery_html) { - $content .= $gallery_html; - } - } - - if ($pay_type === AKSRT_PAY_TYPE_VIDEO && strpos($content, 'aksrt-paid-video') === false) { - $video_html = aksrt_render_paid_video($post); - if ($video_html) { - $content .= $video_html; - } - } - - return $content; + return $preview !== '' ? '
' . wp_kses_post($preview) . '
' : ''; } } @@ -1511,20 +1421,10 @@ add_action('wp_head', function (): void { .aksrt-paid-gallery{margin:24px 0} .aksrt-paid-gallery__swiper{border-radius:var(--radius-lg);overflow:hidden} .aksrt-paid-gallery__swiper img{width:100%;height:auto;display:block} - .aksrt-paid-gallery__locked{position:relative;border-radius:var(--radius-lg);overflow:hidden} - .aksrt-paid-gallery__preview img{width:100%;display:block} - .aksrt-paid-gallery__overlay{position:absolute;inset:0;background:rgba(0,0,0,.5);display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;gap:12px} - .aksrt-paid-gallery__overlay i{font-size:48px;opacity:.8} - .aksrt-paid-gallery__overlay p{font-size:16px;margin:0} - .aksrt-paid-gallery__price{font-size:20px;font-weight:700;background:rgba(255,255,255,.2);padding:6px 16px;border-radius:20px} .aksrt-paid-video{margin:24px 0} .aksrt-paid-video__embed{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:var(--radius-lg)} .aksrt-paid-video__embed iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:none} - .aksrt-paid-video__locked{background:var(--surface-2);border-radius:var(--radius-lg);padding:60px 20px;text-align:center} - .aksrt-paid-video__placeholder i{font-size:64px;color:var(--color-ink-4);margin-bottom:16px} - .aksrt-paid-video__placeholder p{font-size:16px;color:var(--color-ink-3);margin:0 0 12px} - .aksrt-paid-video__price{font-size:20px;font-weight:700;color:var(--color-error)} '; - get_template_part('templates/parts/resource-download-box'); - echo ''; - return; - } - if (!function_exists('aksrt_get_pay_meta')) { return; } $has_access = function_exists('aksrt_has_paid_access') ? aksrt_has_paid_access($post) : false; - $paid_class = $has_access ? 'paid-box' : 'pay-box'; - echo '
'; if ($has_access) { - if ($pay_type === 'gallery' && function_exists('aksrt_render_paid_gallery')) { + if ($pay_type === 'download') { + get_template_part('templates/parts/resource-download-box'); + return; + } + $order_type = $pay_type === 'gallery' ? 5 : ($pay_type === 'video' ? 6 : 4); + echo ''; + } else { echo aksrt_get_paywall_html($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } - echo '
'; } endif; diff --git a/inc/helpers/aksrt-single.php b/inc/helpers/aksrt-single.php index 9d697691..7a2d7f4e 100644 --- a/inc/helpers/aksrt-single.php +++ b/inc/helpers/aksrt-single.php @@ -69,13 +69,12 @@ if (!function_exists('aksrt_single_build_context')) : } $show_cover = !empty($settings['show_cover']) && (string) ($settings['cover_url'] ?? '') !== ''; - $has_download = function_exists('aksrt_is_downloadable_content') && aksrt_is_downloadable_content($post_id); $pay_meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : ['pay_type' => 'no']; $pay_type = (string) ($pay_meta['pay_type'] ?? 'no'); $is_paid_post = function_exists('aksrt_is_paid_post') && aksrt_is_paid_post($post_id); // Paid-reading purchase is rendered inline by each payshow shortcode. // Keep the article-level module for download, gallery, video and VIP only. - $show_pay_box = $has_download || ($is_paid_post && !in_array($pay_type, ['no', 'download', 'reading'], true)); + $show_pay_box = $is_paid_post && !in_array($pay_type, ['no', 'reading'], true); $breadcrumbs_html = ''; if (!empty($settings['breadcrumbs']) && function_exists('aksrt_breadcrumbs')) { @@ -136,7 +135,6 @@ if (!function_exists('aksrt_single_build_context')) : 'front_content' => (string) ($settings['front_content'] ?? ''), 'after_content' => (string) ($settings['after_content'] ?? ''), 'content_maxheight' => !empty($settings['maxheight_enabled']) ? max(0, (int) ($settings['maxheight'] ?? 0)) : 0, - 'has_download' => $has_download, 'show_pay_box' => $show_pay_box, 'pay_type' => $pay_type, 'is_paid_post' => $is_paid_post, diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index 862ca5c8..4883c2b7 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -197,6 +197,7 @@ trait ScriptsPart1b { 'needs_cover' => 'single-post/cover', 'needs_related_toc' => 'single-post/related-toc', 'has_hidden_content' => 'single-post/hidden-content', + 'needs_pay_box' => 'pay-box', ]; $single_feature_handles = []; foreach ($single_features as $requirement => $part) { diff --git a/src/Core/ScriptsPart1c.php b/src/Core/ScriptsPart1c.php index 5353ee9f..7ccbb2f8 100644 --- a/src/Core/ScriptsPart1c.php +++ b/src/Core/ScriptsPart1c.php @@ -28,6 +28,7 @@ trait ScriptsPart1c { 'has_toc' => false, 'has_video' => false, 'has_hidden_content' => false, + 'needs_pay_box' => false, 'needs_cover' => false, 'needs_navigation' => false, 'needs_related_toc' => false, @@ -112,6 +113,7 @@ trait ScriptsPart1c { $requirements['has_toc'] = $is_article && $heading_count >= 2; $requirements['has_video'] = $has_video; $requirements['has_hidden_content'] = $has_hidden_content || $has_yiyan || $has_inserted_content; + $requirements['needs_pay_box'] = $is_paid_content_single || (bool) preg_match('/\[(?:payshow|hidecontent)\b|wp:aksrt\/paid-fragment/i', $content); $requirements['needs_cover'] = $is_article && ( $show_cover || !empty($settings['breadcrumbs']) diff --git a/src/Core/Shortcodes.php b/src/Core/Shortcodes.php index 9ffd5e7a..4196dbc7 100644 --- a/src/Core/Shortcodes.php +++ b/src/Core/Shortcodes.php @@ -120,22 +120,7 @@ class Shortcodes { } $message = $atts['message'] ?: __('此内容需付费后查看', 'aksrt'); - if (function_exists('aksrt_get_paywall_html')) { - $paywall = aksrt_get_paywall_html($post, true); - if ($message !== '') { - $paywall = str_replace( - '

', - '

' . esc_html($message) . ' ', - $paywall - ); - } - return $paywall; - } - - return '

' - . '
' - . esc_html($message) - . '
'; + return aksrt_get_paywall_html($post, ['message' => $message]); } /** diff --git a/src/WidgetSystem/AssetManager.php b/src/WidgetSystem/AssetManager.php index 888bcdbf..5851f7bb 100644 --- a/src/WidgetSystem/AssetManager.php +++ b/src/WidgetSystem/AssetManager.php @@ -23,6 +23,9 @@ final class AssetManager { public static function enqueue_for(WidgetDefinition $definition): void { self::register(); + if ($definition->slug() === 'post_pay') { + wp_enqueue_style('aksrt-pay-box'); + } foreach ($definition->assets() as $asset) { self::enqueue_optional($asset); } @@ -50,8 +53,10 @@ final class AssetManager { $root = get_template_directory(); $uri = get_template_directory_uri(); $css = $root . '/assets/widgets/frontend.css'; + $pay_css = $root . '/assets/css/frontend/pay-box.css'; $js = $root . '/assets/widgets/frontend.js'; wp_register_style(self::STYLE_HANDLE, $uri . '/assets/widgets/frontend.css', ['aksrt-public'], is_file($css) ? (string) filemtime($css) : null); + wp_register_style('aksrt-pay-box', $uri . '/assets/css/frontend/pay-box.css', ['aksrt-public'], is_file($pay_css) ? (string) filemtime($pay_css) : null); wp_register_script(self::SCRIPT_HANDLE, $uri . '/assets/widgets/frontend.js', ['aksrt-runtime'], is_file($js) ? (string) filemtime($js) : null, true); $third_party = $uri . '/assets/widgets/third-party/'; wp_register_script('aksrt-widget-hls', $third_party . 'hls.min.js', [], 'reference-pinned', true); diff --git a/src/WidgetSystem/FrontendRenderer.php b/src/WidgetSystem/FrontendRenderer.php index 00370fed..97103773 100644 --- a/src/WidgetSystem/FrontendRenderer.php +++ b/src/WidgetSystem/FrontendRenderer.php @@ -23,9 +23,9 @@ final class FrontendRenderer { } AssetManager::enqueue_for($definition); - if (in_array($definition->slug(), ['tab_posts', 'slider', 'slider_media_combo', 'icon_cards', 'icon_cover_cards', 'buttons', 'notice'], true)) { + if (in_array($definition->slug(), ['post_pay', 'tab_posts', 'slider', 'slider_media_combo', 'icon_cards', 'icon_cover_cards', 'buttons', 'notice'], true)) { echo (string) ($args['before_widget'] ?? ''); - echo self::reference_title($instance); + if ($definition->slug() !== 'post_pay') echo self::reference_title($instance); echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo (string) ($args['after_widget'] ?? ''); return; diff --git a/src/WidgetSystem/Renderers/ContentRenderer.php b/src/WidgetSystem/Renderers/ContentRenderer.php index 8a631eb3..459083c0 100644 --- a/src/WidgetSystem/Renderers/ContentRenderer.php +++ b/src/WidgetSystem/Renderers/ContentRenderer.php @@ -145,14 +145,11 @@ final class ContentRenderer { } $meta = aksrt_get_pay_meta($post); if (($meta['pay_type'] ?? 'no') === 'no') { - return RendererHelpers::empty_state(__('当前文章未配置付费内容', 'aksrt')); + return ''; } - if (function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($post)) { - $html = '
' . esc_html__('内容已解锁', 'aksrt') . '

' . esc_html__('你已拥有当前知识内容的访问权限。', 'aksrt') . '

'; - $html .= function_exists('aksrt_get_pay_attributes_html') ? aksrt_get_pay_attributes_html($post, 'aksrt-post-pay__attrs') : ''; - $html .= function_exists('aksrt_get_demo_link_html') ? aksrt_get_demo_link_html($post, 'aksrt-widget-button') : ''; - return $html . '
'; - } - return function_exists('aksrt_get_paywall_html') ? aksrt_get_paywall_html($post, true) : RendererHelpers::empty_state(__('请登录或购买后查看', 'aksrt')); + return aksrt_get_paywall_html($post, [ + 'layout' => 'widget', + 'theme' => (string) $instance['theme'], + ]); } } diff --git a/tests/widget-system/pay-card-contract.test.cjs b/tests/widget-system/pay-card-contract.test.cjs new file mode 100644 index 00000000..2734a3ad --- /dev/null +++ b/tests/widget-system/pay-card-contract.test.cjs @@ -0,0 +1,53 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); + +const root = path.resolve(__dirname, '..', '..'); +const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); + +test('paid article and sidebar widget use the Zibll purchase-card DOM', () => { + const pay = read('inc/helpers/aksrt-pay.php'); + const widget = read('src/WidgetSystem/Renderers/ContentRenderer.php'); + const shell = read('src/WidgetSystem/FrontendRenderer.php'); + const single = read('inc/helpers/aksrt-single-render.php'); + const hooks = read('inc/core-hooks/part-3.php'); + + assert.match(pay, /zib-widget pay-box pay-widget/); + assert.match(pay, /zib-widget pay-box order-type-/); + assert.match(pay, /flex pay-flexbox/); + assert.match(pay, /hide-sm pay-thumb/); + assert.match(pay, /pay-tag abs-center/); + assert.match(pay, /price-box/); + assert.match(pay, /vip-price-buts but-average inline/); + assert.match(pay, /cashier-link/); + assert.match(widget, /'layout' => 'widget'/); + assert.match(shell, /\['post_pay', 'tab_posts'/); + assert.doesNotMatch(single, /aksrt-panel pay-box pay-type-download/); + assert.doesNotMatch(hooks, /aksrt_paid_content_enhance/); +}); + +test('the purchase card owns one direct stylesheet and no retired paywall path', () => { + const pay = read('inc/helpers/aksrt-pay.php'); + const shortcodes = read('src/Core/Shortcodes.php'); + const assets = read('src/Core/ScriptsPart1b.php') + read('src/WidgetSystem/AssetManager.php'); + const hidden = read('assets/css/frontend/single-post/hidden-content.css'); + const widgets = read('assets/widgets/frontend.css'); + const css = read('assets/css/frontend/pay-box.css'); + const publicCss = read('assets/css/main.css'); + + assert.match(assets, /assets\/css\/frontend\/pay-box\.css|\'pay-box\'/); + assert.match(shortcodes, /return aksrt_get_paywall_html\(\$post, \['message' => \$message\]\);/); + assert.match(css, /\.zib-widget\.pay-box/); + assert.doesNotMatch(css, /@media/); + assert.match(publicCss, /\.pay-box\.pay-widget \.vip-price/); + assert.match(publicCss, /\.pay-flexbox\s*\{\s*flex-wrap:\s*wrap;/); + + const retired = pay + hidden + widgets + shortcodes; + assert.doesNotMatch(retired, /aksrt-paywall__(?:card|icon|body|title|desc|price|actions|btn|hint|login-link)/); + assert.doesNotMatch(retired, /aksrt-post-pay/); + assert.doesNotMatch(retired, /str_replace\([\s\S]*aksrt-paywall__desc/); + assert.doesNotMatch(pay, /aksrt-paid-(?:gallery|video)__(?:locked|preview|overlay|placeholder|price)/); +});