diff --git a/assets/js/cms-download.js b/assets/js/cms-download.js index 15b69868..03e2ec09 100644 --- a/assets/js/cms-download.js +++ b/assets/js/cms-download.js @@ -2,9 +2,9 @@ * AKSRT CMS Download * * 功能: - * - 监听 [data-action="aksrt-download"] 按钮点击 - * - 调 POST /aksrt/v1/download/request - * - 成功后直接打开生成的下载链接;失败/购买等场景仍走弹窗 + * - 监听购买入口(data-aksrt-pay-content / data-action="aksrt-need-purchase") + * - 内容购买收银台、扫码支付与错误提示弹窗 + * - 下载已改为直连:按钮为源地址锚点,不再经过本站中转请求 * * 弹窗视觉走主题 design token(见 assets/css/frontend/cms-download.css), * 付费收银台沿用参考实现的 colorful header 结构。 @@ -140,7 +140,6 @@ // ===== 统一视图壳(下载 / 错误 / 扫码共用主题化外观) ===== // 头部与按钮统一继承运行时主题色(--focus-color),状态用图标 + 标题区分。 const DL_VIEWS = { - success: { icon: 'fa-cloud-download-alt', title: '下载已就绪' }, login: { icon: 'fa-user-lock', title: '请先登录' }, purchase: { icon: 'fa-shopping-cart', title: '请先购买' }, vip: { icon: 'fa-gem', title: '会员专属' }, @@ -251,34 +250,8 @@ } } - // ===== 成功:直接打开生成的下载链接,不再弹窗 ===== - function startDownload(payload, returnFocusEl) { - const url = payload && payload.dl_url ? String(payload.dl_url) : ''; - if (!url) { - renderError('下载链接生成失败,请稍后重试', {}, returnFocusEl); - return; - } - // 与旧版弹窗内「立即下载」一致:新标签页打开本站中转链接。 - const link = document.createElement('a'); - link.href = url; - link.target = '_blank'; - link.rel = 'noopener noreferrer'; - document.body.appendChild(link); - link.click(); - link.remove(); - } - // ===== 主流程 ===== - function captchaScope(sourceEl) { - const box = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null; - return box ? (box.querySelector('[data-aksrt-download-captcha]') || box) : sourceEl; - } - function isCaptchaActive(sourceEl) { - const card = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null; - if (!card) return false; - return card.getAttribute('data-captcha') === '1'; - } function cashierHeader() { return ''; } } diff --git a/inc/helpers/homepage/header-components.php b/inc/helpers/homepage/header-components.php index 339020f0..f212a4db 100644 --- a/inc/helpers/homepage/header-components.php +++ b/inc/helpers/homepage/header-components.php @@ -49,13 +49,10 @@ if (!function_exists('aksrt_header_slide_current_page_type')) { * * Custom rewrite requests can retain WordPress's is_home flag, so the * explicit theme surfaces must win before the home fallback is evaluated. - * Utility rewrites (download token hand-off, go-link confirm) are error / - * hand-off surfaces and opt out entirely, like the excluded templates. + * Utility rewrites (go-link confirm) are error / hand-off surfaces and + * opt out entirely, like the excluded templates. */ function aksrt_header_slide_current_page_type(): string { - if (class_exists('\AKSRT\Content\Download') && (string) get_query_var(\AKSRT\Content\Download::QUERY_VAR) !== '') { - return ''; - } if ((string) get_query_var('aksrt_go_redirect') !== '') { return ''; } diff --git a/src/Admin/Native/PayDownloadLogs.php b/src/Admin/Native/PayDownloadLogs.php index a79d7cb9..6330f365 100644 --- a/src/Admin/Native/PayDownloadLogs.php +++ b/src/Admin/Native/PayDownloadLogs.php @@ -76,7 +76,7 @@ class PayDownloadLogs { $total = $result['total']; $pages = max(1, (int) ceil($total / $per_page)); - echo '

' . esc_html__('基于下载令牌表(aksrt_dl_token)。已使用记录会保留一段时间后清理;未使用且过期的令牌会被定期删除。', 'aksrt') . '

'; + echo '

