Files
wordpress-theme-aksrt/inc/helpers/aksrt-domain-model.php
T
kate522 8f15d16d35 feat(pay): sales volume, paid order meta, pending resume, limit-over buy
Bump content sales on resource order paid; show order number/time in download
box; poll pending gateway payments; support download_limit_over_price overbuy.
2026-07-14 12:04:59 +08:00

441 lines
16 KiB
PHP

<?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 RESOURCE_SALES_VOLUME_KEY = '_aksrt_sales_volume';
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' => '',
// Episode list: [ ['url' => string, 'title' => string], ... ]
'featured_video_episode' => [],
'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,
'pay_modo' => '0', // '0' cash | 'points' credits (Zibll)
'price' => 0.0,
'points_price' => 0,
'original_price' => 0.0,
'vip1_price' => 0.0,
'vip2_price' => 0.0,
'vip1_points' => 0,
'vip2_points' => 0,
'pay_limit' => 0, // 0 all | 1 VIP1+ | 2 VIP2 only (Zibll)
'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,
// Zibll: free daily quota overbuy price (cash or points by pay_modo).
'download_limit_over_price' => 0.0,
];
}
/**
* @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']));
$pay_modo = sanitize_key((string) ($raw['pay_modo'] ?? '0'));
$meta['pay_modo'] = $pay_modo === 'points' ? 'points' : '0';
$meta['price'] = max(0, (float) ($raw['price'] ?? 0));
$meta['points_price'] = max(0, (int) ($raw['points_price'] ?? $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['vip1_points'] = max(0, (int) ($raw['vip1_points'] ?? $raw['vip1_price'] ?? 0));
$meta['vip2_points'] = max(0, (int) ($raw['vip2_points'] ?? $raw['vip2_price'] ?? 0));
$pay_limit = (int) ($raw['pay_limit'] ?? 0);
$meta['pay_limit'] = in_array($pay_limit, [0, 1, 2], true) ? $pay_limit : 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']);
$meta['download_limit_over_price'] = max(0, (float) ($raw['download_limit_over_price'] ?? 0));
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();
}
}