Files
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

558 lines
22 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Jio Resource Helper
*
* 资源(CMS)相关的顶层辅助函数。
*
* 提供给模板层调用,将原始 post meta 转成视图友好的结构,
* 同时承担"显示策略"判定(是否显示价格/版本/下载按钮等)。
*
* @package Jio
*/
defined('ABSPATH') || exit;
if (!function_exists('jio_get_resource_meta')) {
/**
* 一次性取齐资源的全部元数据,已带默认值。
*
* 单次 get_post_meta($id, '', true) 调用 + WordPress 内部 meta 缓存命中,
* 避免 11 次单独查询。
*
* @param int|WP_Post|null $post
* @return array
*/
function jio_get_resource_meta($post = null): array {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return jio_default_resource_meta();
}
$pay_meta = function_exists('jio_get_pay_meta')
? jio_get_pay_meta($p)
: \Jio\Core\DomainModel::default_pay_meta();
$download_count = (int) get_post_meta($p->ID, \Jio\Core\DomainModel::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, \Jio\Core\DomainModel::RESOURCE_VIEW_COUNT_KEY, true);
$common_view_count = (int) get_post_meta($p->ID, \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY, true);
// Normalize downloads extract_code aliases for legacy rows still stored on pay meta.
$downloads = array_values(is_array($pay_meta['downloads'] ?? null) ? $pay_meta['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)));
$virtual_sales = max(0, (int) ($pay_meta['virtual_sales'] ?? 0));
$show_sales = !isset($pay_meta['show_sales']) || !empty($pay_meta['show_sales']);
return [
'version' => (string) ($pay_meta['version'] ?? ''),
'size' => (string) ($pay_meta['size'] ?? ''),
'price' => (string) ($pay_meta['price'] ?? '0.00'),
'points_price' => (int) ($pay_meta['points_price'] ?? 0),
'pay_modo' => (($pay_meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0',
'pay_limit' => in_array((int) ($pay_meta['pay_limit'] ?? 0), [0, 1, 2], true) ? (int) $pay_meta['pay_limit'] : 0,
'original_price' => (string) ($pay_meta['original_price'] ?? '0.00'),
'vip1_price' => (string) ($pay_meta['vip1_price'] ?? '0.00'),
'vip2_price' => (string) ($pay_meta['vip2_price'] ?? '0.00'),
'vip1_points' => (int) ($pay_meta['vip1_points'] ?? 0),
'vip2_points' => (int) ($pay_meta['vip2_points'] ?? 0),
'pay_type' => (string) ($pay_meta['pay_type'] ?? \Jio\Core\DomainModel::PAY_TYPE_DOWNLOAD),
'license' => (string) ($pay_meta['license'] ?? ''),
'compat' => (string) ($pay_meta['compat'] ?? ''),
'demo_url' => (string) ($pay_meta['demo_url'] ?? ''),
'downloads' => $downloads,
'download_count' => max(0, max($download_count, $legacy_download_count)),
'view_count' => max(0, max($resource_view_count, $common_view_count)),
'virtual_sales' => $virtual_sales,
// 展示销量 = 真实订单 + 后台「销量浮动」(pay_count),与内容帖同一口径。
'sales_count' => $show_sales ? jio_get_content_sales_count($p, $pay_meta) : 0,
'show_sales' => $show_sales,
'require_login' => !empty($pay_meta['require_login']),
'require_captcha' => !empty($pay_meta['require_captcha']),
];
}
}
if (!function_exists('jio_default_resource_meta')) {
function jio_default_resource_meta(): array {
$meta = \Jio\Core\DomainModel::default_pay_meta();
return [
'version' => (string) ($meta['version'] ?? ''),
'size' => (string) ($meta['size'] ?? ''),
'price' => (string) ($meta['price'] ?? '0.00'),
'points_price' => (int) ($meta['points_price'] ?? 0),
'pay_modo' => (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0',
'pay_limit' => in_array((int) ($meta['pay_limit'] ?? 0), [0, 1, 2], true) ? (int) $meta['pay_limit'] : 0,
'original_price' => (string) ($meta['original_price'] ?? '0.00'),
'vip1_price' => (string) ($meta['vip1_price'] ?? '0.00'),
'vip2_price' => (string) ($meta['vip2_price'] ?? '0.00'),
'vip1_points' => (int) ($meta['vip1_points'] ?? 0),
'vip2_points' => (int) ($meta['vip2_points'] ?? 0),
'pay_type' => \Jio\Core\DomainModel::PAY_TYPE_DOWNLOAD,
'license' => (string) ($meta['license'] ?? ''),
'compat' => (string) ($meta['compat'] ?? ''),
'demo_url' => (string) ($meta['demo_url'] ?? ''),
'downloads' => [],
'download_count' => 0,
'view_count' => 0,
'virtual_sales' => 0,
'sales_count' => 0,
'show_sales' => true,
'require_login' => !empty($meta['require_login']),
'require_captcha' => !empty($meta['require_captcha']),
];
}
}
if (!function_exists('jio_resource_price_label')) {
/**
* 把价格数字渲染成展示徽章文案。
*
* @param int $price
* @return array { text:string, free:bool }
*/
function jio_resource_price_label(int $price): array {
if ($price <= 0) {
return [
'text' => __('免费', 'jio'),
'free' => true,
];
}
return [
'text' => sprintf(__('%s 积分', 'jio'), (string) $price),
'free' => false,
];
}
}
if (!function_exists('jio_resource_price_amount')) {
/**
* Format the raw resource price number for compact UI labels.
*
* @param int $price
* @return string
*/
function jio_resource_price_amount(int $price): string {
return (string) max(0, $price);
}
}
if (!function_exists('jio_resource_card_badge_text')) {
/**
* List-card pay badge (legacy equivalent).
*
* Renders for any paid post type, not only download resources.
* Returns plain text; card template wraps with link + class.
*
* @param int|WP_Post|null $post
* @return string
*/
function jio_resource_card_badge_text($post = null): string {
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return '';
}
$meta = function_exists('jio_get_pay_meta') ? jio_get_pay_meta($p) : null;
if (!is_array($meta)) {
return '';
}
$pay_type = (string) ($meta['pay_type'] ?? 'no');
if ($pay_type === '' || $pay_type === 'no') {
return '';
}
$pay_modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
$pay_limit = (int) ($meta['pay_limit'] ?? 0);
$type_labels = function_exists('jio_get_pay_types') ? jio_get_pay_types() : [];
$type_name = $type_labels[$pay_type] ?? __('付费内容', 'jio');
// VIP-only / purchase limit (legacy pay_limit).
if ($pay_type === 'vip' || $pay_limit > 0) {
return __('会员专属', 'jio');
}
$priced = function_exists('jio_get_user_pay_price')
? jio_get_user_pay_price($p)
: [
'amount' => $pay_modo === 'points' ? (int) ($meta['points_price'] ?? 0) : (string) ($meta['price'] ?? '0.00'),
'modo' => $pay_modo,
];
$price = $priced['amount'] ?? ($pay_modo === 'points' ? 0 : '0.00');
$pay_modo = (($priced['modo'] ?? $pay_modo) === 'points') ? 'points' : '0';
$price_minor = function_exists('jio_asset_price_minor') ? jio_asset_price_minor($price, $pay_modo) : 0;
// Free paid-type (price 0).
if ($price_minor <= 0) {
if ($pay_type === 'download') {
return __('免费资源', 'jio');
}
return str_replace(__('付费', 'jio'), __('免费', 'jio'), (string) $type_name);
}
if ($pay_modo === 'points') {
$currency = class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_currency_name() : __('积分', 'jio');
$amount = function_exists('jio_resource_price_amount')
? jio_resource_price_amount($price_minor)
: (string) (int) $price;
return sprintf('%s %s%s', $type_name, $amount, $currency);
}
$symbol = '¥';
if (class_exists('\Jio\Settings\Settings')) {
$paid = \Jio\Settings\Settings::get_paid_content_settings();
$symbol = (string) ($paid['display']['currency_symbol'] ?? '¥');
}
$amount = function_exists('jio_format_price')
? jio_format_price($price)
: (string) $price;
return sprintf('%s %s%s', $type_name, $symbol, $amount);
}
}
if (!function_exists('jio_resource_pay_type_badge')) {
/**
* 根据付费类型返回徽章 HTML。
*
* @param string $pay_type
* @return string HTML 或空字符串
*/
function jio_resource_pay_type_badge(string $pay_type): string {
$badges = [
'reading' => ['label' => __('付费阅读', 'jio'), 'color' => '#EF4444', 'icon' => 'fa-lock'],
'download'=> ['label' => __('付费下载', 'jio'), 'color' => '#F97316', 'icon' => 'fa-download'],
'gallery' => ['label' => __('付费图库', 'jio'), 'color' => '#10B981', 'icon' => 'fa-image'],
'video' => ['label' => __('付费视频', 'jio'), 'color' => '#8B5CF6', 'icon' => 'fa-play-circle'],
'vip' => ['label' => __('VIP 专属', 'jio'), 'color' => '#3B82F6', 'icon' => 'fa-certificate'],
];
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="fa %s"></i> %s</span>',
esc_attr($b['color']),
esc_attr($b['icon']),
esc_html($b['label'])
);
}
}
if (!function_exists('jio_resource_preview_images')) {
/**
* Collect preview images for resource cards and archive thumbnails.
*
* Priority: curated featured-slide images -> featured image -> gallery/content images.
*
* @param int|WP_Post|null $post
* @param int $limit
* @return string[]
*/
function jio_resource_preview_images($post = null, int $limit = 4): array {
$p = get_post($post);
if (!($p instanceof WP_Post) || !function_exists('jio_is_resource_post') || !jio_is_resource_post($p)) {
return [];
}
$limit = max(1, min(8, $limit));
$images = [];
if (class_exists('\Jio\Core\PostMetaBox')) {
$featured_slide = (string) \Jio\Core\PostMetaBox::get_meta_value($p->ID, 'featured_slide', '');
if ($featured_slide !== '') {
$cover_ids = array_filter(array_map('absint', preg_split('/[\s,]+/', $featured_slide) ?: []));
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('jio_get_post_gallery_images')) {
$images = array_merge($images, jio_get_post_gallery_images($p->ID, $limit));
}
if (function_exists('jio_get_first_content_image_url')) {
$content_image = (string) jio_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('jio_resource_primary_image_url')) {
/**
* Primary preview image for a resource post.
*
* @param int|WP_Post|null $post
* @return string
*/
function jio_resource_primary_image_url($post = null): string {
$images = jio_resource_preview_images($post, 1);
return $images[0] ?? '';
}
}
if (!function_exists('jio_resource_meta_strip')) {
/**
* 卡片用的紧凑元数据条 [v1.0 · 210MB · ↓1234]。
*
* @param array $meta jio_get_resource_meta 返回值
* @return array 字符串数组
*/
function jio_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('jio_is_download_credit_exempt_user')) {
function jio_is_download_credit_exempt_user(int $user_id = 0): bool {
if ($user_id <= 0 || !class_exists('\Jio\Settings\Settings')) {
return false;
}
$paid_settings = \Jio\Settings\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('jio_can_download')) {
/**
* 是否可下载。
*
* 单一合同:委托 \Jio\Content\Visibility(收银台订单解锁 / VIP / 登录 / 日限额)。
* 不再用 cms_features.download_credit 做「按次扣积分」门禁。
*
* @param int|WP_Post|null $post
* @param int $user_id 0 表示当前用户
* @return true|WP_Error
*/
function jio_can_download($post = null, int $user_id = 0) {
if (class_exists('\Jio\Content\Visibility')) {
return \Jio\Content\Visibility::can_download_resource($post, $user_id);
}
$p = get_post($post);
if (!($p instanceof WP_Post)) {
return new WP_Error('jio_not_resource', __('该内容不是可下载资源。', 'jio'));
}
$uid = $user_id > 0 ? $user_id : get_current_user_id();
if (!function_exists('jio_user_can') || !jio_user_can('resource_download', $uid, (int) $p->ID)) {
return new WP_Error('jio_need_login', __('请先登录后再下载。', 'jio'), [
'status' => 401,
'login_url' => wp_login_url(get_permalink($p)),
]);
}
// Without Content_Visibility, only login gate remains.
return true;
}
}
if (!function_exists('jio_resource_download_cost')) {
/**
* 下载侧展示用「未解锁积分价」。
*
* 现金 pay_modo:永远 0(走订单解锁,不按次扣积分)。
* 积分 pay_modo:未购时返回积分价(用于 UI);已购订单后 0。
* 实际扣费只在收银台创建 resource 订单时发生,下载 token 不再扣积分。
*/
function jio_resource_download_cost($post = null): int {
$p = get_post($post);
$uid = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
// Paid order → unlocked.
if ($p instanceof WP_Post && $uid > 0 && class_exists('\Jio\Payment\Order')) {
if (\Jio\Payment\Order::get_instance()->has_paid_access(
$uid,
\Jio\Core\DomainModel::ORDER_PRODUCT_RESOURCE,
(int) $p->ID
)) {
return 0;
}
}
if ($p instanceof WP_Post && $uid <= 0 && function_exists('jio_guest_has_paid_access') && jio_guest_has_paid_access((int) $p->ID)) {
return 0;
}
$priced = function_exists('jio_get_user_pay_price')
? jio_get_user_pay_price($post, $uid)
: null;
$pay_modo = is_array($priced) && (($priced['modo'] ?? '0') === 'points') ? 'points' : '0';
if (!is_array($priced) && function_exists('jio_get_pay_meta')) {
$pay = jio_get_pay_meta($post);
$pay_modo = (($pay['pay_modo'] ?? '0') === 'points') ? 'points' : '0';
}
// Cash mode: order unlock only.
if ($pay_modo !== 'points') {
return 0;
}
if ($uid > 0 && function_exists('jio_is_download_credit_exempt_user') && jio_is_download_credit_exempt_user($uid)) {
return 0;
}
if (is_array($priced)) {
return is_int($priced['amount'] ?? null) ? max(0, (int) $priced['amount']) : 0;
}
$meta = function_exists('jio_get_pay_meta') ? jio_get_pay_meta($post) : [];
return max(0, (int) ($meta['points_price'] ?? 0));
}
}
if (!function_exists('jio_resource_meta_query')) {
/**
* 「是资源」WP_Query meta_query 片段:post + _jio_is_resource = 1。
*
* @return array
*/
function jio_resource_meta_query(): array {
return [
'key' => '_jio_is_resource',
'value' => '1',
'compare' => '=',
];
}
}
if (!function_exists('jio_resource_archive_url')) {
/**
* 资源列表页 URL(/resources/)。
*/
function jio_resource_archive_url(): string {
return home_url('/resources/');
}
}
if (!function_exists('jio_is_resource_archive')) {
/**
* 当前请求是否为资源列表(/resources 虚拟归档)。
*/
function jio_is_resource_archive(): bool {
return (bool) get_query_var('jio_is_resource_archive');
}
}
if (!function_exists('jio_detect_cloud_service')) {
/**
* 从下载链接自动识别云盘服务商。
*
* @param string $url 下载链接
* @return array { key:string, name:string, color:string }
*/
function jio_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' => __('下载', 'jio'), 'color' => '#6b7280'];
}
}
if (!function_exists('jio_resource_meta_icon')) {
/**
* 根据元信息字段返回对应的 Font Awesome 图标类。
*/
function jio_resource_meta_icon(string $field): string {
$icons = [
'version' => 'fa fas fa-code-branch',
'size' => 'fa fas fa-hdd',
'compat' => 'fa fas fa-laptop',
'license' => 'fa fas fa-shield-alt',
'demo_url' => 'fa fas fa-external-link-alt',
'download' => 'fa fas fa-download',
'views' => 'fa fas fa-eye',
];
return $icons[$field] ?? 'fa fas fa-info-circle';
}
}