' . esc_html__('基于下载令牌表(aksrt_dl_token)。下载已改为直连模式,不再签发新令牌,以下为历史记录。', 'aksrt') . '

'; echo '
'; echo ''; diff --git a/src/Content/Download.php b/src/Content/Download.php deleted file mode 100644 index a7dea8b2..00000000 --- a/src/Content/Download.php +++ /dev/null @@ -1,538 +0,0 @@ -getTimestamp(); - wp_schedule_event($next, 'daily', 'aksrt_dl_token_cleanup'); - } - } - - public function cleanup_expired_tokens(): void { - global $wpdb; - if (!class_exists('\AKSRT\Core\DBMigrator')) return; - - $table = \AKSRT\Core\DBMigrator::table('dl_token'); - if (!$table) return; - - $now_utc = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'); - $audit_cutoff = (new \DateTimeImmutable('-7 days', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'); - - $wpdb->query($wpdb->prepare( - "DELETE FROM {$table} WHERE expires_at < %s", - $now_utc - )); - - $wpdb->query($wpdb->prepare( - "DELETE FROM {$table} WHERE used_count >= max_uses AND created_at < %s", - $audit_cutoff - )); - } - - /** - * Effective download settings for the fixed relay configuration. - */ - private function config(): array { - return [ - 'endpoint' => self::ENDPOINT, - 'max_uses' => self::MAX_USES, - 'ttl' => self::TTL_MINUTES * MINUTE_IN_SECONDS, - ]; - } - - public function register_rewrite(): void { - add_rewrite_rule( - '^' . preg_quote($this->config()['endpoint'], '/') . '/([A-Za-z0-9]{1,64})/?$', - 'index.php?' . self::QUERY_VAR . '=$matches[1]', - 'top' - ); - } - - /** - * Re-flush rewrite rules on theme version changes, using the same - * version-marker approach as Auth\Router. - */ - public function maybe_flush_rewrite(): void { - $version = AKSRT_VERSION . ':download-endpoint:' . self::ENDPOINT; - if ((string) get_option('aksrt_dl_rewrite_version') !== $version) { - flush_rewrite_rules(false); - update_option('aksrt_dl_rewrite_version', $version); - } - } - - public function add_query_var(array $vars): array { - $vars[] = self::QUERY_VAR; - return $vars; - } - - public function register_routes(): void { - register_rest_route('aksrt/v1', '/download/request', [ - 'methods' => 'POST', - 'callback' => [$this, 'rest_request'], - 'permission_callback' => [$this, 'check_rest_nonce'], - ]); - } - - public function check_rest_nonce(\WP_REST_Request $request) { - // The capability matrix can explicitly allow resource downloads for - // everyone. A WordPress REST nonce only exists for authenticated - // sessions, so requiring one from visitors would make the "all" - // column ineffective. Anonymous requests still pass the unified - // download gate (capability, payment and captcha) in rest_request(). - if (get_current_user_id() <= 0) { - return true; - } - - $nonce = $request->get_header('x-wp-nonce'); - if (!$nonce) { - return new \WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]); - } - if (!wp_verify_nonce($nonce, 'wp_rest')) { - return new \WP_Error('aksrt_rest_bad_nonce', __('安全令牌已过期,请刷新页面', 'aksrt'), ['status' => 403]); - } - return true; - } - - public function rest_request(\WP_REST_Request $request) { - $params = $request->get_json_params(); - if (!is_array($params)) $params = $request->get_params(); - - $post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0; - $source_index = isset($params['source_index']) ? (int) $params['source_index'] : 0; - - $post = get_post($post_id); - if (!$post || !function_exists('aksrt_is_downloadable_content') || !aksrt_is_downloadable_content($post)) { - return new \WP_Error('aksrt_invalid', __('内容不存在或未启用下载能力', 'aksrt'), ['status' => 404]); - } - - $meta = function_exists('aksrt_get_download_box_meta') ? aksrt_get_download_box_meta($post) : []; - if (empty($meta['downloads'][$source_index])) { - return new \WP_Error('aksrt_no_source', __('下载源不存在', 'aksrt'), ['status' => 404]); - } - $source = $meta['downloads'][$source_index]; - - $gate = aksrt_can_download($post); - if (is_wp_error($gate)) return $gate; - - $uid = get_current_user_id(); - $cost = aksrt_resource_download_cost($post); - - // Daily free-download quota (legacy). - if ($cost <= 0 && $uid > 0 && function_exists('aksrt_check_free_download_quota')) { - $quota = aksrt_check_free_download_quota($uid, $cost); - if (is_wp_error($quota)) { - return $quota; - } - } - - $cap_payload = isset($params['captcha']) && is_array($params['captcha']) ? $params['captcha'] : []; - $verify = aksrt_require_captcha('download', $cap_payload); - if ($verify !== true) { - $code = is_wp_error($verify) ? $verify->get_error_code() : 'aksrt_captcha_failed'; - $msg = is_wp_error($verify) ? $verify->get_error_message() : __('人机验证失败', 'aksrt'); - return new \WP_Error($code ?: 'aksrt_captcha_failed', $msg, ['status' => 400]); - } - - // Purchase is one-shot via cashier (resource order). Token issue does not re-deduct. - $config = $this->config(); - $token = $this->issue_token($uid, $post_id, $source_index, 0, $config); - if (is_wp_error($token)) { - return $token; - } - - // Record free download against daily quota after token issued. - if ($cost <= 0 && $uid > 0 && function_exists('aksrt_record_user_free_download')) { - aksrt_record_user_free_download($uid, $post_id); - } - - $dl_url = home_url('/' . $config['endpoint'] . '/' . $token); - if ($uid > 0 && function_exists('aksrt_add_user_notification')) { - aksrt_add_user_notification($uid, [ - 'type' => 'download_ready', - 'title' => __('资源下载已就绪', 'aksrt'), - 'desc' => sprintf(__('《%s》下载链接已生成,可立即开始下载。', 'aksrt'), get_the_title($post)), - 'url' => $dl_url, - ]); - } - - $extract_code = trim((string) ($source['extract_code'] ?? '')); - - return new \WP_REST_Response([ - 'success' => true, - 'token' => $token, - 'dl_url' => $dl_url, - 'expires_in' => $config['ttl'], - 'source_label' => $source['label'] ?? '', - 'source_type' => $source['type'] ?? 'direct', - 'has_extract' => $extract_code !== '', - 'extract_code' => $extract_code, - 'balance' => $uid > 0 ? \AKSRT\Ledger\LedgerService::get_instance()->balance($uid, \AKSRT\Ledger\AssetType::POINTS) : 0, - ], 200); - } - - public function maybe_serve_download(): void { - $token = get_query_var(self::QUERY_VAR); - if (!$token) return; - - $token = (string) $token; - if (strlen($token) > 64 || !preg_match('/^[A-Za-z0-9]+$/', $token)) { - $this->fail_410(); - } - - global $wpdb; - $table = \AKSRT\Core\DBMigrator::table('dl_token'); - if (!$table) $this->fail_410(); - - $row = $wpdb->get_row($wpdb->prepare( - "SELECT * FROM {$table} WHERE token = %s LIMIT 1", - $token - ), ARRAY_A); - if (!$row) $this->fail_410(); - - $expires_at = strtotime($row['expires_at'] . ' UTC'); - if ($expires_at !== false && time() > $expires_at) $this->fail_410(); - - if ((int) $row['used_count'] >= (int) $row['max_uses']) $this->fail_410(); - - $ip = function_exists('aksrt_get_client_ip') ? (string) aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''); - $ua_hash = sha1((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')); - if (!empty($row['ip_bind']) && $row['ip_bind'] !== $ip) $this->fail_410(); - if (!empty($row['ua_hash']) && $row['ua_hash'] !== $ua_hash) $this->fail_410(); - - $post = get_post((int) $row['post_id']); - if (!$post || !function_exists('aksrt_is_downloadable_content') || !aksrt_is_downloadable_content($post)) $this->fail_410(); - $meta = function_exists('aksrt_get_download_box_meta') ? aksrt_get_download_box_meta($post) : []; - $idx = (int) $row['source_index']; - if (empty($meta['downloads'][$idx]['url'])) $this->fail_410(); - - // Probes (HEAD requests and Speculative-Loading prefetches) validate - // availability but must never consume a use nor waste bandwidth. - $is_probe = $this->is_probe_request(); - - $target_url = esc_url_raw((string) $meta['downloads'][$idx]['url']); - - // Local attachment / uploads file: stream without artificial throttling. - $local_path = $this->resolve_local_download_path($target_url); - if ($local_path !== '' && is_readable($local_path)) { - $this->send_download_headers($local_path); - if (!$is_probe) { - // Count only fully delivered bodies; aborted transfers stay reusable. - if ($this->stream_body($local_path)) { - $this->consume_token($row, $token); - } - } - exit; - } - - nocache_headers(); - wp_redirect($target_url, 302); - // Remote hosts give no completion signal, so the redirect hand-off - // counts as delivery; probes still never consume. - if (!$is_probe) { - $this->consume_token($row, $token); - } - exit; - } - - /** - * Requests that inspect the link instead of taking the download: plain - * HEAD plus browser speculative prefetches (Sec-Purpose: prefetch). - */ - private function is_probe_request(): bool { - if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) === 'HEAD') { - return true; - } - $purpose = strtolower((string) ($_SERVER['HTTP_SEC_PURPOSE'] ?? ($_SERVER['HTTP_PURPOSE'] ?? ''))); - return $purpose !== '' && str_contains($purpose, 'prefetch'); - } - - /** - * Emit the attachment headers for a local download. - */ - private function send_download_headers(string $path): void { - $filename = basename($path); - $size = filesize($path); - if ($size === false) { - $this->fail_410(); - } - - nocache_headers(); - header('Content-Description: File Transfer'); - header('Content-Type: application/octet-stream'); - header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); - header('Content-Transfer-Encoding: binary'); - header('Content-Length: ' . (string) $size); - } - - /** - * Stream the local file body after its headers. Returns true only when - * every byte was flushed to the client. - */ - private function stream_body(string $path): bool { - $size = filesize($path); - if ($size === false) { - $this->fail_410(); - } - - $chunk = 1024 * 64; // 64KB - - $fh = fopen($path, 'rb'); - if (!$fh) { - $this->fail_410(); - } - // Disable output compression so Content-Length remains accurate. - if (function_exists('apache_setenv')) { - @apache_setenv('no-gzip', '1'); - } - @ini_set('zlib.output_compression', 'Off'); - while (!feof($fh)) { - if (connection_aborted()) { - fclose($fh); - return false; - } - $buf = fread($fh, $chunk); - if ($buf === false) { - fclose($fh); - return false; - } - echo $buf; - if (function_exists('flush')) { - flush(); - } - } - fclose($fh); - - return !connection_aborted(); - } - - /** - * Map a site-local URL to a filesystem path under uploads (or ABSPATH). - */ - private function resolve_local_download_path(string $url): string { - if ($url === '') { - return ''; - } - $uploads = wp_upload_dir(); - $baseurl = trailingslashit((string) ($uploads['baseurl'] ?? '')); - $basedir = trailingslashit((string) ($uploads['basedir'] ?? '')); - if ($baseurl !== '' && str_starts_with($url, $baseurl)) { - $rel = substr($url, strlen($baseurl)); - $path = $basedir . ltrim(rawurldecode($rel), '/\\'); - $real = realpath($path); - $root = realpath($basedir); - if ($real && $root && str_starts_with($real, $root) && is_file($real)) { - return $real; - } - } - // Same-host path under ABSPATH. - $home = trailingslashit(home_url('/')); - if (str_starts_with($url, $home)) { - $rel = substr($url, strlen($home)); - $path = ABSPATH . ltrim(rawurldecode($rel), '/\\'); - $real = realpath($path); - $root = realpath(ABSPATH); - if ($real && $root && str_starts_with($real, $root) && is_file($real)) { - return $real; - } - } - return ''; - } - - /** - * Count a delivered download. The guard caps concurrent finishers at - * max_uses instead of overshooting the counter. - */ - private function consume_token(array $row, string $token): void { - global $wpdb; - $table = \AKSRT\Core\DBMigrator::table('dl_token'); - if (!$table) return; - - $updated = $wpdb->query($wpdb->prepare( - "UPDATE {$table} SET used_count = used_count + 1 WHERE token = %s AND used_count < max_uses", - $token - )); - - if (!$updated) { - return; - } - - if (function_exists('aksrt_update_post_counter')) { - aksrt_update_post_counter((int) $row['post_id'], '_aksrt_res_download_count', 1); - } else { - $count = (int) get_post_meta((int) $row['post_id'], '_aksrt_res_download_count', true); - update_post_meta((int) $row['post_id'], '_aksrt_res_download_count', $count + 1); - } - } - - private function fail_410(): void { - status_header(410); - nocache_headers(); - - // Themed expiry page; wp_die stays only as the safety net for the - // paths that already streamed headers/body (send_download_headers, - // stream_body) where the theme chrome can no longer be emitted. - if (function_exists('get_header') && !headers_sent()) { - $this->render_expired_page(); - exit; - } - - wp_die( - esc_html__('下载链接已过期或无效,请回到资源页重新获取。', 'aksrt'), - esc_html__('下载链接已失效', 'aksrt'), - ['response' => 410] - ); - } - - /** - * Flat, theme-native 410 page. All colors resolve from the runtime CSS - * variables (--color-primary plus its html.dark override, legacy ink - * vars flipped by body.dark-theme), so the admin-configured primary - * color and the light/dark mode apply with no extra scripts. - */ - private function render_expired_page(): void { - add_action('wp_head', static function (): void { - $css = <<<'CSS' -.aksrt-410{display:flex;align-items:center;justify-content:center;min-height:72vh;padding:48px 20px;text-align:center} -.aksrt-410__card{max-width:460px} -.aksrt-410__icon{width:92px;height:92px;margin:0 auto 28px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:2.25rem;background:var(--color-primary-soft,rgba(0,0,0,.06));color:var(--color-primary,#f04494)} -.aksrt-410__code{font-family:var(--font-display,var(--font-body,inherit));font-size:clamp(4rem,10vw,5.5rem);font-weight:800;line-height:1;letter-spacing:.02em;color:var(--muted-4-color,#d2d2d2);margin:0 0 10px} -.aksrt-410__title{font-family:var(--font-display,inherit);font-size:clamp(20px,3vw,26px);font-weight:700;line-height:1.3;color:var(--key-color,#333);margin:0 0 12px} -.aksrt-410__desc{font-size:15px;line-height:1.7;color:var(--muted-color,#777);margin:0 0 32px} -.aksrt-410__actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap} -.aksrt-410__actions .btn-primary,.aksrt-410__actions .btn-outline{--btn-h:42px;--btn-px:22px} -CSS; - echo '' . "\n"; - }, 99); - - $title_filter = static function (array $parts): array { - $parts['title'] = __('下载链接已失效', 'aksrt'); - return $parts; - }; - add_filter('document_title_parts', $title_filter); - - get_header(); - remove_filter('document_title_parts', $title_filter); - ?> -
-
- -

