feat(creator-studio): 前台 Gutenberg 创作工作台与受保护付费附件
前台投稿页改为沉浸式创作工作台:Gutenberg 负责区块解析与序列化, React + TypeScript 负责工作台外壳与发布抽屉,Jio 服务端继续作为权限、 审核与付费的唯一权威。 - 新增 assets/creator-studio 独立构建(wp-scripts,锁定 WP 6.4 时代包), 仅在投稿模板加载,构建缺失或游客访问时回退原有 TinyMCE 表单 - 自动保存(4s 防抖 + rev 冲突 409)、本地快照恢复、发布配置抽屉、 移动端全屏单列与安全区适配 - 图片插入走现有 front-submit 上传管线,保留能力校验与待认领生命周期 - 已发布文章的改稿写入私有版本,审核通过前线上内容/链接/付费条款不变; 原生后台「改稿审核」一次性应用或退回,并校验源文章并发指纹 - 私有附件新表 jio_private_asset + jio_dl_token.asset_id,schema 升到 3.4.0; 文件存放 ABSPATH 之外,未配置私有存储时 fail closed - 下载令牌绑定文章/附件/用户/IP/UA/次数,每次交付复核 Visibility 的 订单、VIP 与配额判定;中断传输归还次数 - 付费字段改为写入前校验,非法价格不再留下半保存文章 - 新增 creator-studio 契约测试;更新受影响的 schema/下载/投稿契约 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7a8279f8e7
commit
fac4a714e0
45 files changed
+28495
-56
No files matched your search
+39
-8
@@ -335,6 +335,19 @@ if (!function_exists('jio_get_user_content_paid_order')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('jio_bump_content_download_count')) {
|
||||
/** 资源下载计数 +1(受控下载成功交付后调用)。 */
|
||||
function jio_bump_content_download_count(int $post_id, int $delta = 1): int {
|
||||
if ($post_id <= 0) {
|
||||
return 0;
|
||||
}
|
||||
$key = \Jio\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY;
|
||||
$next = max(0, (int) get_post_meta($post_id, $key, true) + $delta);
|
||||
update_post_meta($post_id, $key, $next);
|
||||
return $next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment real sales volume for a paid content post (idempotent per order via order meta note flag optional).
|
||||
*/
|
||||
@@ -1484,19 +1497,32 @@ if (!function_exists('jio_get_paid_download_actions_html')) {
|
||||
}
|
||||
|
||||
$buttons = '';
|
||||
$protected_post_id = (int) ($meta['post_id'] ?? $meta['ID'] ?? get_the_ID() ?? 0);
|
||||
if ($protected_post_id <= 0 && function_exists('get_the_ID')) {
|
||||
$protected_post_id = (int) get_the_ID();
|
||||
}
|
||||
foreach ($downloads as $index => $download) {
|
||||
if (!is_array($download)) {
|
||||
continue;
|
||||
}
|
||||
$url = (string) ($download['url'] ?? '');
|
||||
if ($url === '') {
|
||||
$asset_key = class_exists('\Jio\Content\PrivateAssets')
|
||||
? \Jio\Content\PrivateAssets::sanitize_asset_key((string) ($download['asset_key'] ?? ''))
|
||||
: '';
|
||||
$is_protected = $asset_key !== '';
|
||||
$url = $is_protected ? '' : (string) ($download['url'] ?? '');
|
||||
if (!$is_protected && $url === '') {
|
||||
// 直连模式没有可用的原始地址,空源不渲染。
|
||||
continue;
|
||||
}
|
||||
$label = trim((string) ($download['label'] ?? ''));
|
||||
$service = function_exists('jio_detect_cloud_service')
|
||||
? jio_detect_cloud_service($url)
|
||||
: ['key' => 'other', 'name' => __('本地下载', 'jio')];
|
||||
if ($label === '' && $is_protected) {
|
||||
$label = trim((string) ($download['file_name'] ?? ''));
|
||||
}
|
||||
$service = $is_protected
|
||||
? ['key' => 'protected', 'name' => __('受保护下载', 'jio')]
|
||||
: (function_exists('jio_detect_cloud_service')
|
||||
? jio_detect_cloud_service($url)
|
||||
: ['key' => 'other', 'name' => __('本地下载', 'jio')]);
|
||||
if ($label === '') {
|
||||
$label = (string) ($service['name'] ?? __('本地下载', 'jio'));
|
||||
}
|
||||
@@ -1514,8 +1540,13 @@ if (!function_exists('jio_get_paid_download_actions_html')) {
|
||||
$copy_label = $extract_code !== '' ? $extract_code : ($copy_key !== '' ? $copy_key : __('更多内容', 'jio'));
|
||||
|
||||
$buttons .= '<div class="but-download flex ac">';
|
||||
$buttons .= '<a class="mr10 but ' . esc_attr($button_class) . '" href="' . esc_url($url) . '" target="_blank" rel="nofollow noopener">';
|
||||
$buttons .= $icon_html . esc_html($label) . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($is_protected) {
|
||||
$buttons .= '<button type="button" class="mr10 but ' . esc_attr($button_class) . '" data-jio-protected-asset="' . esc_attr($asset_key) . '" data-jio-protected-post="' . esc_attr((string) $protected_post_id) . '">';
|
||||
$buttons .= $icon_html . esc_html($label) . '</button>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
$buttons .= '<a class="mr10 but ' . esc_attr($button_class) . '" href="' . esc_url($url) . '" target="_blank" rel="nofollow noopener">';
|
||||
$buttons .= $icon_html . esc_html($label) . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($copy_value !== '') {
|
||||
$tooltip = $copy_key !== '' ? ' data-toggle="tooltip" title="' . esc_attr(sprintf(__('点击复制%s', 'jio'), $copy_key)) . '"' : '';
|
||||
$buttons .= '<button type="button" class="but"' . $tooltip . ' data-clipboard-tag="' . esc_attr($copy_key !== '' ? $copy_key : __('内容', 'jio')) . '" data-clipboard-text="' . esc_attr($copy_value) . '">' . esc_html($copy_label) . '</button>';
|
||||
@@ -1613,7 +1644,7 @@ if (!function_exists('jio_get_paid_box_html')) {
|
||||
$root_data = '';
|
||||
if ($pay_type === JIO_PAY_TYPE_DOWNLOAD) {
|
||||
$root_data = ' data-jio-download-box data-post-id="' . esc_attr((string) $post->ID) . '"';
|
||||
$download_box = '<div class="hidden-box show"><div class="hidden-text"><i class="fa fas fa-download mr6" aria-hidden="true"></i>' . esc_html__('资源下载', 'jio') . '</div>' . jio_get_paid_download_actions_html($meta) . '</div>';
|
||||
$download_box = '<div class="hidden-box show"><div class="hidden-text"><i class="fa fas fa-download mr6" aria-hidden="true"></i>' . esc_html__('资源下载', 'jio') . '</div>' . jio_get_paid_download_actions_html($meta + ['post_id' => (int) $post->ID]) . '</div>';
|
||||
$demo = jio_get_demo_link_html($post, 'but jb-yellow padding-lg');
|
||||
if ($demo !== '') {
|
||||
$download_box .= '<div class="mt10">' . $demo . '</div>';
|
||||
|
||||
Reference in new issue
Block a user