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); // 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))); 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' => $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']), ]; } } 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']), ]; } } 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')) { /** * List-card pay badge (Zibll zib_get_posts_list_pay_tags 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 aksrt_resource_card_badge_text($post = null): string { $p = get_post($post); if (!($p instanceof WP_Post)) { return ''; } $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($p) : null; if (!is_array($meta)) { return ''; } $pay_type = (string) ($meta['pay_type'] ?? 'no'); if ($pay_type === '' || $pay_type === 'no') { return ''; } $price = (float) ($meta['price'] ?? 0); $type_labels = function_exists('aksrt_get_pay_types') ? aksrt_get_pay_types() : []; $type_name = $type_labels[$pay_type] ?? __('付费内容', 'aksrt'); // VIP-only posts. if ($pay_type === 'vip') { return __('会员专属', 'aksrt'); } // Free paid-type (price 0): Zibll shows "免费资源" / free type name. if ($price <= 0) { if ($pay_type === 'download') { return __('免费资源', 'aksrt'); } // e.g. 付费阅读 → 免费阅读 return str_replace(__('付费', 'aksrt'), __('免费', 'aksrt'), (string) $type_name); } $currency = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); $amount = function_exists('aksrt_resource_price_amount') ? aksrt_resource_price_amount($price) : (string) $price; return sprintf('%s %s%s', $type_name, $amount, $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( ' %s', 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 片段:post + _aksrt_is_resource = 1。 * * @return array */ function aksrt_resource_meta_query(): array { return [ 'key' => '_aksrt_is_resource', 'value' => '1', 'compare' => '=', ]; } } if (!function_exists('aksrt_resource_archive_url')) { /** * 资源列表页 URL(/resources/)。 */ function aksrt_resource_archive_url(): string { return home_url('/resources/'); } } if (!function_exists('aksrt_is_resource_archive')) { /** * 当前请求是否为资源列表(/resources 虚拟归档)。 */ function aksrt_is_resource_archive(): bool { 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'; } }