410

-

-

- -
-
- 500]); - } - - if (empty($config)) { - $config = $this->config(); - } - - $ip = function_exists('aksrt_get_client_ip') ? (string) aksrt_get_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''); - $ua_hash = sha1((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')); - - $now_utc = (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s'); - $expires_utc = (new \DateTimeImmutable('now', new \DateTimeZone('UTC'))) - ->modify('+' . (int) $config['ttl'] . ' seconds') - ->format('Y-m-d H:i:s'); - - // PRIMARY KEY(token) already guarantees uniqueness; retry on the - // astronomically unlikely collision instead of failing the request. - for ($attempt = 0; $attempt < 3; $attempt++) { - $token = $this->random_token(self::TOKEN_LEN); - - $ok = $wpdb->insert( - $table, - [ - 'token' => $token, - 'user_id' => $user_id, - 'post_id' => $post_id, - 'source_index' => $source_index, - 'cost' => $cost, - 'ip_bind' => $ip, - 'ua_hash' => $ua_hash, - 'used_count' => 0, - 'max_uses' => (int) $config['max_uses'], - 'expires_at' => $expires_utc, - 'created_at' => $now_utc, - ], - ['%s', '%d', '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s', '%s'] - ); - - if ($ok !== false) { - return $token; - } - } - - return new \WP_Error('aksrt_token_insert', __('生成下载令牌失败', 'aksrt'), ['status' => 500]); - } - - private function random_token(int $len): string { - $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $max = strlen($alphabet) - 1; - $out = ''; - for ($i = 0; $i < $len; $i++) { - $out .= $alphabet[random_int(0, $max)]; - } - return $out; - } -} diff --git a/src/Core/SEOHead.php b/src/Core/SEOHead.php index 06d607d7..575260b6 100644 --- a/src/Core/SEOHead.php +++ b/src/Core/SEOHead.php @@ -26,8 +26,6 @@ class SEOHead { } // 单一出口:所有 SEO/OG/JSON-LD 输出集中在 priority 2。 add_action('wp_head', [$this, 'render'], 2); - // robots:dl token 页面 noindex(独立、最早输出)。 - add_action('wp_head', [$this, 'render_robots'], 1); } /** @@ -229,15 +227,6 @@ class SEOHead { return ''; } - /** - * dl token 页面 noindex。 - */ - public function render_robots(): void { - if (function_exists('get_query_var') && get_query_var('aksrt_dl_token')) { - echo '' . "\n"; - } - } - /** * 输出 head(favicon + canonical + description/keywords + OG/Twitter + JSON-LD)。 */ diff --git a/templates/parts/resource-download-box.php b/templates/parts/resource-download-box.php index 8bbcf93e..49aac7f7 100644 --- a/templates/parts/resource-download-box.php +++ b/templates/parts/resource-download-box.php @@ -55,11 +55,6 @@ $pay_modo = is_array($priced) ? ((($priced['modo'] ?? '0') === 'points') ? 'points' : '0') : ((($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'); $user_amount = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0); -$cost = function_exists('aksrt_resource_download_cost') ? (int) aksrt_resource_download_cost($post_id) : 0; - -$captcha_active = function_exists('aksrt_is_captcha_enabled_for') - && aksrt_is_captcha_enabled_for('download'); -$balance = (int) ($gate_data['balance'] ?? (is_user_logged_in() ? \AKSRT\Ledger\LedgerService::get_instance()->balance(get_current_user_id(), \AKSRT\Ledger\AssetType::POINTS) : 0)); $login_url = (string) ($gate_data['login_url'] ?? wp_login_url(get_permalink($post_id))); $vip_url = function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('credits') @@ -160,7 +155,6 @@ if ($is_need_purchase && $user_amount > 0) { data-pay-limit="" data-has-paid="" data-credit="0" - data-captcha="" aria-label=""> 0) : ?> @@ -345,6 +339,10 @@ if ($is_need_purchase && $user_amount > 0) { } $type = isset($row['type']) ? (string) $row['type'] : 'direct'; $url = isset($row['url']) ? (string) $row['url'] : ''; + if ($url === '') { + // 直连模式没有可用的原始地址,空源不渲染。 + continue; + } $extract_code = isset($row['extract_code']) ? trim((string) $row['extract_code']) : ''; $has_extract = $extract_code !== ''; @@ -354,27 +352,22 @@ if ($is_need_purchase && $user_amount > 0) { } if ($is_need_login) { - $btn_tag = 'a'; $btn_href = $login_url; $btn_text = __('登录后下载', 'aksrt'); $btn_icon = 'fa-sign-in-alt'; $btn_modifier = ' resource-download-box__btn--login'; - $btn_action = ''; $btn_target = ''; $btn_rel = ''; } else { - $btn_tag = 'button'; - $btn_href = ''; + $btn_href = $url; $btn_text = __('立即下载', 'aksrt'); $btn_icon = 'fa-download'; $btn_modifier = $has_paid_access ? ' resource-download-box__btn--owned' : ''; - $btn_action = 'aksrt-download'; - $btn_target = ''; - $btn_rel = ''; + $btn_target = ' target="_blank"'; + $btn_rel = ' rel="nofollow noopener"'; } ?>
  • - @@ -388,25 +381,12 @@ if ($is_need_purchase && $user_amount > 0) { - - - + + +
  • @@ -471,12 +451,6 @@ if ($is_need_purchase && $user_amount > 0) {

    - -
    - -
    - -