导入 AKSRT 主题开发源码
This commit is contained in:
1 parent
9681dd3d48
commit
1268a55dd0
595 files changed
+127705
No files matched your search
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Credits Helper
|
||||
*
|
||||
* 模板层使用的顶层辅助函数:
|
||||
* aksrt_user_credits($user_id = 0) 读取余额
|
||||
* aksrt_format_credits(int $n) 格式化展示
|
||||
* aksrt_credits_label(string $reason) 流水原因 → 友好文案
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_user_credits')) {
|
||||
/**
|
||||
* 取用户当前积分余额。
|
||||
*
|
||||
* @param int $user_id 0 表示当前登录用户
|
||||
* @return int
|
||||
*/
|
||||
function aksrt_user_credits(int $user_id = 0): int {
|
||||
if ($user_id <= 0) $user_id = get_current_user_id();
|
||||
if ($user_id <= 0) return 0;
|
||||
if (!class_exists('AKSRT_Credits')) return 0;
|
||||
return AKSRT_Credits::balance($user_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_format_credits')) {
|
||||
function aksrt_format_credits(int $n): string {
|
||||
return number_format_i18n($n);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_credits_label')) {
|
||||
/**
|
||||
* 把 reason 字段翻译成展示文案。
|
||||
*/
|
||||
function aksrt_credits_label(string $reason): string {
|
||||
$map = [
|
||||
'register' => __('注册赠送', 'aksrt'),
|
||||
'signin' => __('每日签到', 'aksrt'),
|
||||
'comment' => __('发表评论', 'aksrt'),
|
||||
'invite' => __('邀请奖励', 'aksrt'),
|
||||
'invitee' => __('被邀请奖励', 'aksrt'),
|
||||
'download' => __('下载消费', 'aksrt'),
|
||||
'admin' => __('管理员调整', 'aksrt'),
|
||||
'refund' => __('退款', 'aksrt'),
|
||||
];
|
||||
return $map[$reason] ?? $reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Domain Model Registry
|
||||
*
|
||||
* Canonical keys, defaults, and state machines shared by helpers and services.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Domain_Model {
|
||||
|
||||
public const POST_META_KEY = 'aksrt_post_meta';
|
||||
public const TERM_META_KEY = 'aksrt_term_meta';
|
||||
|
||||
public const PAY_META_KEY = '_aksrt_pay';
|
||||
public const PAY_TYPE_KEY = '_aksrt_pay_type';
|
||||
public const PAY_PRICE_KEY = '_aksrt_pay_price';
|
||||
|
||||
public const RESOURCE_MARKER_KEY = '_aksrt_is_resource';
|
||||
public const RESOURCE_DOWNLOAD_COUNT_KEY = '_aksrt_res_download_count';
|
||||
public const RESOURCE_VIEW_COUNT_KEY = '_aksrt_res_view_count';
|
||||
public const POST_VIEW_COUNT_KEY = 'aksrt_post_views';
|
||||
|
||||
public const ORDER_MIGRATED_OPTION = 'aksrt_order_migrated_to_table';
|
||||
public const RESOURCE_PAY_META_MIGRATED = 'aksrt_resource_pay_meta_migrated';
|
||||
|
||||
public const ORDER_PRODUCT_RESOURCE = 'resource';
|
||||
public const ORDER_PRODUCT_VIP = 'vip';
|
||||
public const ORDER_PRODUCT_RECHARGE = 'recharge';
|
||||
public const ORDER_PRODUCT_SHOP = 'shop_product';
|
||||
|
||||
public const PAY_TYPE_NO = 'no';
|
||||
public const PAY_TYPE_READING = 'reading';
|
||||
public const PAY_TYPE_DOWNLOAD = 'download';
|
||||
public const PAY_TYPE_GALLERY = 'gallery';
|
||||
public const PAY_TYPE_VIDEO = 'video';
|
||||
public const PAY_TYPE_VIP = 'vip';
|
||||
|
||||
public const ORDER_STATUS_CREATED = 'created';
|
||||
public const ORDER_STATUS_PENDING = 'pending';
|
||||
public const ORDER_STATUS_PAID = 'paid';
|
||||
public const ORDER_STATUS_DELIVERED = 'delivered';
|
||||
public const ORDER_STATUS_COMPLETED = 'completed';
|
||||
public const ORDER_STATUS_CANCELLED = 'cancelled';
|
||||
public const ORDER_STATUS_EXPIRED = 'expired';
|
||||
public const ORDER_STATUS_REFUNDING = 'refunding';
|
||||
public const ORDER_STATUS_REFUNDED = 'refunded';
|
||||
public const ORDER_STATUS_PENDING_REVIEW = 'pending_review';
|
||||
|
||||
public const SHIPPING_STATUS_PENDING = 0;
|
||||
public const SHIPPING_STATUS_SHIPPED = 1;
|
||||
public const SHIPPING_STATUS_COMPLETED = 2;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function default_post_meta(): array {
|
||||
return [
|
||||
'cover_images' => '',
|
||||
'custom_excerpt' => '',
|
||||
'layout_override' => '',
|
||||
'header_image_mode' => '',
|
||||
'show_toc' => '',
|
||||
'show_reading_progress' => '',
|
||||
'enable_lightbox' => '',
|
||||
'show_related' => '',
|
||||
'related_count' => 0,
|
||||
'enable_like' => '',
|
||||
'enable_favorite' => '',
|
||||
'enable_share' => '',
|
||||
'enable_print' => '',
|
||||
'enable_pdf' => '',
|
||||
'copyright_text' => '',
|
||||
'is_featured' => false,
|
||||
// §3.8 文章扩展字段
|
||||
'thumbnail_url' => '',
|
||||
'featured_slide' => '',
|
||||
'featured_video' => '',
|
||||
'featured_video_episode' => 0,
|
||||
'subtitle' => '',
|
||||
'views' => 0,
|
||||
'likes' => 0,
|
||||
'maxheight' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function default_term_meta(): array {
|
||||
return [
|
||||
'cover_url' => '',
|
||||
'description' => '',
|
||||
'layout_mode' => '',
|
||||
'show_sidebar' => '',
|
||||
'show_header' => true,
|
||||
'show_featured' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function default_pay_meta(): array {
|
||||
return [
|
||||
'pay_type' => self::PAY_TYPE_NO,
|
||||
'price' => 0.0,
|
||||
'original_price' => 0.0,
|
||||
'vip1_price' => 0.0,
|
||||
'vip2_price' => 0.0,
|
||||
'version' => '',
|
||||
'size' => '',
|
||||
'license' => '',
|
||||
'compat' => '',
|
||||
'demo_url' => '',
|
||||
'video_url' => '',
|
||||
'downloads' => [],
|
||||
'attributes' => [],
|
||||
'gallery' => [],
|
||||
'require_login' => false,
|
||||
'require_captcha' => false,
|
||||
// §3.8 电商增强字段
|
||||
'virtual_sales' => 0,
|
||||
'stock' => -1,
|
||||
'card_keys' => [],
|
||||
'purchase_limit' => 0,
|
||||
'free_preview' => '',
|
||||
'show_sales' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function pay_type_labels(): array {
|
||||
return [
|
||||
self::PAY_TYPE_NO => '不付费',
|
||||
self::PAY_TYPE_READING => '付费阅读',
|
||||
self::PAY_TYPE_DOWNLOAD => '付费下载',
|
||||
self::PAY_TYPE_GALLERY => '付费图库',
|
||||
self::PAY_TYPE_VIDEO => '付费视频',
|
||||
self::PAY_TYPE_VIP => 'VIP 专属',
|
||||
];
|
||||
}
|
||||
|
||||
public static function normalize_pay_type(string $pay_type): string {
|
||||
$pay_type = sanitize_key($pay_type);
|
||||
return array_key_exists($pay_type, self::pay_type_labels()) ? $pay_type : self::PAY_TYPE_NO;
|
||||
}
|
||||
|
||||
public static function is_resource_pay_type(string $pay_type): bool {
|
||||
return self::normalize_pay_type($pay_type) === self::PAY_TYPE_DOWNLOAD;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
public static function sanitize_downloads($value): array {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$allowed_types = ['direct', 'net_disk', 'external'];
|
||||
$downloads = [];
|
||||
|
||||
foreach ($value as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = isset($row['url']) ? esc_url_raw((string) $row['url']) : '';
|
||||
if ($url === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = isset($row['type']) && in_array($row['type'], $allowed_types, true)
|
||||
? (string) $row['type']
|
||||
: 'direct';
|
||||
|
||||
$extract_code = '';
|
||||
foreach ([
|
||||
'extract_code',
|
||||
'extractCode',
|
||||
'extract',
|
||||
'extract_key',
|
||||
'extraction_code',
|
||||
'extractionCode',
|
||||
'code',
|
||||
'password',
|
||||
'pwd',
|
||||
'pan_code',
|
||||
'panCode',
|
||||
'pan_password',
|
||||
'panPassword',
|
||||
'access_code',
|
||||
'accessCode',
|
||||
] as $extract_key) {
|
||||
if (isset($row[$extract_key]) && !is_array($row[$extract_key])) {
|
||||
$candidate = trim((string) $row[$extract_key]);
|
||||
if ($candidate !== '') {
|
||||
$extract_code = sanitize_text_field($candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$downloads[] = [
|
||||
'label' => isset($row['label']) ? sanitize_text_field((string) $row['label']) : '',
|
||||
'url' => $url,
|
||||
'type' => $type,
|
||||
'extract_code' => $extract_code,
|
||||
];
|
||||
}
|
||||
|
||||
return array_values($downloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
public static function sanitize_attributes($value): array {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
|
||||
foreach ($value as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = sanitize_text_field((string) ($row['key'] ?? ''));
|
||||
$item_value = sanitize_text_field((string) ($row['value'] ?? ''));
|
||||
|
||||
if ($key === '' && $item_value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes[] = [
|
||||
'key' => $key,
|
||||
'value' => $item_value,
|
||||
];
|
||||
}
|
||||
|
||||
return array_values($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return string[]
|
||||
*/
|
||||
public static function sanitize_gallery($value): array {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$gallery = [];
|
||||
foreach ($value as $url) {
|
||||
$url = esc_url_raw((string) $url);
|
||||
if ($url !== '') {
|
||||
$gallery[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($gallery));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize card/key codes for auto-delivery.
|
||||
* Each entry is a non-empty sanitized string (one code per line).
|
||||
*/
|
||||
public static function sanitize_card_keys($value): array {
|
||||
if (!is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
foreach ($value as $key) {
|
||||
$key = sanitize_text_field((string) $key);
|
||||
if ($key !== '') {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function sanitize_pay_meta(array $raw): array {
|
||||
$meta = self::default_pay_meta();
|
||||
$meta['pay_type'] = self::normalize_pay_type((string) ($raw['pay_type'] ?? $meta['pay_type']));
|
||||
$meta['price'] = max(0, (float) ($raw['price'] ?? 0));
|
||||
$meta['original_price'] = max(0, (float) ($raw['original_price'] ?? 0));
|
||||
$meta['vip1_price'] = max(0, (float) ($raw['vip1_price'] ?? 0));
|
||||
$meta['vip2_price'] = max(0, (float) ($raw['vip2_price'] ?? 0));
|
||||
$meta['version'] = sanitize_text_field((string) ($raw['version'] ?? ''));
|
||||
$meta['size'] = sanitize_text_field((string) ($raw['size'] ?? ''));
|
||||
$meta['license'] = sanitize_text_field((string) ($raw['license'] ?? ''));
|
||||
$meta['compat'] = sanitize_text_field((string) ($raw['compat'] ?? ''));
|
||||
$meta['demo_url'] = esc_url_raw((string) ($raw['demo_url'] ?? ''));
|
||||
$meta['video_url'] = esc_url_raw((string) ($raw['video_url'] ?? ''));
|
||||
$meta['downloads'] = self::sanitize_downloads($raw['downloads'] ?? []);
|
||||
$meta['attributes'] = self::sanitize_attributes($raw['attributes'] ?? []);
|
||||
$meta['gallery'] = self::sanitize_gallery($raw['gallery'] ?? []);
|
||||
$meta['require_login'] = !empty($raw['require_login']);
|
||||
$meta['require_captcha'] = !empty($raw['require_captcha']);
|
||||
|
||||
// §3.8 电商增强字段
|
||||
$meta['virtual_sales'] = max(0, (int) ($raw['virtual_sales'] ?? 0));
|
||||
$meta['stock'] = (int) ($raw['stock'] ?? -1);
|
||||
$meta['card_keys'] = self::sanitize_card_keys($raw['card_keys'] ?? []);
|
||||
$meta['purchase_limit'] = max(0, (int) ($raw['purchase_limit'] ?? 0));
|
||||
$meta['free_preview'] = wp_kses_post((string) ($raw['free_preview'] ?? ''));
|
||||
$meta['show_sales'] = !isset($raw['show_sales']) || !empty($raw['show_sales']);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function order_status_labels(): array {
|
||||
return [
|
||||
self::ORDER_STATUS_CREATED => __('已创建', 'aksrt'),
|
||||
self::ORDER_STATUS_PENDING => __('待支付', 'aksrt'),
|
||||
self::ORDER_STATUS_PAID => __('已支付', 'aksrt'),
|
||||
self::ORDER_STATUS_DELIVERED => __('已发货', 'aksrt'),
|
||||
self::ORDER_STATUS_COMPLETED => __('已完成', 'aksrt'),
|
||||
self::ORDER_STATUS_CANCELLED => __('已取消', 'aksrt'),
|
||||
self::ORDER_STATUS_EXPIRED => __('已过期', 'aksrt'),
|
||||
self::ORDER_STATUS_REFUNDING => __('退款中', 'aksrt'),
|
||||
self::ORDER_STATUS_REFUNDED => __('已退款', 'aksrt'),
|
||||
self::ORDER_STATUS_PENDING_REVIEW => __('待评价', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string[]>
|
||||
*/
|
||||
public static function order_transitions(): array {
|
||||
return [
|
||||
self::ORDER_STATUS_CREATED => [
|
||||
self::ORDER_STATUS_PENDING,
|
||||
self::ORDER_STATUS_CANCELLED,
|
||||
],
|
||||
self::ORDER_STATUS_PENDING => [
|
||||
self::ORDER_STATUS_PAID,
|
||||
self::ORDER_STATUS_CANCELLED,
|
||||
self::ORDER_STATUS_EXPIRED,
|
||||
],
|
||||
self::ORDER_STATUS_PAID => [
|
||||
self::ORDER_STATUS_DELIVERED,
|
||||
self::ORDER_STATUS_COMPLETED,
|
||||
self::ORDER_STATUS_REFUNDING,
|
||||
],
|
||||
self::ORDER_STATUS_DELIVERED => [
|
||||
self::ORDER_STATUS_COMPLETED,
|
||||
self::ORDER_STATUS_PENDING_REVIEW,
|
||||
self::ORDER_STATUS_REFUNDING,
|
||||
],
|
||||
self::ORDER_STATUS_COMPLETED => [
|
||||
self::ORDER_STATUS_PENDING_REVIEW,
|
||||
self::ORDER_STATUS_REFUNDING,
|
||||
],
|
||||
self::ORDER_STATUS_CANCELLED => [],
|
||||
self::ORDER_STATUS_EXPIRED => [
|
||||
self::ORDER_STATUS_PENDING,
|
||||
],
|
||||
self::ORDER_STATUS_REFUNDING => [
|
||||
self::ORDER_STATUS_REFUNDED,
|
||||
self::ORDER_STATUS_PAID,
|
||||
],
|
||||
self::ORDER_STATUS_REFUNDED => [],
|
||||
self::ORDER_STATUS_PENDING_REVIEW => [
|
||||
self::ORDER_STATUS_COMPLETED,
|
||||
self::ORDER_STATUS_REFUNDING,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function normalize_order_status(string $status, string $default = self::ORDER_STATUS_PENDING): string {
|
||||
$status = sanitize_key($status);
|
||||
return array_key_exists($status, self::order_status_labels()) ? $status : $default;
|
||||
}
|
||||
|
||||
public static function can_transition_order_status(string $from, string $to): bool {
|
||||
$from = self::normalize_order_status($from, self::ORDER_STATUS_CREATED);
|
||||
$to = self::normalize_order_status($to);
|
||||
$transitions = self::order_transitions();
|
||||
|
||||
return isset($transitions[$from]) && in_array($to, $transitions[$from], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function paid_order_statuses(): array {
|
||||
return [
|
||||
self::ORDER_STATUS_PAID,
|
||||
self::ORDER_STATUS_DELIVERED,
|
||||
self::ORDER_STATUS_COMPLETED,
|
||||
];
|
||||
}
|
||||
|
||||
public static function is_paid_order_status(string $status): bool {
|
||||
return in_array(self::normalize_order_status($status), self::paid_order_statuses(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function vip_active_order_statuses(): array {
|
||||
return self::paid_order_statuses();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Flow Helper
|
||||
*
|
||||
* v4 起主题统一为 CMS / 资源站形态,不再提供 blog 形态分流。
|
||||
* 历史 helper 全部保留签名以保证兼容,但实际行为已收敛:
|
||||
*
|
||||
* aksrt_get_site_profile() 始终返回 'cms'
|
||||
* aksrt_get_term_flow() 始终返回 'inherit'(不再读取 term meta)
|
||||
* aksrt_is_resource_post() 仍按 _aksrt_is_resource meta 判定
|
||||
* aksrt_get_post_flow() 始终返回 'cms'
|
||||
* aksrt_is_cms_mode() 始终返回 true
|
||||
* aksrt_flow_layout_for() 始终返回 'grid-cms'
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_site_profile')) {
|
||||
/**
|
||||
* @deprecated v4 起恒为 'cms',保留仅为向后兼容。
|
||||
* @return string 'cms'
|
||||
*/
|
||||
function aksrt_get_site_profile(): string {
|
||||
return 'cms';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_term_flow')) {
|
||||
/**
|
||||
* @deprecated v4 起恒为 'inherit',term meta 不再生效。
|
||||
* @param int $term_id
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_term_flow(int $term_id): string {
|
||||
unset($term_id);
|
||||
return 'inherit';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_resource_post')) {
|
||||
/**
|
||||
* 是否为资源(post + _aksrt_is_resource meta,或历史 aksrt_resource CPT)。
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_resource_post($post = null): bool {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) return false;
|
||||
if ($p->post_type === 'aksrt_resource') return true;
|
||||
if ($p->post_type !== 'post') return false;
|
||||
return (bool) get_post_meta($p->ID, '_aksrt_is_resource', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_post_flow')) {
|
||||
/**
|
||||
* @deprecated v4 起恒为 'cms'。
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @return string 'cms'
|
||||
*/
|
||||
function aksrt_get_post_flow($post = null): string {
|
||||
unset($post);
|
||||
return 'cms';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_cms_mode')) {
|
||||
/**
|
||||
* 当前请求是否处在 CMS 形态。v4 起全站统一 CMS。
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_cms_mode(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_flow_layout_for')) {
|
||||
/**
|
||||
* @deprecated v4 起恒为 'grid-cms'。
|
||||
*
|
||||
* @param string $flow
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_flow_layout_for(string $flow): string {
|
||||
unset($flow);
|
||||
return 'grid-cms';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT i18n Switch — 多语言切换辅助函数
|
||||
*
|
||||
* 支持 Polylang、WPML 或 WordPress 内置语言切换。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 获取可用语言列表
|
||||
*
|
||||
* @return array<array{code: string, name: string, url: string, flag: string, active: bool}>
|
||||
*/
|
||||
function aksrt_get_available_languages(): array {
|
||||
$languages = [];
|
||||
|
||||
// Polylang
|
||||
if (function_exists('pll_the_languages')) {
|
||||
$pll_langs = pll_the_languages(['raw' => 1]);
|
||||
if (is_array($pll_langs)) {
|
||||
foreach ($pll_langs as $lang) {
|
||||
$languages[] = [
|
||||
'code' => $lang['slug'] ?? '',
|
||||
'name' => $lang['name'] ?? '',
|
||||
'url' => $lang['url'] ?? '#',
|
||||
'flag' => $lang['flag'] ?? '',
|
||||
'active' => !empty($lang['current_lang']),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $languages;
|
||||
}
|
||||
|
||||
// WPML
|
||||
if (defined('ICL_LANGUAGE_CODE') && function_exists('icl_get_languages')) {
|
||||
$wpml_langs = icl_get_languages('skip_missing=0');
|
||||
if (is_array($wpml_langs)) {
|
||||
foreach ($wpml_langs as $lang) {
|
||||
$languages[] = [
|
||||
'code' => $lang['language_code'] ?? '',
|
||||
'name' => $lang['native_name'] ?? '',
|
||||
'url' => $lang['url'] ?? '#',
|
||||
'flag' => $lang['country_flag_url'] ?? '',
|
||||
'active' => !empty($lang['active']),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $languages;
|
||||
}
|
||||
|
||||
// WordPress 内置:通过 filter 提供语言列表
|
||||
$filtered = apply_filters('aksrt_available_languages', []);
|
||||
if (is_array($filtered) && !empty($filtered)) {
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有多语言插件可用
|
||||
*/
|
||||
function aksrt_has_multilingual(): bool {
|
||||
if (function_exists('pll_the_languages')) return true;
|
||||
if (defined('ICL_LANGUAGE_CODE')) return true;
|
||||
if (!empty(apply_filters('aksrt_available_languages', []))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前语言代码
|
||||
*/
|
||||
function aksrt_get_current_language(): string {
|
||||
if (function_exists('pll_current_language')) {
|
||||
return pll_current_language('slug');
|
||||
}
|
||||
if (defined('ICL_LANGUAGE_CODE')) {
|
||||
return ICL_LANGUAGE_CODE;
|
||||
}
|
||||
return get_locale();
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染语言切换器 HTML
|
||||
*
|
||||
* @param string $style dropdown | inline | flags
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_language_switcher(string $style = 'dropdown'): string {
|
||||
$languages = aksrt_get_available_languages();
|
||||
if (count($languages) < 2) return '';
|
||||
|
||||
$current = aksrt_get_current_language();
|
||||
|
||||
if ($style === 'flags') {
|
||||
$html = '<div class="aksrt-lang-switch aksrt-lang-switch--flags">';
|
||||
foreach ($languages as $lang) {
|
||||
$active_class = $lang['active'] ? ' is-active' : '';
|
||||
$html .= '<a href="' . esc_url($lang['url']) . '" class="aksrt-lang-switch__flag' . esc_attr($active_class) . '" title="' . esc_attr($lang['name']) . '">';
|
||||
if ($lang['flag']) {
|
||||
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="' . esc_attr($lang['code']) . '" width="20" height="14">';
|
||||
} else {
|
||||
$html .= '<span>' . esc_html(strtoupper($lang['code'])) . '</span>';
|
||||
}
|
||||
$html .= '</a>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
// dropdown style
|
||||
$html = '<div class="aksrt-lang-switch">';
|
||||
$html .= '<button type="button" class="aksrt-lang-switch__toggle" aria-haspopup="true" aria-expanded="false">';
|
||||
|
||||
// 当前语言
|
||||
foreach ($languages as $lang) {
|
||||
if ($lang['active'] || $lang['code'] === $current) {
|
||||
if ($lang['flag']) {
|
||||
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="" width="18" height="12"> ';
|
||||
}
|
||||
$html .= '<span>' . esc_html($lang['name']) . '</span>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$html .= ' <i class="fas fa-chevron-down" style="font-size:10px;margin-left:4px;"></i>';
|
||||
$html .= '</button>';
|
||||
|
||||
$html .= '<ul class="aksrt-lang-switch__dropdown" role="menu">';
|
||||
foreach ($languages as $lang) {
|
||||
$active_class = $lang['active'] ? ' is-active' : '';
|
||||
$html .= '<li role="menuitem">';
|
||||
$html .= '<a href="' . esc_url($lang['url']) . '" class="aksrt-lang-switch__item' . esc_attr($active_class) . '">';
|
||||
if ($lang['flag']) {
|
||||
$html .= '<img src="' . esc_url($lang['flag']) . '" alt="" width="18" height="12"> ';
|
||||
}
|
||||
$html .= esc_html($lang['name']);
|
||||
$html .= '</a>';
|
||||
$html .= '</li>';
|
||||
}
|
||||
$html .= '</ul>';
|
||||
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Theme - SVG Icons Module
|
||||
*
|
||||
* 本地 SVG 图标读取/缓存、图标 key 映射显示名称、
|
||||
* 联系方式图标下拉选项渲染
|
||||
*
|
||||
* @package AKSRT
|
||||
* @version 3.1.0
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// PHP 7.x 兼容辅助函数(替代 PHP 8.0+ 的 match 表达式)
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_parse_country_list')):
|
||||
/**
|
||||
* 解析 GeoIP 屏蔽国家列表字符串为数组
|
||||
*
|
||||
* @param string $raw 逗号分隔的国家代码
|
||||
* @return array 大写国家代码数组
|
||||
*/
|
||||
function aksrt_parse_country_list($raw) {
|
||||
if (empty($raw)) return array();
|
||||
$parts = explode(',', wp_unslash($raw));
|
||||
$result = array();
|
||||
foreach ($parts as $p) {
|
||||
$trimmed = trim($p);
|
||||
if ($trimmed !== '') {
|
||||
$result[] = strtoupper($trimmed);
|
||||
}
|
||||
}
|
||||
return array_values(array_filter($result));
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 本地 SVG 图标:读取 SVGs 目录并缓存
|
||||
// ============================================================
|
||||
if (!function_exists('aksrt_inline_svg')) :
|
||||
function aksrt_inline_svg($name) {
|
||||
// 兼容 FA5 旧数据:fab fa-github / fabfa-github / fa-github → github
|
||||
$name = preg_replace('/^(fab|fas|far)[\s-]*/', '', $name);
|
||||
$name = preg_replace('/^fa[\s-]*/', '', $name);
|
||||
$name = preg_replace('/^[\s-]+/', '', $name);
|
||||
$name = trim($name, '- ');
|
||||
static $cache = array();
|
||||
if (isset($cache[$name])) return $cache[$name];
|
||||
$path = AKSRT_DIR . '/assets/img/icons/' . $name . '.svg';
|
||||
if (file_exists($path)) {
|
||||
$svg = file_get_contents($path);
|
||||
// 去除 XML 声明和 DOCTYPE
|
||||
$svg = preg_replace('/^<\?xml.*?\?>\s*/i', '', $svg);
|
||||
$svg = preg_replace('/^<!DOCTYPE[^>]*>\s*/i', '', $svg);
|
||||
// 移除 <svg> 上的固定宽高,让 CSS 控制尺寸
|
||||
$svg = preg_replace('/\swidth="[^"]*"/', '', $svg);
|
||||
$svg = preg_replace('/\sheight="[^"]*"/', '', $svg);
|
||||
// 移除 iconfont 冗余属性
|
||||
$svg = preg_replace('/\sclass="[^"]*"/', '', $svg);
|
||||
$svg = preg_replace('/\st="[^"]*"/', '', $svg);
|
||||
$svg = preg_replace('/\sp-id="[^"]*"/', '', $svg);
|
||||
$svg = preg_replace('/\sxmlns:xlink="[^"]*"/', '', $svg);
|
||||
// 移除路径上的 opacity 属性
|
||||
$svg = preg_replace('/(<(?:path|circle|rect|polygon|line|polyline|ellipse)[^>]*)\sopacity="[^"]*"/', '$1', $svg);
|
||||
$cache[$name] = $svg;
|
||||
return $svg;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 图标 key → 显示名称
|
||||
// ============================================================
|
||||
if (!function_exists('aksrt_icon_key_to_name')) :
|
||||
function aksrt_icon_key_to_name($key) {
|
||||
$map = array(
|
||||
'github' => 'GitHub', 'google' => 'Google', 'x' => 'X',
|
||||
'email' => '邮箱', 'wechat' => '微信', 'qq' => 'QQ',
|
||||
'weibo' => '微博', 'telegram' => 'Telegram',
|
||||
'discord' => 'Discord', 'bilibili' => 'Bilibili',
|
||||
'linkedin' => 'LinkedIn', 'facebook' => 'Facebook',
|
||||
'youtube' => 'YouTube', 'instagram' => 'Instagram',
|
||||
'web' => '网站',
|
||||
);
|
||||
return isset($map[$key]) ? $map[$key] : ucwords(str_replace('-', ' ', $key));
|
||||
}
|
||||
endif;
|
||||
|
||||
// ============================================================
|
||||
// 联系方式图标选项(仅保留选定的 15 个)
|
||||
// ============================================================
|
||||
if (!function_exists('aksrt_contact_icon_options')) :
|
||||
function aksrt_contact_icon_options($selected = '') {
|
||||
$icons = array(
|
||||
'github' => 'GitHub',
|
||||
'google' => 'Google',
|
||||
'x' => 'X',
|
||||
'email' => '邮箱',
|
||||
'wechat' => '微信',
|
||||
'qq' => 'QQ',
|
||||
'weibo' => '微博',
|
||||
'telegram' => 'Telegram',
|
||||
'discord' => 'Discord',
|
||||
'bilibili' => 'Bilibili',
|
||||
'linkedin' => 'LinkedIn',
|
||||
'facebook' => 'Facebook',
|
||||
'youtube' => 'YouTube',
|
||||
'instagram' => 'Instagram',
|
||||
'web' => '网站',
|
||||
);
|
||||
$html = '<option value="">—</option>';
|
||||
foreach ($icons as $key => $label) {
|
||||
$sel = $key === $selected ? ' selected' : '';
|
||||
$html .= '<option value="' . esc_attr($key) . '"' . $sel . '>' . esc_html($label) . '</option>';
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
@@ -0,0 +1,629 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Pay System — 统一付费内容系统(1:1 对齐 zibll)
|
||||
*
|
||||
* 核心机制:
|
||||
* - 任何 post 通过 _aksrt_pay meta 成为付费文章
|
||||
* - pay_type 驱动行为(no/reading/download/gallery/video/vip)
|
||||
* - 统一的价格、VIP 价、下载源、属性、图库、视频管理
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 付费类型常量
|
||||
// ============================================================
|
||||
|
||||
if (!defined('AKSRT_PAY_TYPE_NO')) {
|
||||
define('AKSRT_PAY_TYPE_NO', AKSRT_Domain_Model::PAY_TYPE_NO);
|
||||
}
|
||||
if (!defined('AKSRT_PAY_TYPE_READING')) {
|
||||
define('AKSRT_PAY_TYPE_READING', AKSRT_Domain_Model::PAY_TYPE_READING);
|
||||
}
|
||||
if (!defined('AKSRT_PAY_TYPE_DOWNLOAD')) {
|
||||
define('AKSRT_PAY_TYPE_DOWNLOAD', AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD);
|
||||
}
|
||||
if (!defined('AKSRT_PAY_TYPE_GALLERY')) {
|
||||
define('AKSRT_PAY_TYPE_GALLERY', AKSRT_Domain_Model::PAY_TYPE_GALLERY);
|
||||
}
|
||||
if (!defined('AKSRT_PAY_TYPE_VIDEO')) {
|
||||
define('AKSRT_PAY_TYPE_VIDEO', AKSRT_Domain_Model::PAY_TYPE_VIDEO);
|
||||
}
|
||||
if (!defined('AKSRT_PAY_TYPE_VIP')) {
|
||||
define('AKSRT_PAY_TYPE_VIP', AKSRT_Domain_Model::PAY_TYPE_VIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否为 VIP 豁免用户(免扣积分下载)。
|
||||
*
|
||||
* @param int $user_id
|
||||
* @return bool
|
||||
*/
|
||||
if (!function_exists('aksrt_is_vip_exempt')) {
|
||||
function aksrt_is_vip_exempt(int $user_id = 0): bool {
|
||||
if ($user_id <= 0) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
// 优先使用 aksrt_is_download_credit_exempt_user(如果已定义)
|
||||
if (function_exists('aksrt_is_download_credit_exempt_user')) {
|
||||
return aksrt_is_download_credit_exempt_user($user_id);
|
||||
}
|
||||
// 回退:直接检查 VIP 角色
|
||||
if (function_exists('aksrt_is_vip') && aksrt_is_vip($user_id)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function aksrt_get_pay_types(): array {
|
||||
return AKSRT_Domain_Model::pay_type_labels();
|
||||
|
||||
return [
|
||||
AKSRT_PAY_TYPE_NO => __('不付费', 'aksrt'),
|
||||
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'),
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 元数据读取
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_pay_meta')) {
|
||||
function aksrt_get_pay_meta($post = null): array {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) return aksrt_default_pay_meta();
|
||||
|
||||
$meta = get_post_meta($p->ID, AKSRT_Domain_Model::PAY_META_KEY, true);
|
||||
if (is_array($meta) && isset($meta['pay_type'])) {
|
||||
return wp_parse_args(AKSRT_Domain_Model::sanitize_pay_meta($meta), aksrt_default_pay_meta());
|
||||
}
|
||||
|
||||
return aksrt_default_pay_meta();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_default_pay_meta')) {
|
||||
function aksrt_default_pay_meta(): array {
|
||||
return AKSRT_Domain_Model::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' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 状态判断
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_is_paid_post')) {
|
||||
function aksrt_is_paid_post($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] !== AKSRT_PAY_TYPE_NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_paid_reading')) {
|
||||
function aksrt_is_paid_reading($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_READING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_paid_download')) {
|
||||
function aksrt_is_paid_download($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_DOWNLOAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_paid_gallery')) {
|
||||
function aksrt_is_paid_gallery($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_GALLERY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_paid_video')) {
|
||||
function aksrt_is_paid_video($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIDEO;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_vip_only')) {
|
||||
function aksrt_is_vip_only($post = null): bool {
|
||||
return aksrt_get_pay_meta($post)['pay_type'] === AKSRT_PAY_TYPE_VIP;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费状态检查
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_has_paid_access')) {
|
||||
function aksrt_has_paid_access($post = null, int $user_id = 0): bool {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) return true;
|
||||
|
||||
// 单一真相:付费访问由 AKSRT_Content_Visibility(基于订单 + VIP)裁决。
|
||||
if (class_exists('AKSRT_Content_Visibility')) {
|
||||
$state = AKSRT_Content_Visibility::get_post_access_state($p, $user_id);
|
||||
return !empty($state['has_paid_access']);
|
||||
}
|
||||
|
||||
// 兜底:仅在 service 不可用时使用,避免离线环境直接拒绝。
|
||||
$meta = aksrt_get_pay_meta($p);
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_NO) return true;
|
||||
if ((float) $meta['price'] <= 0 && $meta['pay_type'] !== AKSRT_PAY_TYPE_VIP) return true;
|
||||
|
||||
$uid = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
|
||||
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) return true;
|
||||
if ($uid > 0 && get_user_meta($uid, '_aksrt_paid_' . $p->ID, true)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_can_access_content')) {
|
||||
function aksrt_can_access_content($post = null): bool {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) return true;
|
||||
|
||||
if (class_exists('AKSRT_Content_Visibility')) {
|
||||
return AKSRT_Content_Visibility::can_view_paid_content($p);
|
||||
}
|
||||
|
||||
$pay_type = aksrt_get_pay_meta($p)['pay_type'];
|
||||
if ($pay_type === AKSRT_PAY_TYPE_NO) return true;
|
||||
|
||||
// 只有 reading/gallery/video 需要隐藏内容
|
||||
if (!in_array($pay_type, [AKSRT_PAY_TYPE_READING, AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return aksrt_has_paid_access($p);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_can_download')) {
|
||||
/**
|
||||
* 下载授权门禁 —— 单一合同。
|
||||
*
|
||||
* 实际判定全部交给 AKSRT_Content_Visibility::can_download_resource()
|
||||
* (由 inc/helpers/aksrt-resource.php 的同名 helper 在更早加载时定义)。
|
||||
* 此处仅作为该 helper 不可用时的兜底,避免出现第二套下载真相。
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @param int $user_id 0 表示当前用户
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
function aksrt_can_download($post = null, int $user_id = 0) {
|
||||
if (class_exists('AKSRT_Content_Visibility')) {
|
||||
return AKSRT_Content_Visibility::can_download_resource($post, $user_id);
|
||||
}
|
||||
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) {
|
||||
return new WP_Error('aksrt_invalid', __('无效内容。', 'aksrt'));
|
||||
}
|
||||
|
||||
$meta = aksrt_get_pay_meta($p);
|
||||
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_DOWNLOAD) {
|
||||
return new WP_Error('aksrt_not_downloadable', __('该内容不是可下载资源。', 'aksrt'));
|
||||
}
|
||||
|
||||
$paid_settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_paid_content_settings()
|
||||
: ['download_login' => true, 'download_credit' => true];
|
||||
|
||||
$uid = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
|
||||
if (!empty($paid_settings['download_login']) && $uid <= 0) {
|
||||
$login_url = wp_login_url(get_permalink($p));
|
||||
return new WP_Error('aksrt_need_login', __('请先登录。', 'aksrt'), ['login_url' => $login_url]);
|
||||
}
|
||||
|
||||
if ($uid > 0 && aksrt_has_paid_access($p, $uid)) return true;
|
||||
if ($uid > 0 && function_exists('aksrt_is_vip_exempt') && aksrt_is_vip_exempt($uid)) return true;
|
||||
|
||||
$cost = aksrt_get_download_cost($p);
|
||||
if ($cost > 0 && !empty($paid_settings['download_credit'])) {
|
||||
$balance = function_exists('aksrt_user_credits') ? aksrt_user_credits($uid) : 0;
|
||||
if ($balance < $cost) {
|
||||
return new WP_Error('aksrt_credits_insufficient', __('积分不足。', 'aksrt'), [
|
||||
'balance' => $balance,
|
||||
'cost' => $cost,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_get_download_cost')) {
|
||||
function aksrt_get_download_cost($post = null): int {
|
||||
if (function_exists('aksrt_resource_download_cost')) {
|
||||
return (int) aksrt_resource_download_cost($post);
|
||||
}
|
||||
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
return (int) ceil((float) $meta['price']);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 价格显示
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_pay_price_label')) {
|
||||
function aksrt_get_pay_price_label($post = null): array {
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
$price = (float) $meta['price'];
|
||||
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIP) {
|
||||
return ['text' => __('VIP 专属', 'aksrt'), 'free' => false, 'amount' => 0];
|
||||
}
|
||||
|
||||
if ($price <= 0) {
|
||||
return ['text' => __('免费', 'aksrt'), 'free' => true, 'amount' => 0];
|
||||
}
|
||||
|
||||
$amount = aksrt_format_price($price);
|
||||
return ['text' => sprintf(__('%s %s', 'aksrt'), $amount, $currency), 'free' => false, 'amount' => $price];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_format_price')) {
|
||||
function aksrt_format_price(float $price): string {
|
||||
if (fmod($price, 1.0) === 0.0) return (string) (int) $price;
|
||||
return rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费类型徽章
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_pay_type_badge')) {
|
||||
function aksrt_get_pay_type_badge(string $pay_type): string {
|
||||
$badges = [
|
||||
AKSRT_PAY_TYPE_READING => ['label' => __('付费阅读', 'aksrt'), 'color' => '#EF4444', 'icon' => 'fa-lock'],
|
||||
AKSRT_PAY_TYPE_DOWNLOAD => ['label' => __('付费下载', 'aksrt'), 'color' => '#F97316', 'icon' => 'fa-download'],
|
||||
AKSRT_PAY_TYPE_GALLERY => ['label' => __('付费图库', 'aksrt'), 'color' => '#10B981', 'icon' => 'fa-images'],
|
||||
AKSRT_PAY_TYPE_VIDEO => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
|
||||
AKSRT_PAY_TYPE_VIP => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-crown'],
|
||||
];
|
||||
|
||||
if (!isset($badges[$pay_type])) return '';
|
||||
$b = $badges[$pay_type];
|
||||
return sprintf(
|
||||
'<span class="aksrt-pay-badge" style="background:%s"><i class="fas %s"></i> %s</span>',
|
||||
esc_attr($b['color']), esc_attr($b['icon']), esc_html($b['label'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费墙 HTML
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_get_paywall_html')) {
|
||||
function aksrt_get_paywall_html(WP_Post $post): string {
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
$pay_type = $meta['pay_type'];
|
||||
$price = (float) $meta['price'];
|
||||
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
|
||||
$type_labels = [
|
||||
AKSRT_PAY_TYPE_READING => ['icon' => 'fa-lock', 'label' => __('付费阅读', 'aksrt'), 'desc' => __('付费后即可查看完整内容', 'aksrt')],
|
||||
AKSRT_PAY_TYPE_GALLERY => ['icon' => 'fa-images', '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];
|
||||
$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 = (float) ($meta['original_price'] ?? 0);
|
||||
$has_original = $original_price > 0 && $original_price > $price;
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="aksrt-paywall" data-post-id="<?php echo esc_attr((string) $post->ID); ?>" data-pay-type="<?php echo esc_attr($pay_type); ?>">
|
||||
<div class="aksrt-paywall__overlay">
|
||||
<div class="aksrt-paywall__blur"></div>
|
||||
<div class="aksrt-paywall__card">
|
||||
<div class="aksrt-paywall__icon">
|
||||
<i class="fas <?php echo esc_attr($type_info['icon']); ?>" aria-hidden="true"></i>
|
||||
</div>
|
||||
<h3 class="aksrt-paywall__title"><?php echo esc_html($type_info['label']); ?></h3>
|
||||
<p class="aksrt-paywall__desc"><?php echo esc_html($type_info['desc']); ?></p>
|
||||
<div class="aksrt-paywall__price">
|
||||
<?php if ($price > 0) : ?>
|
||||
<span class="aksrt-paywall__price-amount"><?php echo esc_html(aksrt_format_price($price) . ' ' . $currency); ?></span>
|
||||
<?php if ($has_original) : ?>
|
||||
<span class="aksrt-paywall__price-original"><?php echo esc_html(aksrt_format_price($original_price) . ' ' . $currency); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<span class="aksrt-paywall__price-free"><?php esc_html_e('免费', 'aksrt'); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if (!$is_logged_in) : ?>
|
||||
<a href="<?php echo esc_url($login_url); ?>" class="btn-primary btn-lg aksrt-paywall__btn">
|
||||
<i class="fas fa-sign-in-alt"></i> <?php esc_html_e('登录后购买', 'aksrt'); ?>
|
||||
</a>
|
||||
<?php elseif ($price > 0) : ?>
|
||||
<button type="button" class="btn-primary btn-lg aksrt-paywall__btn" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>">
|
||||
<i class="fas fa-shopping-cart"></i> <?php printf(esc_html__('立即购买(%s)', 'aksrt'), aksrt_format_price($price) . ' ' . $currency); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<p class="aksrt-paywall__hint">
|
||||
<i class="fas fa-shield-alt"></i> <?php esc_html_e('购买后永久有效,支持重复查看', 'aksrt'); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费墙样式注入
|
||||
// ============================================================
|
||||
|
||||
add_action('wp_head', function (): void {
|
||||
if (is_admin() || !is_singular()) return;
|
||||
global $post;
|
||||
if (!($post instanceof WP_Post)) return;
|
||||
if (!function_exists('aksrt_can_access_content') || aksrt_can_access_content($post)) return;
|
||||
?>
|
||||
<style>
|
||||
.aksrt-paywall{position:relative;margin:24px 0}
|
||||
.aksrt-paywall__overlay{position:relative}
|
||||
.aksrt-paywall__blur{filter:blur(12px);pointer-events:none;user-select:none;max-height:200px;overflow:hidden;opacity:.3}
|
||||
.aksrt-paywall__card{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface-1,#fff);border:1px solid var(--color-line,#e5e7eb);border-radius:var(--radius-lg,14px);padding:40px;text-align:center;box-shadow:0 12px 40px rgba(0,0,0,.12);min-width:320px;max-width:420px}
|
||||
.aksrt-paywall__icon{font-size:48px;color:var(--color-primary,#f04494);margin-bottom:16px}
|
||||
.aksrt-paywall__title{font-size:20px;font-weight:700;margin:0 0 8px;color:var(--color-ink,#15171A)}
|
||||
.aksrt-paywall__desc{font-size:14px;color:var(--color-ink-3,#525866);margin:0 0 20px}
|
||||
.aksrt-paywall__price{margin-bottom:20px}
|
||||
.aksrt-paywall__price-amount{font-size:28px;font-weight:700;color:var(--color-error,#EF4444)}
|
||||
.aksrt-paywall__price-original{font-size:14px;color:var(--color-ink-4,#8A8F98);text-decoration:line-through;margin-left:8px}
|
||||
.aksrt-paywall__price-free{font-size:20px;font-weight:600;color:var(--color-success,#10B981)}
|
||||
.aksrt-paywall__btn{display:inline-flex;align-items:center;gap:8px}
|
||||
.aksrt-paywall__hint{font-size:12px;color:var(--color-ink-4,#8A8F98);margin:16px 0 0}
|
||||
.aksrt-gallery-locked{filter:blur(8px);pointer-events:none;user-select:none}
|
||||
</style>
|
||||
<?php
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 付费图库轮播(Swiper)
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_render_paid_gallery')) {
|
||||
function aksrt_render_paid_gallery(WP_Post $post): string {
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_GALLERY) return '';
|
||||
|
||||
$images = $meta['gallery'] ?? [];
|
||||
if (empty($images)) return '';
|
||||
|
||||
$has_access = aksrt_has_paid_access($post);
|
||||
$base_uri = get_template_directory_uri();
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="aksrt-paid-gallery" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
|
||||
<?php if ($has_access) : ?>
|
||||
<!-- 已付费:Swiper 轮播 -->
|
||||
<div class="swiper aksrt-paid-gallery__swiper">
|
||||
<div class="swiper-wrapper">
|
||||
<?php foreach ($images as $img_url) : ?>
|
||||
<div class="swiper-slide">
|
||||
<img src="<?php echo esc_url($img_url); ?>" alt="" loading="lazy">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="swiper-pagination"></div>
|
||||
<div class="swiper-button-prev"></div>
|
||||
<div class="swiper-button-next"></div>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<!-- 未付费:模糊预览 + 付费墙 -->
|
||||
<div class="aksrt-paid-gallery__locked">
|
||||
<div class="aksrt-paid-gallery__preview">
|
||||
<img src="<?php echo esc_url($images[0]); ?>" alt="" class="aksrt-gallery-locked">
|
||||
</div>
|
||||
<div class="aksrt-paid-gallery__overlay">
|
||||
<i class="fas fa-images" aria-hidden="true"></i>
|
||||
<p><?php esc_html_e('付费后即可查看全部图片', 'aksrt'); ?></p>
|
||||
<?php
|
||||
$price_label = aksrt_get_pay_price_label($post);
|
||||
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
?>
|
||||
<span class="aksrt-paid-gallery__price">
|
||||
<?php echo esc_html($price_label['text']); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($has_access) : ?>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new Swiper('.aksrt-paid-gallery__swiper', {
|
||||
loop: true,
|
||||
pagination: { el: '.swiper-pagination', clickable: true },
|
||||
navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' },
|
||||
lazy: true,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费视频播放器
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_render_paid_video')) {
|
||||
function aksrt_render_paid_video(WP_Post $post): string {
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
if ($meta['pay_type'] !== AKSRT_PAY_TYPE_VIDEO) return '';
|
||||
|
||||
$video_url = $meta['video_url'] ?? '';
|
||||
if (empty($video_url)) return '';
|
||||
|
||||
$has_access = aksrt_has_paid_access($post);
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="aksrt-paid-video" data-post-id="<?php echo esc_attr((string) $post->ID); ?>">
|
||||
<?php if ($has_access) : ?>
|
||||
<!-- 已付费:视频播放器 -->
|
||||
<div class="aksrt-paid-video__player">
|
||||
<?php if (preg_match('/\.(mp4|webm|ogg)$/i', $video_url)) : ?>
|
||||
<video controls preload="metadata" style="width:100%;border-radius:var(--radius-lg);">
|
||||
<source src="<?php echo esc_url($video_url); ?>">
|
||||
<?php esc_html_e('您的浏览器不支持视频播放。', 'aksrt'); ?>
|
||||
</video>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-paid-video__embed">
|
||||
<iframe src="<?php echo esc_url($video_url); ?>" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<!-- 未付费:锁定预览 -->
|
||||
<div class="aksrt-paid-video__locked">
|
||||
<div class="aksrt-paid-video__placeholder">
|
||||
<i class="fas fa-play-circle" aria-hidden="true"></i>
|
||||
<p><?php esc_html_e('付费后即可观看完整视频', 'aksrt'); ?></p>
|
||||
<?php $price_label = aksrt_get_pay_price_label($post); ?>
|
||||
<span class="aksrt-paid-video__price"><?php echo esc_html($price_label['text']); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 内容过滤钩子(注册在 core-hooks.php)
|
||||
// ============================================================
|
||||
|
||||
// 这些函数供 core-hooks.php 中的 the_content 过滤器调用
|
||||
if (!function_exists('aksrt_paid_content_filter')) {
|
||||
function aksrt_paid_content_filter(string $content): string {
|
||||
if (is_admin() || !is_singular()) return $content;
|
||||
global $post;
|
||||
if (!($post instanceof WP_Post)) return $content;
|
||||
if (aksrt_can_access_content($post)) return $content;
|
||||
|
||||
// 图库类型:在内容末尾追加图库轮播
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_GALLERY) {
|
||||
return aksrt_get_paywall_html($post) . aksrt_render_paid_gallery($post);
|
||||
}
|
||||
|
||||
// 视频类型:在内容末尾追加视频播放器
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIDEO) {
|
||||
return aksrt_get_paywall_html($post) . aksrt_render_paid_video($post);
|
||||
}
|
||||
|
||||
return aksrt_get_paywall_html($post);
|
||||
}
|
||||
}
|
||||
|
||||
// 已付费的内容增强
|
||||
if (!function_exists('aksrt_paid_content_enhance')) {
|
||||
function aksrt_paid_content_enhance(string $content): string {
|
||||
if (is_admin() || !is_singular()) return $content;
|
||||
global $post;
|
||||
if (!($post instanceof WP_Post)) return $content;
|
||||
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_NO) return $content;
|
||||
if (!aksrt_has_paid_access($post)) return $content;
|
||||
|
||||
// 已付费的图库:追加 Swiper 轮播
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_GALLERY) {
|
||||
$gallery_html = aksrt_render_paid_gallery($post);
|
||||
if ($gallery_html) $content .= $gallery_html;
|
||||
}
|
||||
|
||||
// 已付费的视频:追加播放器
|
||||
if ($meta['pay_type'] === AKSRT_PAY_TYPE_VIDEO) {
|
||||
$video_html = aksrt_render_paid_video($post);
|
||||
if ($video_html) $content .= $video_html;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 付费图库/视频样式
|
||||
// ============================================================
|
||||
|
||||
add_action('wp_head', function (): void {
|
||||
if (is_admin() || !is_singular()) return;
|
||||
global $post;
|
||||
if (!($post instanceof WP_Post)) return;
|
||||
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
if (!in_array($meta['pay_type'], [AKSRT_PAY_TYPE_GALLERY, AKSRT_PAY_TYPE_VIDEO], true)) return;
|
||||
?>
|
||||
<style>
|
||||
.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)}
|
||||
</style>
|
||||
<?php
|
||||
});
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,579 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Resource Helper
|
||||
*
|
||||
* 资源(CMS)相关的顶层辅助函数。
|
||||
*
|
||||
* 提供给模板层调用,将原始 post meta 转成视图友好的结构,
|
||||
* 同时承担"显示策略"判定(是否显示价格/版本/下载按钮等)。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_get_resource_meta')) {
|
||||
/**
|
||||
* 一次性取齐资源的全部元数据,已带默认值。
|
||||
*
|
||||
* 单次 get_post_meta($id, '', true) 调用 + WordPress 内部 meta 缓存命中,
|
||||
* 避免 11 次单独查询。
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_resource_meta($post = null): array {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post)) {
|
||||
return aksrt_default_resource_meta();
|
||||
}
|
||||
|
||||
$pay_meta = function_exists('aksrt_get_pay_meta')
|
||||
? aksrt_get_pay_meta($p)
|
||||
: AKSRT_Domain_Model::default_pay_meta();
|
||||
$download_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::RESOURCE_DOWNLOAD_COUNT_KEY, true);
|
||||
$legacy_download_count = (int) get_post_meta($p->ID, 'download_count', true);
|
||||
$resource_view_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::RESOURCE_VIEW_COUNT_KEY, true);
|
||||
$common_view_count = (int) get_post_meta($p->ID, AKSRT_Domain_Model::POST_VIEW_COUNT_KEY, true);
|
||||
|
||||
return [
|
||||
'version' => (string) ($pay_meta['version'] ?? ''),
|
||||
'size' => (string) ($pay_meta['size'] ?? ''),
|
||||
'price' => (float) ($pay_meta['price'] ?? 0),
|
||||
'original_price' => (float) ($pay_meta['original_price'] ?? 0),
|
||||
'vip1_price' => (float) ($pay_meta['vip1_price'] ?? 0),
|
||||
'vip2_price' => (float) ($pay_meta['vip2_price'] ?? 0),
|
||||
'pay_type' => (string) ($pay_meta['pay_type'] ?? AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD),
|
||||
'license' => (string) ($pay_meta['license'] ?? ''),
|
||||
'compat' => (string) ($pay_meta['compat'] ?? ''),
|
||||
'demo_url' => (string) ($pay_meta['demo_url'] ?? ''),
|
||||
'downloads' => array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['downloads'] : []),
|
||||
'download_count' => max(0, max($download_count, $legacy_download_count)),
|
||||
'view_count' => max(0, max($resource_view_count, $common_view_count)),
|
||||
'require_login' => !empty($pay_meta['require_login']),
|
||||
'require_captcha' => !empty($pay_meta['require_captcha']),
|
||||
];
|
||||
|
||||
// 一次取出全部 post meta(WP 已缓存到 object cache)
|
||||
$all = get_post_meta($p->ID);
|
||||
if (!is_array($all)) $all = [];
|
||||
|
||||
$get = static function ($key) use ($all) {
|
||||
if (!isset($all[$key]) || !is_array($all[$key]) || empty($all[$key])) return null;
|
||||
return maybe_unserialize($all[$key][0]);
|
||||
};
|
||||
|
||||
$downloads = $get('_aksrt_res_downloads');
|
||||
if (!is_array($downloads)) $downloads = [];
|
||||
$downloads = array_values(array_filter(array_map(static function ($row) {
|
||||
if (!is_array($row)) return null;
|
||||
$extract_keys = [
|
||||
'extract_code', 'extractCode', 'extract', 'extract_key',
|
||||
'extraction_code', 'extractionCode',
|
||||
'code', 'password', 'pwd',
|
||||
'pan_code', 'panCode', 'pan_password', 'panPassword',
|
||||
'access_code', 'accessCode',
|
||||
];
|
||||
if (!array_key_exists('extract_code', $row)) {
|
||||
foreach ($extract_keys as $alias) {
|
||||
if (isset($row[$alias]) && !is_array($row[$alias]) && trim((string) $row[$alias]) !== '') {
|
||||
$row['extract_code'] = (string) $row[$alias];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$row['extract_code'] = isset($row['extract_code']) ? trim((string) $row['extract_code']) : '';
|
||||
return $row;
|
||||
}, $downloads)));
|
||||
$resource_download_count = (int) ($get('_aksrt_res_download_count') ?? 0);
|
||||
$legacy_download_count = (int) ($get('download_count') ?? 0);
|
||||
$resource_view_count = (int) ($get('_aksrt_res_view_count') ?? 0);
|
||||
$common_view_count = (int) ($get('aksrt_post_views') ?? 0);
|
||||
|
||||
$allowed_pay_types = ['download', 'reading', 'gallery', 'video', 'vip'];
|
||||
$pay_type = (string) ($get('_aksrt_res_pay_type') ?? 'download');
|
||||
if (!in_array($pay_type, $allowed_pay_types, true)) {
|
||||
$pay_type = 'download';
|
||||
}
|
||||
|
||||
return [
|
||||
'version' => (string) ($get('_aksrt_res_version') ?? ''),
|
||||
'size' => (string) ($get('_aksrt_res_size') ?? ''),
|
||||
'price' => (float) ($get('_aksrt_res_price') ?? 0),
|
||||
'original_price' => (float) ($get('_aksrt_res_original_price') ?? 0),
|
||||
'vip1_price' => (float) ($get('_aksrt_res_vip1_price') ?? 0),
|
||||
'vip2_price' => (float) ($get('_aksrt_res_vip2_price') ?? 0),
|
||||
'pay_type' => $pay_type,
|
||||
'license' => (string) ($get('_aksrt_res_license') ?? ''),
|
||||
'compat' => (string) ($get('_aksrt_res_compat') ?? ''),
|
||||
'demo_url' => (string) ($get('_aksrt_res_demo_url') ?? ''),
|
||||
'downloads' => array_values($downloads),
|
||||
'download_count' => max(0, max($resource_download_count, $legacy_download_count)),
|
||||
'view_count' => max(0, max($resource_view_count, $common_view_count)),
|
||||
'require_login' => (bool) ($get('_aksrt_res_require_login') ?? false),
|
||||
'require_captcha' => (bool) ($get('_aksrt_res_require_captcha') ?? false),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_default_resource_meta')) {
|
||||
function aksrt_default_resource_meta(): array {
|
||||
$meta = AKSRT_Domain_Model::default_pay_meta();
|
||||
|
||||
return [
|
||||
'version' => (string) ($meta['version'] ?? ''),
|
||||
'size' => (string) ($meta['size'] ?? ''),
|
||||
'price' => (float) ($meta['price'] ?? 0),
|
||||
'original_price' => (float) ($meta['original_price'] ?? 0),
|
||||
'vip1_price' => (float) ($meta['vip1_price'] ?? 0),
|
||||
'vip2_price' => (float) ($meta['vip2_price'] ?? 0),
|
||||
'pay_type' => AKSRT_Domain_Model::PAY_TYPE_DOWNLOAD,
|
||||
'license' => (string) ($meta['license'] ?? ''),
|
||||
'compat' => (string) ($meta['compat'] ?? ''),
|
||||
'demo_url' => (string) ($meta['demo_url'] ?? ''),
|
||||
'downloads' => [],
|
||||
'download_count' => 0,
|
||||
'view_count' => 0,
|
||||
'require_login' => !empty($meta['require_login']),
|
||||
'require_captcha' => !empty($meta['require_captcha']),
|
||||
];
|
||||
|
||||
return [
|
||||
'version' => '',
|
||||
'size' => '',
|
||||
'price' => 0.0,
|
||||
'original_price' => 0.0,
|
||||
'vip1_price' => 0.0,
|
||||
'vip2_price' => 0.0,
|
||||
'pay_type' => 'download',
|
||||
'license' => '',
|
||||
'compat' => '',
|
||||
'demo_url' => '',
|
||||
'downloads' => [],
|
||||
'download_count' => 0,
|
||||
'view_count' => 0,
|
||||
'require_login' => false,
|
||||
'require_captcha' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_price_label')) {
|
||||
/**
|
||||
* 把价格数字渲染成展示徽章文案。
|
||||
*
|
||||
* @param float $price
|
||||
* @return array { text:string, free:bool }
|
||||
*/
|
||||
function aksrt_resource_price_label(float $price): array {
|
||||
if ($price <= 0) {
|
||||
return [
|
||||
'text' => __('免费', 'aksrt'),
|
||||
'free' => true,
|
||||
];
|
||||
}
|
||||
$n = (fmod($price, 1.0) === 0.0) ? (string) (int) $price : rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.');
|
||||
return [
|
||||
'text' => sprintf(__('%s 积分', 'aksrt'), $n),
|
||||
'free' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_price_amount')) {
|
||||
/**
|
||||
* Format the raw resource price number for compact UI labels.
|
||||
*
|
||||
* @param float $price
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_resource_price_amount(float $price): string {
|
||||
if (fmod($price, 1.0) === 0.0) {
|
||||
return (string) (int) $price;
|
||||
}
|
||||
|
||||
return rtrim(rtrim(number_format($price, 2, '.', ''), '0'), '.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_card_badge_text')) {
|
||||
/**
|
||||
* Resource badge text used in mixed post cards.
|
||||
*
|
||||
* Only resource-type posts should render this badge.
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_resource_card_badge_text($post = null): string {
|
||||
if (!function_exists('aksrt_is_resource_post') || !aksrt_is_resource_post($post)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$meta = aksrt_get_resource_meta($post);
|
||||
$price = (float) $meta['price'];
|
||||
|
||||
if ($price <= 0) {
|
||||
// 免费资源不在卡片上显示价格 chip。
|
||||
return '';
|
||||
}
|
||||
|
||||
$currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
return sprintf(__('付费 %s %s', 'aksrt'), aksrt_resource_price_amount($price), $currency);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_pay_type_badge')) {
|
||||
/**
|
||||
* 根据付费类型返回徽章 HTML。
|
||||
*
|
||||
* @param string $pay_type
|
||||
* @return string HTML 或空字符串
|
||||
*/
|
||||
function aksrt_resource_pay_type_badge(string $pay_type): string {
|
||||
$badges = [
|
||||
'reading' => ['label' => __('付费阅读', 'aksrt'), 'color' => '#EF4444', 'icon' => 'fa-lock'],
|
||||
'download'=> ['label' => __('付费下载', 'aksrt'), 'color' => '#F97316', 'icon' => 'fa-download'],
|
||||
'gallery' => ['label' => __('付费图库', 'aksrt'), 'color' => '#10B981', 'icon' => 'fa-images'],
|
||||
'video' => ['label' => __('付费视频', 'aksrt'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
|
||||
'vip' => ['label' => __('VIP 专属', 'aksrt'), 'color' => '#3B82F6', 'icon' => 'fa-crown'],
|
||||
];
|
||||
|
||||
if ($pay_type === 'download' || !isset($badges[$pay_type])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$b = $badges[$pay_type];
|
||||
return sprintf(
|
||||
'<span class="resource-pay-type-badge" style="background:%s"><i class="fas %s"></i> %s</span>',
|
||||
esc_attr($b['color']),
|
||||
esc_attr($b['icon']),
|
||||
esc_html($b['label'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_preview_images')) {
|
||||
/**
|
||||
* Collect preview images for resource cards and archive thumbnails.
|
||||
*
|
||||
* Priority: curated cover_images meta -> featured image -> gallery/content images.
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @param int $limit
|
||||
* @return string[]
|
||||
*/
|
||||
function aksrt_resource_preview_images($post = null, int $limit = 4): array {
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post) || !function_exists('aksrt_is_resource_post') || !aksrt_is_resource_post($p)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$limit = max(1, min(8, $limit));
|
||||
$images = [];
|
||||
|
||||
if (class_exists('AKSRT_Post_Meta_Box')) {
|
||||
$cover_images = (string) AKSRT_Post_Meta_Box::get_meta_value($p->ID, 'cover_images', '');
|
||||
if ($cover_images !== '') {
|
||||
$cover_ids = array_filter(array_map('absint', preg_split('/[\s,]+/', $cover_images) ?: []));
|
||||
foreach ($cover_ids as $attachment_id) {
|
||||
$url = (string) wp_get_attachment_image_url($attachment_id, 'medium_large');
|
||||
if ($url !== '') {
|
||||
$images[] = esc_url_raw($url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_post_thumbnail($p)) {
|
||||
$featured = (string) get_the_post_thumbnail_url($p, 'medium_large');
|
||||
if ($featured !== '') {
|
||||
$images[] = esc_url_raw($featured);
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_get_post_gallery_images')) {
|
||||
$images = array_merge($images, aksrt_get_post_gallery_images($p->ID, $limit));
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_get_first_content_image_url')) {
|
||||
$content_image = (string) aksrt_get_first_content_image_url($p->ID);
|
||||
if ($content_image !== '') {
|
||||
$images[] = esc_url_raw($content_image);
|
||||
}
|
||||
}
|
||||
|
||||
$images = array_values(array_unique(array_filter($images, static function ($url): bool {
|
||||
return is_string($url) && $url !== '';
|
||||
})));
|
||||
|
||||
return array_slice($images, 0, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_primary_image_url')) {
|
||||
/**
|
||||
* Primary preview image for a resource post.
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_resource_primary_image_url($post = null): string {
|
||||
$images = aksrt_resource_preview_images($post, 1);
|
||||
return $images[0] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_meta_strip')) {
|
||||
/**
|
||||
* 卡片用的紧凑元数据条 [v1.0 · 210MB · ↓1234]。
|
||||
*
|
||||
* @param array $meta aksrt_get_resource_meta 返回值
|
||||
* @return array 字符串数组
|
||||
*/
|
||||
function aksrt_resource_meta_strip(array $meta): array {
|
||||
$parts = [];
|
||||
if ($meta['version'] !== '') {
|
||||
$parts[] = 'v' . $meta['version'];
|
||||
}
|
||||
if ($meta['size'] !== '') {
|
||||
$parts[] = $meta['size'];
|
||||
}
|
||||
if ($meta['download_count'] > 0) {
|
||||
$parts[] = '↓ ' . number_format_i18n($meta['download_count']);
|
||||
}
|
||||
return $parts;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否命中下载积分免扣策略。
|
||||
*/
|
||||
if (!function_exists('aksrt_is_download_credit_exempt_user')) {
|
||||
function aksrt_is_download_credit_exempt_user(int $user_id = 0): bool {
|
||||
if ($user_id <= 0 || !class_exists('AKSRT_Settings')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$paid_settings = AKSRT_Settings::get_paid_content_settings();
|
||||
if (empty($paid_settings['vip_free_download']) || empty($paid_settings['vip_exempt_roles'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || empty($user->roles) || !is_array($user->roles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !empty(array_intersect($user->roles, (array) $paid_settings['vip_exempt_roles']));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_can_download')) {
|
||||
/**
|
||||
* 是否可下载(P3 阶段:登录 + 积分校验已接入;P5 接入验证码)。
|
||||
*
|
||||
* 返回 WP_Error 表示拒绝;返回 true 表示放行。
|
||||
*
|
||||
* @param int|WP_Post|null $post
|
||||
* @param int $user_id 0 表示当前用户
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
function aksrt_can_download($post = null, int $user_id = 0) {
|
||||
if (class_exists('AKSRT_Content_Visibility')) {
|
||||
return AKSRT_Content_Visibility::can_download_resource($post, $user_id);
|
||||
}
|
||||
|
||||
$p = get_post($post);
|
||||
if (!($p instanceof WP_Post) || !aksrt_is_resource_post($p)) {
|
||||
return new WP_Error('aksrt_not_resource', __('该内容不是可下载资源。', 'aksrt'));
|
||||
}
|
||||
|
||||
$meta = aksrt_get_resource_meta($p);
|
||||
$paid_settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_paid_content_settings()
|
||||
: [
|
||||
'download_login' => true,
|
||||
'download_credit' => true,
|
||||
];
|
||||
|
||||
// 登录门槛:全局开关 OR 单条覆盖
|
||||
$need_login = !empty($paid_settings['download_login']) || !empty($meta['require_login']);
|
||||
$uid = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
|
||||
if ($need_login && $uid <= 0) {
|
||||
return new WP_Error('aksrt_need_login', __('请先登录后再下载。', 'aksrt'), [
|
||||
'status' => 401,
|
||||
'login_url' => wp_login_url(get_permalink($p)),
|
||||
]);
|
||||
}
|
||||
|
||||
// 积分门槛:仅在 cms_features.download_credit 开启 + 实际成本 > 0 + 登录态时才校验
|
||||
$credit_enabled = !empty($paid_settings['download_credit']);
|
||||
|
||||
if ($credit_enabled) {
|
||||
$cost = (int) aksrt_resource_download_cost($p);
|
||||
if ($cost > 0) {
|
||||
if ($uid <= 0) {
|
||||
return new WP_Error('aksrt_need_login', __('请先登录后再下载。', 'aksrt'), [
|
||||
'status' => 401,
|
||||
'login_url' => wp_login_url(get_permalink($p)),
|
||||
]);
|
||||
}
|
||||
$balance = class_exists('AKSRT_Credits') ? AKSRT_Credits::balance($uid) : 0;
|
||||
if ($balance < $cost) {
|
||||
return new WP_Error('aksrt_credits_insufficient', __('积分不足,无法下载。', 'aksrt'), [
|
||||
'status' => 402,
|
||||
'balance' => $balance,
|
||||
'cost' => $cost,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// P5 阶段:验证码校验(在请求时校验,不在 can_download 静态判定中拦截)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_download_cost')) {
|
||||
/**
|
||||
* 取实际需要扣除的积分(向上取整),未启用积分门槛时返回 0。
|
||||
*
|
||||
* 优先级:单条 _aksrt_res_price > 全局 download_default_cost。
|
||||
*/
|
||||
function aksrt_resource_download_cost($post = null): int {
|
||||
$meta = aksrt_get_resource_meta($post);
|
||||
$paid_settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_paid_content_settings()
|
||||
: [
|
||||
'download_credit' => true,
|
||||
'default_cost' => 0,
|
||||
];
|
||||
if (empty($paid_settings['download_credit'])) return 0;
|
||||
if (function_exists('get_current_user_id') && aksrt_is_download_credit_exempt_user((int) get_current_user_id())) {
|
||||
return 0;
|
||||
}
|
||||
$price = (float) $meta['price'];
|
||||
if ($price > 0) {
|
||||
return (int) ceil($price);
|
||||
}
|
||||
// 单条未设价格 → 回退到全局默认价
|
||||
return max(0, (int) ($paid_settings['default_cost'] ?? 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_meta_query')) {
|
||||
/**
|
||||
* 「是资源」WP_Query meta_query 片段。
|
||||
*
|
||||
* v3.4: 资源 = post + _aksrt_is_resource = 1。
|
||||
* 兼容历史 aksrt_resource CPT 时由调用方决定是否额外加 post_type。
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_resource_meta_query(): array {
|
||||
return [
|
||||
'key' => '_aksrt_is_resource',
|
||||
'value' => '1',
|
||||
'compare' => '=',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_post_types')) {
|
||||
/**
|
||||
* 资源查询使用的 post_type 数组。
|
||||
*
|
||||
* 当历史 aksrt_resource CPT 仍在站点中存在(未迁移完成)时,会自动包含。
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_resource_post_types(): array {
|
||||
$types = ['post'];
|
||||
if (post_type_exists('aksrt_resource')) {
|
||||
$types[] = 'aksrt_resource';
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_archive_url')) {
|
||||
/**
|
||||
* 取资源归档页 URL(v3.4 起统一为 /resources/)。
|
||||
*/
|
||||
function aksrt_resource_archive_url(): string {
|
||||
return home_url('/resources/');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_is_resource_archive')) {
|
||||
/**
|
||||
* 当前请求是否为资源归档(/resources)。
|
||||
*
|
||||
* 兼容历史 aksrt_resource CPT 归档。
|
||||
*/
|
||||
function aksrt_is_resource_archive(): bool {
|
||||
if (function_exists('post_type_exists') && post_type_exists('aksrt_resource')
|
||||
&& function_exists('is_post_type_archive') && is_post_type_archive('aksrt_resource')) {
|
||||
return true;
|
||||
}
|
||||
return (bool) get_query_var('aksrt_is_resource_archive');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_detect_cloud_service')) {
|
||||
/**
|
||||
* 从下载链接自动识别云盘服务商。
|
||||
*
|
||||
* @param string $url 下载链接
|
||||
* @return array { key:string, name:string, color:string }
|
||||
*/
|
||||
function aksrt_detect_cloud_service(string $url): array {
|
||||
$url_lower = strtolower($url);
|
||||
|
||||
$services = [
|
||||
'baidu' => ['name' => '百度网盘', 'color' => '#1578f8', 'pattern' => ['pan.baidu.com', 'yun.baidu.com']],
|
||||
'lanzou' => ['name' => '蓝奏云', 'color' => '#f37526', 'pattern' => ['lanzouf.com', 'lanzoui.com', 'lanzoux.com', 'lanzouw.com', 'lanzoup.com', 'lanzout.com', 'lanzou']],
|
||||
'aliyun' => ['name' => '阿里云盘', 'color' => '#6666ff', 'pattern' => ['alipan.com', 'aliyundrive.com']],
|
||||
'weiyun' => ['name' => '微云', 'color' => '#4facfe', 'pattern' => ['share.weiyun.com', 'weiyun.com']],
|
||||
'onedrive' => ['name' => 'OneDrive', 'color' => '#0078d4', 'pattern' => ['1drv.ms', 'onedrive.live.com']],
|
||||
'tianyi' => ['name' => '天翼云盘', 'color' => '#0099ff', 'pattern' => ['cloud.189.cn']],
|
||||
'123pan' => ['name' => '123云盘', 'color' => '#2b7bff', 'pattern' => ['123pan.com', '123684.com']],
|
||||
'quark' => ['name' => '夸克网盘', 'color' => '#6a5acd', 'pattern' => ['pan.quark.cn']],
|
||||
'xunlei' => ['name' => '迅雷云盘', 'color' => '#00a4ff', 'pattern' => ['pan.xunlei.com']],
|
||||
'mega' => ['name' => 'MEGA', 'color' => '#d92323', 'pattern' => ['mega.nz', 'mega.co.nz']],
|
||||
'gofile' => ['name' => 'GoFile', 'color' => '#4caf50', 'pattern' => ['gofile.io']],
|
||||
'mediafire' => ['name' => 'MediaFire', 'color' => '#1294f2', 'pattern' => ['mediafire.com']],
|
||||
'github' => ['name' => 'GitHub', 'color' => '#333333', 'pattern' => ['github.com', 'raw.githubusercontent.com']],
|
||||
];
|
||||
|
||||
foreach ($services as $key => $svc) {
|
||||
foreach ($svc['pattern'] as $p) {
|
||||
if (strpos($url_lower, $p) !== false) {
|
||||
return ['key' => $key, 'name' => $svc['name'], 'color' => $svc['color']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['key' => 'other', 'name' => __('下载', 'aksrt'), 'color' => '#6b7280'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_resource_meta_icon')) {
|
||||
/**
|
||||
* 根据元信息字段返回对应的 Font Awesome 图标类。
|
||||
*/
|
||||
function aksrt_resource_meta_icon(string $field): string {
|
||||
$icons = [
|
||||
'version' => 'fas fa-code-branch',
|
||||
'size' => 'fas fa-hdd',
|
||||
'compat' => 'fas fa-laptop',
|
||||
'license' => 'fas fa-shield-alt',
|
||||
'demo_url' => 'fas fa-external-link-alt',
|
||||
'download' => 'fas fa-download',
|
||||
'views' => 'fas fa-eye',
|
||||
];
|
||||
return $icons[$field] ?? 'fas fa-info-circle';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Shortcodes Registry — 短代码统一注册入口
|
||||
*
|
||||
* Phase 9 收口:所有主题短代码集中在此注册,不再散落到
|
||||
* `AKSRT_Content_Visibility` / `AKSRT_Templates` 等业务类的 init() 中。
|
||||
* 业务渲染仍由各自类/模板实现,registry 只负责「注册 → 委派」。
|
||||
*
|
||||
* 详见 shortcode-registry.md。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!class_exists('AKSRT_Shortcodes')) :
|
||||
|
||||
class AKSRT_Shortcodes {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册全部短代码。在 `init` 上调用一次。
|
||||
*/
|
||||
public function register(): void {
|
||||
// 隐藏内容(评论/登录/密码可见)→ AKSRT_Content_Visibility
|
||||
add_shortcode('aksrt_hide', [$this, 'render_hide']);
|
||||
// §3.8 用户友好短代码别名
|
||||
add_shortcode('hide', [$this, 'render_hide_comment']);
|
||||
add_shortcode('reply', [$this, 'render_reply']);
|
||||
add_shortcode('vip', [$this, 'render_vip']);
|
||||
add_shortcode('payshow', [$this, 'render_payshow']);
|
||||
add_shortcode('content', [$this, 'render_content_gate']);
|
||||
// 文章归档 / 个人资料卡 → AKSRT_Templates 模板
|
||||
add_shortcode('aksrt_archive', [$this, 'render_archive']);
|
||||
add_shortcode('aksrt_profile', [$this, 'render_profile']);
|
||||
// 文章列表(统一渲染器)
|
||||
add_shortcode('aksrt_posts', [$this, 'render_posts']);
|
||||
}
|
||||
|
||||
/**
|
||||
* [aksrt_hide mode="comment|login|password"]...[/aksrt_hide]
|
||||
*/
|
||||
public function render_hide(array $atts, string $content = ''): string {
|
||||
if (!class_exists('AKSRT_Content_Visibility')) {
|
||||
return '';
|
||||
}
|
||||
return AKSRT_Content_Visibility::get_instance()->render_hide_shortcode($atts, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* [hide]...[/hide] — 评论后可见(默认 mode=comment)
|
||||
*/
|
||||
public function render_hide_comment(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['message' => ''], $atts, 'hide');
|
||||
return $this->render_hide(['mode' => 'comment', 'message' => $atts['message']], $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* [reply]...[/reply] — 回复(评论)后可见
|
||||
*/
|
||||
public function render_reply(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['message' => ''], $atts, 'reply');
|
||||
$msg = $atts['message'] ?: __('回复本文后可查看隐藏内容', 'aksrt');
|
||||
return $this->render_hide(['mode' => 'comment', 'message' => $msg], $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* [vip]...[/vip] — VIP 用户可见
|
||||
*/
|
||||
public function render_vip(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['message' => ''], $atts, 'vip');
|
||||
$uid = get_current_user_id();
|
||||
|
||||
$is_vip = $uid > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($uid);
|
||||
|
||||
if ($is_vip) {
|
||||
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--vip">'
|
||||
. do_shortcode($content) . '</div>';
|
||||
}
|
||||
|
||||
$message = $atts['message'] ?: __('此内容仅 VIP 用户可见', 'aksrt');
|
||||
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--vip">';
|
||||
$html .= '<div class="aksrt-hidden-content__message">';
|
||||
$html .= '<i class="fas fa-crown" aria-hidden="true"></i> ';
|
||||
$html .= '<span>' . esc_html($message) . '</span>';
|
||||
$html .= '</div>';
|
||||
if ($uid <= 0) {
|
||||
$html .= '<a href="' . esc_url(wp_login_url(get_permalink())) . '" class="aksrt-hidden-content__login-btn btn-primary btn-sm">';
|
||||
$html .= esc_html__('登录后查看', 'aksrt') . '</a>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* [payshow]...[/payshow] — 付费后可见
|
||||
*/
|
||||
public function render_payshow(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['message' => ''], $atts, 'payshow');
|
||||
$pid = get_the_ID();
|
||||
|
||||
$can_view = false;
|
||||
if ($pid && class_exists('AKSRT_Content_Visibility')) {
|
||||
$can_view = AKSRT_Content_Visibility::can_view_paid_content($pid);
|
||||
}
|
||||
|
||||
if ($can_view) {
|
||||
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--payshow">'
|
||||
. do_shortcode($content) . '</div>';
|
||||
}
|
||||
|
||||
$message = $atts['message'] ?: __('此内容需付费后查看', 'aksrt');
|
||||
$uid = get_current_user_id();
|
||||
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--payshow">';
|
||||
$html .= '<div class="aksrt-hidden-content__message">';
|
||||
$html .= '<i class="fas fa-lock" aria-hidden="true"></i> ';
|
||||
$html .= '<span>' . esc_html($message) . '</span>';
|
||||
$html .= '</div>';
|
||||
if ($uid <= 0) {
|
||||
$html .= '<a href="' . esc_url(wp_login_url(get_permalink())) . '" class="aksrt-hidden-content__login-btn btn-primary btn-sm">';
|
||||
$html .= esc_html__('登录后购买查看', 'aksrt') . '</a>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* [content type="admin|author|vip"]...[/content] — 按角色/身份控制可见性
|
||||
*/
|
||||
public function render_content_gate(array $atts = [], string $content = ''): string {
|
||||
$atts = shortcode_atts(['type' => 'admin', 'message' => ''], $atts, 'content');
|
||||
$type = sanitize_key((string) $atts['type']);
|
||||
$uid = get_current_user_id();
|
||||
$pid = get_the_ID();
|
||||
$allowed = false;
|
||||
|
||||
switch ($type) {
|
||||
case 'admin':
|
||||
$allowed = $uid > 0 && user_can($uid, 'manage_options');
|
||||
break;
|
||||
case 'author':
|
||||
if ($uid > 0 && $pid) {
|
||||
$post = get_post($pid);
|
||||
$allowed = $post && (int) $post->post_author === $uid;
|
||||
}
|
||||
break;
|
||||
case 'vip':
|
||||
$allowed = $uid > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($uid);
|
||||
break;
|
||||
case 'login':
|
||||
$allowed = $uid > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($allowed) {
|
||||
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--role">'
|
||||
. do_shortcode($content) . '</div>';
|
||||
}
|
||||
|
||||
$default_msgs = [
|
||||
'admin' => __('此内容仅管理员可见', 'aksrt'),
|
||||
'author' => __('此内容仅作者可见', 'aksrt'),
|
||||
'vip' => __('此内容仅 VIP 用户可见', 'aksrt'),
|
||||
'login' => __('此内容仅登录用户可见', 'aksrt'),
|
||||
];
|
||||
$message = $atts['message'] ?: ($default_msgs[$type] ?? __('此内容已隐藏', 'aksrt'));
|
||||
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--role">';
|
||||
$html .= '<div class="aksrt-hidden-content__message">';
|
||||
$html .= '<i class="fas fa-eye-slash" aria-hidden="true"></i> ';
|
||||
$html .= '<span>' . esc_html($message) . '</span>';
|
||||
$html .= '</div>';
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* [aksrt_archive] 文章归档
|
||||
*/
|
||||
public function render_archive(array $atts = [], string $content = ''): string {
|
||||
if (!class_exists('AKSRT_Templates')) {
|
||||
return '';
|
||||
}
|
||||
return AKSRT_Templates::get_instance()->render_archive_shortcode($atts, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* [aksrt_profile] 个人资料卡
|
||||
*/
|
||||
public function render_profile(array $atts = [], string $content = ''): string {
|
||||
if (!class_exists('AKSRT_Templates')) {
|
||||
return '';
|
||||
}
|
||||
return AKSRT_Templates::get_instance()->render_profile_shortcode($atts, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* [aksrt_posts count="6" cat="" tag="" orderby="date" order="DESC" mode="card"]
|
||||
*
|
||||
* 用统一渲染器输出文章列表,避免内容里手写卡片循环。
|
||||
*/
|
||||
public function render_posts(array $atts = [], string $content = ''): string {
|
||||
if (!function_exists('aksrt_render_post_list')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$a = shortcode_atts([
|
||||
'count' => 6,
|
||||
'cat' => '',
|
||||
'tag' => '',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'mode' => 'card',
|
||||
], $atts, 'aksrt_posts');
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => max(1, min(24, (int) $a['count'])),
|
||||
'orderby' => sanitize_key((string) $a['orderby']) ?: 'date',
|
||||
'order' => strtoupper((string) $a['order']) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
$cat = sanitize_text_field((string) $a['cat']);
|
||||
if ($cat !== '') {
|
||||
$query_args[is_numeric($cat) ? 'cat' : 'category_name'] = $cat;
|
||||
}
|
||||
$tag = sanitize_text_field((string) $a['tag']);
|
||||
if ($tag !== '') {
|
||||
$query_args[is_numeric($tag) ? 'tag_id' : 'tag'] = $tag;
|
||||
}
|
||||
|
||||
$mode = in_array($a['mode'], ['card', 'list', 'mini'], true) ? $a['mode'] : 'card';
|
||||
|
||||
$query = new WP_Query($query_args);
|
||||
$html = aksrt_render_post_list($query, [
|
||||
'context' => 'widget',
|
||||
'mode' => $mode,
|
||||
], false);
|
||||
wp_reset_postdata();
|
||||
|
||||
return '<div class="aksrt-shortcode-posts">' . $html . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
endif;
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Slider Helper — 轮播渲染函数
|
||||
*
|
||||
* 对标 zibll 的 zib_new_slider(),提供服务端轮播 HTML 生成。
|
||||
* 支持 data-* 属性驱动配置、视差、懒加载、缩略图分页。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 渲染 Swiper 轮播
|
||||
*
|
||||
* @param array $args {
|
||||
* @type array $slides 幻灯片数据数组
|
||||
* @type string $effect 动画效果: slide, fade, cube, coverflow, flip (默认 slide)
|
||||
* @type bool $loop 是否循环 (默认 true)
|
||||
* @type bool $autoplay 是否自动播放 (默认 true)
|
||||
* @type int $interval 自动播放间隔 ms (默认 5000)
|
||||
* @type int $speed 动画速度 ms (0=自动计算)
|
||||
* @type string $direction 方向: horizontal, vertical (默认 horizontal)
|
||||
* @type int $spaceBetween 幻灯片间距 px (默认 0)
|
||||
* @type string $pagination_type 分页器类型: bullets, fraction, progressbar, thumbnail (默认 bullets)
|
||||
* @type string $pc_height PC 端高度 CSS 值 (默认 auto)
|
||||
* @type string $m_height 移动端高度 CSS 值 (默认 auto)
|
||||
* @type bool $scale_height 是否使用比例高度 (默认 false)
|
||||
* @type float $scale_ratio 比例值 (默认 0.45 = 45%)
|
||||
* @type string $wrapper_class 额外 wrapper 类
|
||||
* @type string $slider_class 额外 slider 类
|
||||
* @type bool $show_nav 是否显示导航箭头 (默认 true)
|
||||
* @type bool $show_pagination 是否显示分页器 (默认 true)
|
||||
* }
|
||||
*
|
||||
* slide 数据格式:
|
||||
* [
|
||||
* 'image' => 'https://...',
|
||||
* 'link' => 'https://...',
|
||||
* 'title' => '标题',
|
||||
* 'desc' => '描述',
|
||||
* 'alt' => '图片 alt',
|
||||
* 'bg_color' => '#f0f0f0', // 背景色
|
||||
* 'target' => '_blank',
|
||||
* ]
|
||||
*
|
||||
* @return string HTML
|
||||
*/
|
||||
function aksrt_render_slider(array $args = []): string {
|
||||
$defaults = [
|
||||
'slides' => [],
|
||||
'effect' => 'slide',
|
||||
'loop' => true,
|
||||
'autoplay' => true,
|
||||
'interval' => 5000,
|
||||
'speed' => 0,
|
||||
'direction' => 'horizontal',
|
||||
'spaceBetween' => 0,
|
||||
'pagination_type' => 'bullets',
|
||||
'pc_height' => 'auto',
|
||||
'm_height' => 'auto',
|
||||
'scale_height' => false,
|
||||
'scale_ratio' => 0.45,
|
||||
'wrapper_class' => '',
|
||||
'slider_class' => '',
|
||||
'show_nav' => true,
|
||||
'show_pagination' => true,
|
||||
];
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
$slides = $args['slides'];
|
||||
|
||||
if (empty($slides) || !is_array($slides)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 构建 data 属性
|
||||
$data_attrs = [];
|
||||
$data_attrs[] = 'data-effect="' . esc_attr($args['effect']) . '"';
|
||||
if ($args['loop']) $data_attrs[] = 'data-loop';
|
||||
if ($args['autoplay']) {
|
||||
$data_attrs[] = 'data-autoplay';
|
||||
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
|
||||
}
|
||||
if ($args['speed'] > 0) $data_attrs[] = 'data-speed="' . esc_attr((string) $args['speed']) . '"';
|
||||
if ($args['direction'] !== 'horizontal') $data_attrs[] = 'data-direction="' . esc_attr($args['direction']) . '"';
|
||||
if ($args['spaceBetween'] > 0) $data_attrs[] = 'data-spaceBetween="' . esc_attr((string) $args['spaceBetween']) . '"';
|
||||
if ($args['pagination_type'] !== 'bullets') $data_attrs[] = 'data-pagination-type="' . esc_attr($args['pagination_type']) . '"';
|
||||
|
||||
// 高度样式
|
||||
$height_style = '';
|
||||
if ($args['scale_height']) {
|
||||
$height_style = sprintf('--pc-height:auto;--m-height:auto;--scale-ratio:%s;', esc_attr((string) $args['scale_ratio']));
|
||||
} else {
|
||||
$height_style = sprintf('--pc-height:%s;--m-height:%s;', esc_attr($args['pc_height']), esc_attr($args['m_height']));
|
||||
}
|
||||
|
||||
// CSS 类
|
||||
$slider_classes = ['new-swiper', 'slide-index'];
|
||||
if ($args['slider_class']) $slider_classes[] = $args['slider_class'];
|
||||
$slider_class_str = implode(' ', $slider_classes);
|
||||
|
||||
$wrapper_classes = ['aksrt-slider-wrapper'];
|
||||
if ($args['wrapper_class']) $wrapper_classes[] = $args['wrapper_class'];
|
||||
$wrapper_class_str = implode(' ', $wrapper_classes);
|
||||
|
||||
// 渲染 HTML
|
||||
$html = '<div class="' . esc_attr($wrapper_class_str) . '">';
|
||||
$html .= '<div class="' . esc_attr($slider_class_str) . '" ' . implode(' ', $data_attrs) . ' style="' . esc_attr($height_style) . '">';
|
||||
$html .= '<div class="swiper-wrapper">';
|
||||
|
||||
foreach ($slides as $slide) {
|
||||
$image = esc_url($slide['image'] ?? '');
|
||||
$link = esc_url($slide['link'] ?? '');
|
||||
$title = sanitize_text_field($slide['title'] ?? '');
|
||||
$desc = sanitize_text_field($slide['desc'] ?? '');
|
||||
$alt = sanitize_text_field($slide['alt'] ?? $title);
|
||||
$bg_color = sanitize_hex_color($slide['bg_color'] ?? '');
|
||||
$target = $slide['target'] === '_blank' ? '_blank' : '_self';
|
||||
|
||||
$slide_style = $bg_color ? 'background-color:' . esc_attr($bg_color) . ';' : '';
|
||||
|
||||
$html .= '<div class="swiper-slide"' . ($slide_style ? ' style="' . esc_attr($slide_style) . '"' : '') . '>';
|
||||
|
||||
if ($link) {
|
||||
$html .= '<a href="' . esc_url($link) . '" target="' . esc_attr($target) . '">';
|
||||
}
|
||||
|
||||
if ($image) {
|
||||
$html .= '<img class="swiper-lazy" data-src="' . esc_url($image) . '" alt="' . esc_attr($alt) . '" loading="lazy">';
|
||||
$html .= '<div class="swiper-lazy-preloader"></div>';
|
||||
}
|
||||
|
||||
if ($link) {
|
||||
$html .= '</a>';
|
||||
}
|
||||
|
||||
// 文字覆盖层
|
||||
if ($title || $desc) {
|
||||
$html .= '<div class="aksrt-slide-text">';
|
||||
if ($title) $html .= '<div class="aksrt-slide-text__title">' . esc_html($title) . '</div>';
|
||||
if ($desc) $html .= '<div class="aksrt-slide-text__desc">' . esc_html($desc) . '</div>';
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>'; // .swiper-wrapper
|
||||
|
||||
if ($args['show_nav']) {
|
||||
$html .= '<div class="swiper-button-prev"></div>';
|
||||
$html .= '<div class="swiper-button-next"></div>';
|
||||
}
|
||||
|
||||
if ($args['show_pagination']) {
|
||||
$html .= '<div class="swiper-pagination"></div>';
|
||||
}
|
||||
|
||||
$html .= '</div>'; // .new-swiper
|
||||
$html .= '</div>'; // .aksrt-slider-wrapper
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染水平滚动器(Tab 导航、横向内容列表)
|
||||
*
|
||||
* @param array $args {
|
||||
* @type array $items 内容数组
|
||||
* @type string $wrapper_class 额外 wrapper 类
|
||||
* @type bool $autoplay 是否自动播放
|
||||
* @type int $interval 自动播放间隔
|
||||
* @type string $render_item 自定义渲染回调 (callable)
|
||||
* }
|
||||
* @return string HTML
|
||||
*/
|
||||
function aksrt_render_scroll_slider(array $args = []): string {
|
||||
$defaults = [
|
||||
'items' => [],
|
||||
'wrapper_class' => '',
|
||||
'autoplay' => false,
|
||||
'interval' => 4000,
|
||||
'render_item' => null,
|
||||
];
|
||||
|
||||
$args = wp_parse_args($args, $defaults);
|
||||
$items = $args['items'];
|
||||
|
||||
if (empty($items)) return '';
|
||||
|
||||
$data_attrs = ['class="swiper-scroll' . ($args['wrapper_class'] ? ' ' . esc_attr($args['wrapper_class']) : '') . '"'];
|
||||
if ($args['autoplay']) {
|
||||
$data_attrs[] = 'data-autoplay';
|
||||
$data_attrs[] = 'data-interval="' . esc_attr((string) $args['interval']) . '"';
|
||||
}
|
||||
|
||||
$html = '<div ' . implode(' ', $data_attrs) . '>';
|
||||
$html .= '<div class="swiper-wrapper">';
|
||||
|
||||
foreach ($items as $i => $item) {
|
||||
$html .= '<div class="swiper-slide">';
|
||||
if (is_callable($args['render_item'])) {
|
||||
$html .= call_user_func($args['render_item'], $item, $i);
|
||||
} else {
|
||||
$html .= '<div class="aksrt-scroll-item">' . esc_html($item['label'] ?? '') . '</div>';
|
||||
}
|
||||
$html .= '</div>';
|
||||
}
|
||||
|
||||
$html .= '</div>';
|
||||
$html .= '<div class="swiper-button-prev"></div>';
|
||||
$html .= '<div class="swiper-button-next"></div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Transaction Helpers
|
||||
*
|
||||
* Frontend transaction view-model helpers.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
function aksrt_get_order_status_labels(): array {
|
||||
if (class_exists('AKSRT_Domain_Model')) {
|
||||
return AKSRT_Domain_Model::order_status_labels();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function aksrt_get_product_page_view(int $product_id): array {
|
||||
if ($product_id <= 0 || !class_exists('AKSRT_Shop')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return AKSRT_Shop::get_instance()->get_product_detail_view($product_id);
|
||||
}
|
||||
|
||||
function aksrt_get_product_edit_page_view(int $user_id, int $product_id = 0): array {
|
||||
if ($user_id <= 0 || !class_exists('AKSRT_Shop')) {
|
||||
return [
|
||||
'allowed' => false,
|
||||
'is_edit' => false,
|
||||
'product' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return AKSRT_Shop::get_instance()->get_product_edit_view($user_id, $product_id);
|
||||
}
|
||||
|
||||
function aksrt_get_cart_page_view(int $user_id): array {
|
||||
if ($user_id <= 0 || !class_exists('AKSRT_Shop')) {
|
||||
return [
|
||||
'payment_methods' => [],
|
||||
'shipping_methods' => [],
|
||||
'address_book' => [],
|
||||
'coupon_enabled' => false,
|
||||
'currency_symbol' => '¥',
|
||||
'pending_orders' => [],
|
||||
];
|
||||
}
|
||||
|
||||
return AKSRT_Shop::get_instance()->get_cart_page_view($user_id);
|
||||
}
|
||||
|
||||
function aksrt_get_orders_page_view(int $user_id, int $paged = 1, int $per_page = 20, string $status = ''): array {
|
||||
if ($user_id <= 0 || !class_exists('AKSRT_Shop')) {
|
||||
return [
|
||||
'items' => [],
|
||||
'total' => 0,
|
||||
'total_pages' => 1,
|
||||
'page' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
return AKSRT_Shop::get_instance()->get_order_center_view($user_id, [
|
||||
'page' => $paged,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
|
||||
function aksrt_get_revenue_page_view(int $user_id): array {
|
||||
$balance = 0.0;
|
||||
$total_earned = 0.0;
|
||||
$total_withdrawn = 0.0;
|
||||
$records = [];
|
||||
|
||||
if (class_exists('AKSRT_Credits')) {
|
||||
$balance = (float) AKSRT_Credits::balance($user_id);
|
||||
}
|
||||
|
||||
if (class_exists('AKSRT_Revenue')) {
|
||||
$revenues = AKSRT_Revenue::get_instance()->get_user_revenues($user_id, ['limit' => 200]);
|
||||
foreach ($revenues as $rev) {
|
||||
$total_earned += (float) ($rev->amount ?? 0);
|
||||
$records[] = [
|
||||
'type' => 'earning',
|
||||
'title' => (string) ($rev->description ?? __('创作分成', 'aksrt')),
|
||||
'amount' => (float) ($rev->amount ?? 0),
|
||||
'created_at' => (string) ($rev->created_at ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (class_exists('AKSRT_Withdrawal')) {
|
||||
$withdrawals = AKSRT_Withdrawal::get_instance()->get_user_withdrawals($user_id, ['limit' => 200]);
|
||||
foreach ($withdrawals as $w) {
|
||||
$total_withdrawn += (float) ($w->amount ?? 0);
|
||||
$records[] = [
|
||||
'type' => 'withdrawal',
|
||||
'title' => __('提现', 'aksrt'),
|
||||
'amount' => (float) ($w->amount ?? 0),
|
||||
'created_at' => (string) ($w->created_at ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($records, static function ($a, $b) {
|
||||
return strcmp($b['created_at'] ?? '', $a['created_at'] ?? '');
|
||||
});
|
||||
|
||||
return [
|
||||
'balance' => $balance,
|
||||
'total_earned' => $total_earned,
|
||||
'total_withdrawn' => $total_withdrawn,
|
||||
'records' => $records,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* User Dashboard Data Helper
|
||||
*
|
||||
* Aggregates user data for author pages and public user homepages.
|
||||
* Shared between author.php and page-user-home.php to avoid duplicate queries.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Get aggregated dashboard view data for a user.
|
||||
*
|
||||
* @param int $user_id Target user ID.
|
||||
* @param array $args Optional. 'viewer_id' => current viewer ID.
|
||||
* @return array {
|
||||
* @type int $post_count Published post count.
|
||||
* @type int $comment_count Approved comment count.
|
||||
* @type int $favorite_count Favorited post count.
|
||||
* @type array $favorite_post_ids Array of favorited post IDs.
|
||||
* @type array $user_contacts Social/contact links from user meta.
|
||||
* @type array $profile_signals Identity chips (verified, level, badges).
|
||||
* @type array $profile_notice Ban/restriction notice, if any.
|
||||
* @type string $background_url Cover image URL.
|
||||
* @type float $credit_balance Credit balance (own profile only).
|
||||
* @type string $currency_name Currency/credits display name.
|
||||
* @type bool $credits_enabled Whether credits system is active.
|
||||
* @type bool $is_own_profile Whether viewer is the user themselves.
|
||||
* }
|
||||
*/
|
||||
function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array {
|
||||
$viewer_id = (int) ($args['viewer_id'] ?? get_current_user_id());
|
||||
$is_own_profile = is_user_logged_in() && $viewer_id === $user_id;
|
||||
|
||||
$post_count = (int) count_user_posts($user_id, 'post', true);
|
||||
$comment_count = (int) get_comments([
|
||||
'user_id' => $user_id,
|
||||
'count' => true,
|
||||
'status' => 'approve',
|
||||
]);
|
||||
|
||||
$favorite_post_ids = function_exists('aksrt_get_user_interaction_posts')
|
||||
? aksrt_get_user_interaction_posts('_aksrt_favorite_posts', $user_id)
|
||||
: [];
|
||||
$favorite_count = count($favorite_post_ids);
|
||||
$recent_posts = get_posts([
|
||||
'author' => $user_id,
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 5,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'no_found_rows' => true,
|
||||
]);
|
||||
|
||||
$user_contacts = get_user_meta($user_id, 'aksrt_user_contacts', true) ?: [];
|
||||
if (!is_array($user_contacts)) {
|
||||
$user_contacts = [];
|
||||
}
|
||||
|
||||
$user_center_settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_user_center_settings()
|
||||
: ['social_links' => true];
|
||||
|
||||
$background_url = get_user_meta($user_id, 'aksrt_background_image', true) ?: '';
|
||||
|
||||
$credits_enabled = class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled();
|
||||
$credit_balance = $credits_enabled && $is_own_profile && function_exists('aksrt_user_credits')
|
||||
? (float) aksrt_user_credits($user_id)
|
||||
: 0.0;
|
||||
$currency_name = $credits_enabled && class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_currency_name()
|
||||
: __('积分', 'aksrt');
|
||||
|
||||
$user_home_manager = class_exists('AKSRT_User_Home') ? AKSRT_User_Home::get_instance() : null;
|
||||
$profile_signals = $user_home_manager ? $user_home_manager->get_profile_signals($user_id) : [];
|
||||
$profile_notice = $user_home_manager ? $user_home_manager->get_profile_notice($user_id) : [];
|
||||
$show_report = $user_home_manager ? $user_home_manager->show_report_entry() : false;
|
||||
|
||||
return [
|
||||
'post_count' => $post_count,
|
||||
'comment_count' => $comment_count,
|
||||
'favorite_count' => $favorite_count,
|
||||
'favorite_post_ids'=> $favorite_post_ids,
|
||||
'recent_posts' => $recent_posts,
|
||||
'user_contacts' => $user_contacts,
|
||||
'social_links_on' => !empty($user_center_settings['social_links']),
|
||||
'profile_signals' => $profile_signals,
|
||||
'profile_notice' => $profile_notice,
|
||||
'show_report' => $show_report,
|
||||
'background_url' => $background_url,
|
||||
'credit_balance' => $credit_balance,
|
||||
'currency_name' => $currency_name,
|
||||
'credits_enabled' => $credits_enabled,
|
||||
'is_own_profile' => $is_own_profile,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT VIP Helper — VIP 会员展示辅助函数
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 检查用户是否为 VIP
|
||||
*
|
||||
* 判定逻辑:
|
||||
* 1. VIP 功能未启用 → false
|
||||
* 2. 用户角色在 exempt_roles 白名单中 → true(管理员/编辑等特权角色)
|
||||
* 3. 用户有有效的 VIP 订单(status=completed 且未过期) → true
|
||||
*
|
||||
* @param int $user_id
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_is_vip(int $user_id = 0): bool {
|
||||
if ($user_id <= 0) $user_id = get_current_user_id();
|
||||
if ($user_id <= 0) return false;
|
||||
|
||||
if (!class_exists('AKSRT_Settings')) return false;
|
||||
|
||||
$vip_settings = AKSRT_Settings::get_payment_vip_settings();
|
||||
if (empty($vip_settings['enabled'])) return false;
|
||||
|
||||
// 1. 角色豁免:VIP exempt_roles 中的角色直接视为 VIP
|
||||
$paid_settings = AKSRT_Settings::get_paid_content_settings();
|
||||
$exempt_roles = $paid_settings['vip_exempt_roles'] ?? [];
|
||||
if (!empty($exempt_roles)) {
|
||||
$user = get_userdata($user_id);
|
||||
if ($user && !empty(array_intersect($user->roles, $exempt_roles))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 订单判定:检查是否有有效的 VIP 订单
|
||||
if (class_exists('AKSRT_Order')) {
|
||||
$order_mgr = AKSRT_Order::get_instance();
|
||||
$valid_order = method_exists($order_mgr, 'get_valid_vip_order')
|
||||
? $order_mgr->get_valid_vip_order($user_id)
|
||||
: null;
|
||||
if ($valid_order) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 VIP 徽标文本
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_vip_badge_text(): string {
|
||||
if (!class_exists('AKSRT_Settings')) return 'VIP';
|
||||
|
||||
$vip_settings = AKSRT_Settings::get_payment_vip_settings();
|
||||
return sanitize_text_field($vip_settings['badge_text'] ?? 'VIP');
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染 VIP 徽标 HTML
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param string $size sm | md | lg
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_render_vip_badge(int $user_id = 0, string $size = 'sm'): string {
|
||||
if (!aksrt_is_vip($user_id)) return '';
|
||||
|
||||
$badge_text = aksrt_get_vip_badge_text();
|
||||
$size_class = 'aksrt-vip-badge--' . $size;
|
||||
|
||||
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>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户 VIP 等级信息(用于展示)
|
||||
*
|
||||
* @param int $user_id
|
||||
* @return array{is_vip: bool, badge_text: string, badge_html: string, name: string}
|
||||
*/
|
||||
function aksrt_get_vip_info(int $user_id = 0): array {
|
||||
$is_vip = aksrt_is_vip($user_id);
|
||||
$badge_text = aksrt_get_vip_badge_text();
|
||||
|
||||
return [
|
||||
'is_vip' => $is_vip,
|
||||
'badge_text' => $badge_text,
|
||||
'badge_html' => $is_vip ? aksrt_render_vip_badge($user_id) : '',
|
||||
'name' => $is_vip ? ($badge_text . '会员') : '普通用户',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Navigation Walker
|
||||
*
|
||||
* Custom walker for WordPress nav menus that adds:
|
||||
* - FA5 icons from menu item classes
|
||||
* - Badge labels from title attribute pattern [text:type]
|
||||
* - Submenu style overrides (dropdown / mega / cards)
|
||||
* - CSS class augmentation for hover effects
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Nav_Walker extends Walker_Nav_Menu {
|
||||
|
||||
/**
|
||||
* @var array Decoration settings from aksrt_get_menu_decoration_settings().
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
public function __construct(array $settings = []) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前处理的菜单项是否为 Mega Menu 容器
|
||||
* @var bool
|
||||
*/
|
||||
private $is_mega_menu = false;
|
||||
|
||||
/**
|
||||
* Mega Menu 列数
|
||||
* @var int
|
||||
*/
|
||||
private $mega_columns = 3;
|
||||
|
||||
/**
|
||||
* Start level: open submenu with appropriate class based on style.
|
||||
*/
|
||||
public function start_lvl(&$output, $depth = 0, $args = null): void {
|
||||
$indent = str_repeat("\t", $depth);
|
||||
|
||||
if ($this->is_mega_menu && $depth === 0) {
|
||||
// Mega Menu 子菜单容器
|
||||
$output .= "\n{$indent}<ul class=\"sub-menu mega-menu__panel mega-menu__columns-{$this->mega_columns}\">\n";
|
||||
} else {
|
||||
$output .= "\n{$indent}<ul class=\"sub-menu\">\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start element: add icon, badge, and classes.
|
||||
*/
|
||||
public function start_el(&$output, $item, $depth = 0, $args = null, $id = 0): void {
|
||||
$indent = ($depth) ? str_repeat("\t", $depth) : '';
|
||||
|
||||
$classes = (array) $item->classes;
|
||||
$decoration = function_exists('aksrt_get_menu_item_decoration')
|
||||
? aksrt_get_menu_item_decoration($item)
|
||||
: ['icon' => '', 'badge_text' => '', 'badge_type' => 'new', 'submenu_style' => ''];
|
||||
|
||||
// 检测 Mega Menu 标记
|
||||
$this->is_mega_menu = false;
|
||||
$this->mega_columns = 3;
|
||||
if ($depth === 0) {
|
||||
foreach ($classes as $cls) {
|
||||
if ($cls === 'mega-menu') {
|
||||
$this->is_mega_menu = true;
|
||||
}
|
||||
if (preg_match('/^mega-menu-columns-(\d)$/', $cls, $m)) {
|
||||
$this->mega_columns = max(2, min(5, (int) $m[1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build li classes
|
||||
$li_classes = ['menu-item', 'menu-item-' . $item->ID];
|
||||
if (in_array('current-menu-item', $classes, true)) {
|
||||
$li_classes[] = 'current-menu-item';
|
||||
}
|
||||
if (in_array('menu-item-has-children', $classes, true)) {
|
||||
$li_classes[] = 'menu-item-has-children';
|
||||
}
|
||||
if ($this->is_mega_menu) {
|
||||
$li_classes[] = 'mega-menu-item';
|
||||
}
|
||||
|
||||
// Add decoration classes
|
||||
$li_classes[] = 'aksrt-nav__item';
|
||||
if ($decoration['icon'] !== '' && !empty($this->settings['icon_enabled'])) {
|
||||
$li_classes[] = 'aksrt-nav__item--has-icon';
|
||||
}
|
||||
if ($decoration['badge_text'] !== '' && !empty($this->settings['badge_enabled'])) {
|
||||
$li_classes[] = 'aksrt-nav__item--has-badge';
|
||||
}
|
||||
$hover = $this->settings['hover_effect'] ?? 'underline';
|
||||
if ($hover !== 'none') {
|
||||
$li_classes[] = 'aksrt-nav__item--hover-' . $hover;
|
||||
}
|
||||
|
||||
$class_str = implode(' ', array_unique($li_classes));
|
||||
$output .= $indent . '<li class="' . esc_attr($class_str) . '" data-menu-item-id="' . (int) $item->ID . '" data-menu-depth="' . (int) $depth . '"' . (in_array('menu-item-has-children', $classes, true) ? ' data-menu-has-children="1"' : '') . '>';
|
||||
|
||||
// Build link content
|
||||
$icon_html = '';
|
||||
if ($decoration['icon'] !== '' && !empty($this->settings['icon_enabled'])) {
|
||||
$icon_html = aksrt_render_menu_icon($decoration['icon'], true);
|
||||
}
|
||||
|
||||
$badge_html = '';
|
||||
if ($decoration['badge_text'] !== '' && !empty($this->settings['badge_enabled'])) {
|
||||
$badge_html = aksrt_render_menu_badge($decoration['badge_text'], $decoration['badge_type'], true);
|
||||
}
|
||||
|
||||
// Mega Menu 子项:检查是否有描述文字(用作卡片副标题)
|
||||
$description_html = '';
|
||||
if ($this->is_mega_menu && $depth === 1 && !empty($item->description)) {
|
||||
$description_html = '<span class="mega-menu__item-desc">' . esc_html($item->description) . '</span>';
|
||||
}
|
||||
|
||||
$atts = [
|
||||
'href' => $item->url,
|
||||
'title' => $item->attr_title ?: '',
|
||||
];
|
||||
|
||||
$link_inner = $icon_html . '<span>' . esc_html($item->title) . '</span>' . $description_html . $badge_html;
|
||||
|
||||
$output .= '<a';
|
||||
foreach ($atts as $attr => $value) {
|
||||
if ('' !== $value) {
|
||||
$output .= ' ' . $attr . '="' . esc_attr($value) . '"';
|
||||
}
|
||||
}
|
||||
$output .= '>' . $link_inner . '</a>';
|
||||
}
|
||||
|
||||
/**
|
||||
* End element.
|
||||
*/
|
||||
public function end_el(&$output, $item, $depth = 0, $args = null): void {
|
||||
$output .= "</li>\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
/**
|
||||
* Menu Decoration Helpers
|
||||
*
|
||||
* Helper functions for reading menu item meta (icons, badges, submenu styles)
|
||||
* and rendering decorated menu items.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Get menu decoration settings from AKSRT_Settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_menu_decoration_settings(): array {
|
||||
static $cache = null;
|
||||
if ($cache !== null) {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
if (class_exists('AKSRT_Settings')) {
|
||||
$all = AKSRT_Settings::get_all();
|
||||
} else {
|
||||
$all = [];
|
||||
}
|
||||
|
||||
$cache = [
|
||||
'icon_enabled' => !empty($all['menu_icon_enabled']),
|
||||
'badge_enabled' => !empty($all['menu_badge_enabled']),
|
||||
'submenu_style' => in_array($all['menu_submenu_style'] ?? '', ['dropdown', 'mega', 'cards'], true)
|
||||
? $all['menu_submenu_style']
|
||||
: 'dropdown',
|
||||
'submenu_columns' => max(1, min(5, (int) ($all['menu_submenu_columns'] ?? 1))),
|
||||
'submenu_width' => max(160, min(800, (int) ($all['menu_submenu_width'] ?? 220))),
|
||||
'hover_effect' => in_array($all['menu_hover_effect'] ?? '', ['underline', 'background', 'color', 'none'], true)
|
||||
? $all['menu_hover_effect']
|
||||
: 'underline',
|
||||
'item_padding' => max(4, min(32, (int) ($all['menu_item_padding'] ?? 12))),
|
||||
'mobile_style' => in_array($all['menu_mobile_style'] ?? '', ['drawer', 'fullscreen', 'dropdown'], true)
|
||||
? $all['menu_mobile_style']
|
||||
: 'drawer',
|
||||
];
|
||||
|
||||
return $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get decoration meta for a specific menu item.
|
||||
*
|
||||
* Reads CSS class and title attribute from the menu item to extract:
|
||||
* - icon: FA5 class (e.g. "fas fa-home")
|
||||
* - badge_text: badge label (e.g. "新", "热")
|
||||
* - badge_type: badge color type (e.g. "new", "hot", "info")
|
||||
* - submenu_style: override for this item's submenu
|
||||
*
|
||||
* @param WP_Post $menu_item The menu item post object.
|
||||
* @return array
|
||||
*/
|
||||
function aksrt_get_menu_item_decoration($menu_item): array {
|
||||
$classes = (array) ($menu_item->classes ?? []);
|
||||
$title = (string) ($menu_item->title ?? '');
|
||||
$attr = (string) ($menu_item->attr_title ?? '');
|
||||
|
||||
$result = [
|
||||
'icon' => '',
|
||||
'badge_text' => '',
|
||||
'badge_type' => 'new',
|
||||
'submenu_style' => '',
|
||||
];
|
||||
|
||||
// Extract icon from CSS classes (pattern: icon-* or fa-*)
|
||||
foreach ($classes as $cls) {
|
||||
$cls = trim($cls);
|
||||
if (strpos($cls, 'fa-') === 0 || strpos($cls, 'fas ') === 0 || strpos($cls, 'fab ') === 0 || strpos($cls, 'far ') === 0) {
|
||||
$result['icon'] = $cls;
|
||||
break;
|
||||
}
|
||||
if (strpos($cls, 'icon-') === 0) {
|
||||
$result['icon'] = 'fas fa-' . substr($cls, 5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract badge from title attribute (pattern: [新] or [热:hot])
|
||||
if (preg_match('/^\[([^\]:]+)(?::(\w+))?\]/', $title, $m)) {
|
||||
$result['badge_text'] = $m[1];
|
||||
$result['badge_type'] = $m[2] ?? 'new';
|
||||
// Remove badge markup from display title
|
||||
$menu_item->title = trim(substr($title, strlen($m[0])));
|
||||
}
|
||||
|
||||
// Extract submenu style override from classes
|
||||
foreach ($classes as $cls) {
|
||||
if (strpos($cls, 'submenu-') === 0) {
|
||||
$style = substr($cls, 8);
|
||||
if (in_array($style, ['dropdown', 'mega', 'cards'], true)) {
|
||||
$result['submenu_style'] = $style;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a menu item icon if enabled.
|
||||
*
|
||||
* @param string $icon_class FA5 icon class.
|
||||
* @param bool $enabled Whether icons are enabled globally.
|
||||
* @return string HTML string.
|
||||
*/
|
||||
function aksrt_render_menu_icon(string $icon_class, bool $enabled = true): string {
|
||||
if (!$enabled || $icon_class === '') {
|
||||
return '';
|
||||
}
|
||||
return '<i class="' . esc_attr($icon_class) . ' aksrt-nav__icon" aria-hidden="true"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a menu item badge if enabled.
|
||||
*
|
||||
* @param string $text Badge text.
|
||||
* @param string $type Badge type (new, hot, info, etc.).
|
||||
* @param bool $enabled Whether badges are enabled globally.
|
||||
* @return string HTML string.
|
||||
*/
|
||||
function aksrt_render_menu_badge(string $text, string $type = 'new', bool $enabled = true): string {
|
||||
if (!$enabled || $text === '') {
|
||||
return '';
|
||||
}
|
||||
return '<span class="aksrt-nav__badge aksrt-nav__badge--' . esc_attr($type) . '">' . esc_html($text) . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS classes for a menu item based on decoration settings.
|
||||
*
|
||||
* @param array $decoration Decoration meta from aksrt_get_menu_item_decoration().
|
||||
* @param array $settings Global decoration settings.
|
||||
* @return string Space-separated CSS classes.
|
||||
*/
|
||||
function aksrt_get_menu_item_classes(array $decoration, array $settings): string {
|
||||
$classes = ['aksrt-nav__item'];
|
||||
|
||||
if ($settings['hover_effect'] !== 'none') {
|
||||
$classes[] = 'aksrt-nav__item--hover-' . $settings['hover_effect'];
|
||||
}
|
||||
|
||||
if ($decoration['icon'] !== '') {
|
||||
$classes[] = 'aksrt-nav__item--has-icon';
|
||||
}
|
||||
|
||||
if ($decoration['badge_text'] !== '') {
|
||||
$classes[] = 'aksrt-nav__item--has-badge';
|
||||
}
|
||||
|
||||
return implode(' ', $classes);
|
||||
}
|
||||
Reference in new issue
Block a user