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
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* 原生后台:已发布文章的改稿审核。
|
||||
*
|
||||
* 运营在此查看待审改稿、对比原文章,并通过 admin-post 动作一次性应用或退回。
|
||||
* 不提供直接编辑改稿内容的入口,避免绕过作者侧校验与版本指纹。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace Jio\Admin\Native;
|
||||
|
||||
use Jio\Core\SubmissionVersion;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class SubmissionReview {
|
||||
|
||||
public const SLUG = 'jio_submission_reviews';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('admin_menu', [$this, 'register_menu']);
|
||||
add_action('admin_notices', [$this, 'render_notice']);
|
||||
}
|
||||
|
||||
public function register_menu(): void {
|
||||
add_submenu_page(
|
||||
'edit.php',
|
||||
__('改稿审核', 'jio'),
|
||||
__('改稿审核', 'jio'),
|
||||
'edit_others_posts',
|
||||
self::SLUG,
|
||||
[$this, 'render_page']
|
||||
);
|
||||
}
|
||||
|
||||
public function render_notice(): void {
|
||||
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
||||
if (!$screen || strpos((string) $screen->id, self::SLUG) === false) {
|
||||
return;
|
||||
}
|
||||
$notice = isset($_GET['jio_review_notice']) ? sanitize_key((string) wp_unslash($_GET['jio_review_notice'])) : '';
|
||||
if ($notice === '') {
|
||||
return;
|
||||
}
|
||||
$message = isset($_GET['jio_review_message']) ? sanitize_text_field(rawurldecode((string) wp_unslash($_GET['jio_review_message']))) : '';
|
||||
$class = $notice === 'ok' ? 'notice-success' : 'notice-error';
|
||||
echo '<div class="notice ' . esc_attr($class) . ' is-dismissible"><p>' . esc_html($message) . '</p></div>';
|
||||
}
|
||||
|
||||
public function render_page(): void {
|
||||
if (!SubmissionVersion::can_review(get_current_user_id())) {
|
||||
wp_die(esc_html__('没有审核权限。', 'jio'));
|
||||
}
|
||||
|
||||
$status = isset($_GET['review_status']) ? sanitize_key((string) wp_unslash($_GET['review_status'])) : SubmissionVersion::STATUS_PENDING;
|
||||
$status = in_array($status, [SubmissionVersion::STATUS_PENDING, SubmissionVersion::STATUS_REJECTED, SubmissionVersion::STATUS_APPROVED], true)
|
||||
? $status
|
||||
: SubmissionVersion::STATUS_PENDING;
|
||||
|
||||
$versions = get_posts([
|
||||
'post_type' => SubmissionVersion::POST_TYPE,
|
||||
'post_status' => ['pending', 'draft', 'private'],
|
||||
'posts_per_page' => 40,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'meta_key' => SubmissionVersion::META_STATUS,
|
||||
'meta_value' => $status,
|
||||
]);
|
||||
|
||||
$tabs = [
|
||||
SubmissionVersion::STATUS_PENDING => __('待审核', 'jio'),
|
||||
SubmissionVersion::STATUS_REJECTED => __('已退回', 'jio'),
|
||||
SubmissionVersion::STATUS_APPROVED => __('已应用', 'jio'),
|
||||
];
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e('改稿审核', 'jio'); ?></h1>
|
||||
<p class="description">
|
||||
<?php esc_html_e('作者对已发布文章的修改以独立版本保存;通过后才会替换线上文章。', 'jio'); ?>
|
||||
</p>
|
||||
|
||||
<ul class="subsubsub">
|
||||
<?php foreach ($tabs as $key => $label) : ?>
|
||||
<li>
|
||||
<a href="<?php echo esc_url(add_query_arg(['page' => self::SLUG, 'review_status' => $key], admin_url('edit.php'))); ?>"
|
||||
class="<?php echo $key === $status ? 'current' : ''; ?>">
|
||||
<?php echo esc_html($label); ?>
|
||||
</a>
|
||||
<?php echo $key !== array_key_last($tabs) ? ' |' : ''; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<table class="widefat striped" style="margin-top:16px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php esc_html_e('文章', 'jio'); ?></th>
|
||||
<th><?php esc_html_e('作者', 'jio'); ?></th>
|
||||
<th><?php esc_html_e('提交时间', 'jio'); ?></th>
|
||||
<th><?php esc_html_e('来源', 'jio'); ?></th>
|
||||
<th><?php esc_html_e('操作', 'jio'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (!$versions) : ?>
|
||||
<tr><td colspan="5"><?php esc_html_e('暂无记录。', 'jio'); ?></td></tr>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($versions as $version) :
|
||||
$version_id = (int) $version->ID;
|
||||
$source_id = SubmissionVersion::source_id($version_id);
|
||||
$author = get_userdata((int) $version->post_author);
|
||||
$note = (string) get_post_meta($version_id, SubmissionVersion::META_NOTE, true);
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?php echo esc_html($version->post_title !== '' ? $version->post_title : __('(无标题)', 'jio')); ?></strong>
|
||||
<?php if ($note !== '') : ?>
|
||||
<p class="description"><?php echo esc_html(sprintf(__('退回原因:%s', 'jio'), $note)); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if ($source_id > 0) : ?>
|
||||
<p class="description">
|
||||
<a href="<?php echo esc_url(get_edit_post_link($source_id)); ?>">
|
||||
<?php esc_html_e('查看线上文章', 'jio'); ?>
|
||||
</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $author ? esc_html($author->display_name) : '—'; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$submitted = (string) get_post_meta($version_id, SubmissionVersion::META_SUBMITTED_AT, true);
|
||||
echo esc_html($submitted !== '' ? $submitted : $version->post_modified);
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<?php esc_html_e('作者改稿', 'jio'); ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($status === SubmissionVersion::STATUS_PENDING) : ?>
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;">
|
||||
<input type="hidden" name="action" value="jio_submission_approve">
|
||||
<input type="hidden" name="version_id" value="<?php echo esc_attr((string) $version_id); ?>">
|
||||
<?php wp_nonce_field('jio_review_approve_' . $version_id); ?>
|
||||
<button type="submit" class="button button-primary"><?php esc_html_e('应用改稿', 'jio'); ?></button>
|
||||
</form>
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" style="display:inline;margin-left:8px;">
|
||||
<input type="hidden" name="action" value="jio_submission_reject">
|
||||
<input type="hidden" name="version_id" value="<?php echo esc_attr((string) $version_id); ?>">
|
||||
<?php wp_nonce_field('jio_review_reject_' . $version_id); ?>
|
||||
<input type="text" name="note" placeholder="<?php esc_attr_e('退回原因(可选)', 'jio'); ?>" class="regular-text" style="width:180px;">
|
||||
<button type="submit" class="button"><?php esc_html_e('退回', 'jio'); ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user