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,547 @@
|
||||
<?php
|
||||
/**
|
||||
* 已发布文章的改稿版本(Jio Creator Studio)。
|
||||
*
|
||||
* 作者修改一篇已发布文章时,改动不会直接写入线上文章,而是写入一个私有的
|
||||
* 投稿版本(私有 post type)。线上文章的内容、永久链接与付费条款在审核通过
|
||||
* 前保持不变;审核通过后由本类一次性把版本内容应用到源文章。
|
||||
*
|
||||
* 新文章仍走既有 FrontSubmit::persist() 的 draft → pending/publish 流程;只有
|
||||
* 源文章已发布时才进入版本流程,避免线上内容被静默改写。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace Jio\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class SubmissionVersion {
|
||||
|
||||
public const POST_TYPE = 'jio_submission';
|
||||
|
||||
/** 版本记录上的 meta。 */
|
||||
public const META_SOURCE = '_jio_source_post';
|
||||
public const META_SOURCE_REV = '_jio_source_rev';
|
||||
public const META_REV = '_jio_version_rev';
|
||||
public const META_STATUS = '_jio_review_status';
|
||||
public const META_NOTE = '_jio_review_note';
|
||||
public const META_SNAPSHOT = '_jio_snapshot';
|
||||
public const META_SUBMITTED_AT = '_jio_submitted_at';
|
||||
|
||||
public const STATUS_WORKING = 'working';
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_APPROVED = 'approved';
|
||||
public const STATUS_REJECTED = 'rejected';
|
||||
|
||||
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('init', [$this, 'register_post_type'], 2);
|
||||
add_action('admin_post_jio_submission_approve', [$this, 'admin_post_approve']);
|
||||
add_action('admin_post_jio_submission_reject', [$this, 'admin_post_reject']);
|
||||
add_filter('wp_robots', [$this, 'robots_noindex']);
|
||||
}
|
||||
|
||||
public function admin_post_approve(): void {
|
||||
$this->handle_review_action('approve');
|
||||
}
|
||||
|
||||
public function admin_post_reject(): void {
|
||||
$this->handle_review_action('reject');
|
||||
}
|
||||
|
||||
private function handle_review_action(string $action): void {
|
||||
if (!SubmissionVersion::can_review(get_current_user_id())) {
|
||||
wp_die(esc_html__('没有审核权限。', 'jio'), '', ['response' => 403]);
|
||||
}
|
||||
$version_id = absint($_POST['version_id'] ?? 0);
|
||||
check_admin_referer('jio_review_' . $action . '_' . $version_id);
|
||||
|
||||
$result = $action === 'approve'
|
||||
? $this->approve($version_id, get_current_user_id(), !empty($_POST['force']))
|
||||
: $this->reject($version_id, get_current_user_id(), (string) ($_POST['note'] ?? ''));
|
||||
|
||||
$ok = !is_wp_error($result) && !empty($result['success']);
|
||||
$msg = is_wp_error($result) ? $result->get_error_message() : (string) ($result['message'] ?? '');
|
||||
|
||||
$redirect = add_query_arg(
|
||||
['jio_review_notice' => $ok ? 'ok' : 'error', 'jio_review_message' => rawurlencode($msg)],
|
||||
wp_get_referer() ?: admin_url('edit.php?post_type=' . self::POST_TYPE)
|
||||
);
|
||||
wp_safe_redirect($redirect);
|
||||
exit;
|
||||
}
|
||||
|
||||
public function register_post_type(): void {
|
||||
register_post_type(self::POST_TYPE, [
|
||||
'labels' => [
|
||||
'name' => __('投稿版本', 'jio'),
|
||||
'singular_name' => __('投稿版本', 'jio'),
|
||||
],
|
||||
'public' => false,
|
||||
'publicly_queryable' => false,
|
||||
'exclude_from_search' => true,
|
||||
'show_ui' => false,
|
||||
'show_in_rest' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'capability_type' => 'post',
|
||||
'map_meta_cap' => true,
|
||||
'supports' => ['title', 'editor', 'revisions', 'author'],
|
||||
'can_export' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, bool> $robots
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function robots_noindex(array $robots): array {
|
||||
if (is_singular(self::POST_TYPE)) {
|
||||
$robots['noindex'] = true;
|
||||
$robots['nofollow'] = true;
|
||||
}
|
||||
return $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 该源文章是否需要走版本流程(仅已发布文章)。
|
||||
*/
|
||||
public static function requires_version(int $source_id): bool {
|
||||
$post = get_post($source_id);
|
||||
return $post instanceof \WP_Post
|
||||
&& $post->post_type === 'post'
|
||||
&& $post->post_status === 'publish';
|
||||
}
|
||||
|
||||
/**
|
||||
* 作者针对某篇源文章当前可见的工作版本(工作 / 待审 / 被退回)。
|
||||
*/
|
||||
public static function get_working(int $source_id, int $user_id): ?\WP_Post {
|
||||
if ($source_id <= 0 || $user_id <= 0) {
|
||||
return null;
|
||||
}
|
||||
$posts = get_posts([
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => ['draft', 'pending', 'publish', 'private'],
|
||||
'author' => $user_id,
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'no_found_rows' => true,
|
||||
'meta_key' => self::META_SOURCE,
|
||||
'meta_value' => (string) $source_id,
|
||||
]);
|
||||
foreach ($posts as $post) {
|
||||
if (!in_array(self::status((int) $post->ID), [self::STATUS_APPROVED], true)) {
|
||||
return $post;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function status(int $version_id): string {
|
||||
$status = (string) get_post_meta($version_id, self::META_STATUS, true);
|
||||
return in_array($status, [self::STATUS_WORKING, self::STATUS_PENDING, self::STATUS_APPROVED, self::STATUS_REJECTED], true)
|
||||
? $status
|
||||
: self::STATUS_WORKING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function snapshot(int $version_id): array {
|
||||
$raw = get_post_meta($version_id, self::META_SNAPSHOT, true);
|
||||
return is_array($raw) ? $raw : [];
|
||||
}
|
||||
|
||||
public static function revision(int $version_id): int {
|
||||
return max(0, (int) get_post_meta($version_id, self::META_REV, true));
|
||||
}
|
||||
|
||||
public static function source_id(int $version_id): int {
|
||||
return max(0, (int) get_post_meta($version_id, self::META_SOURCE, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 源文章的版本指纹:改动后用于检测审核期间的并发修改。
|
||||
*/
|
||||
public static function source_revision(int $source_id): string {
|
||||
$post = get_post($source_id);
|
||||
if (!$post instanceof \WP_Post) {
|
||||
return '';
|
||||
}
|
||||
return sha1(implode('|', [
|
||||
$post->post_modified_gmt,
|
||||
(string) strlen((string) $post->post_content),
|
||||
md5((string) $post->post_content),
|
||||
md5((string) $post->post_title),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新作者的工作版本。
|
||||
*
|
||||
* @param array{title?:string,content?:string,excerpt?:string,snapshot?:array<string,mixed>,base_rev?:int} $data
|
||||
* @return array{success:bool,message?:string,version_id?:int,rev?:int,conflict?:bool}|\WP_Error
|
||||
*/
|
||||
public function save_working(int $source_id, int $user_id, array $data, bool $submit = false) {
|
||||
if (!self::requires_version($source_id)) {
|
||||
return new \WP_Error('jio_version_not_required', __('该文章不需要版本流程。', 'jio'), ['status' => 400]);
|
||||
}
|
||||
$source = get_post($source_id);
|
||||
if (!$source instanceof \WP_Post || (int) $source->post_author !== $user_id) {
|
||||
return new \WP_Error('jio_version_forbidden', __('没有编辑此文章的权限。', 'jio'), ['status' => 403]);
|
||||
}
|
||||
|
||||
$version = self::get_working($source_id, $user_id);
|
||||
$base_rev = max(0, (int) ($data['base_rev'] ?? 0));
|
||||
|
||||
if ($version instanceof \WP_Post) {
|
||||
$current_rev = self::revision((int) $version->ID);
|
||||
if ($base_rev !== 0 && $base_rev !== $current_rev) {
|
||||
return new \WP_Error(
|
||||
'jio_version_conflict',
|
||||
__('该文章已在其他页面更新,请刷新后再保存。', 'jio'),
|
||||
['status' => 409, 'current_rev' => $current_rev, 'version_id' => (int) $version->ID]
|
||||
);
|
||||
}
|
||||
$version_id = (int) $version->ID;
|
||||
} else {
|
||||
$version_id = wp_insert_post([
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => 'draft',
|
||||
'post_author' => $user_id,
|
||||
'post_title' => (string) $source->post_title,
|
||||
'post_content' => (string) $source->post_content,
|
||||
'post_excerpt' => (string) $source->post_excerpt,
|
||||
'meta_input' => [
|
||||
self::META_SOURCE => $source_id,
|
||||
self::META_SOURCE_REV => self::source_revision($source_id),
|
||||
self::META_REV => 0,
|
||||
self::META_STATUS => self::STATUS_WORKING,
|
||||
],
|
||||
], true);
|
||||
if (is_wp_error($version_id) || !$version_id) {
|
||||
return new \WP_Error('jio_version_failed', __('无法创建改稿版本,请稍后再试。', 'jio'), ['status' => 500]);
|
||||
}
|
||||
}
|
||||
|
||||
$title = isset($data['title']) ? sanitize_text_field((string) $data['title']) : (string) $source->post_title;
|
||||
$excerpt = isset($data['excerpt']) ? sanitize_textarea_field((string) $data['excerpt']) : (string) $source->post_excerpt;
|
||||
$content = isset($data['content']) ? (string) $data['content'] : (string) $source->post_content;
|
||||
|
||||
$can_iframe = $this->user_can_cap('new_post_iframe_video', $user_id);
|
||||
if ($can_iframe) {
|
||||
add_filter('wp_kses_allowed_html', [FrontSubmit::get_instance(), 'allow_iframe_kses'], 99, 2);
|
||||
}
|
||||
$content = wp_kses_post($content);
|
||||
if ($can_iframe) {
|
||||
remove_filter('wp_kses_allowed_html', [FrontSubmit::get_instance(), 'allow_iframe_kses'], 99);
|
||||
}
|
||||
|
||||
$next_rev = $base_rev > 0 ? $base_rev + 1 : ($version instanceof \WP_Post ? self::revision($version_id) + 1 : 1);
|
||||
|
||||
// 已经在审核队列中的改稿:作者继续编辑(含自动保存)时保持 pending,
|
||||
// 否则一次自动保存会把待审内容悄悄移出审核队列。
|
||||
$current_status = $version instanceof \WP_Post ? self::status($version_id) : self::STATUS_WORKING;
|
||||
$stays_pending = !$submit && $current_status === self::STATUS_PENDING;
|
||||
$next_status = $submit || $stays_pending ? self::STATUS_PENDING : self::STATUS_WORKING;
|
||||
|
||||
$update = [
|
||||
'ID' => $version_id,
|
||||
'post_title' => $title,
|
||||
'post_content' => $content,
|
||||
'post_excerpt' => $excerpt,
|
||||
'post_status' => $next_status === self::STATUS_PENDING ? 'pending' : 'draft',
|
||||
];
|
||||
$updated = wp_update_post($update, true);
|
||||
if (is_wp_error($updated)) {
|
||||
return new \WP_Error('jio_version_failed', $updated->get_error_message(), ['status' => 500]);
|
||||
}
|
||||
|
||||
if (isset($data['snapshot']) && is_array($data['snapshot'])) {
|
||||
$snapshot = $this->sanitize_snapshot($data['snapshot']);
|
||||
update_post_meta($version_id, self::META_SNAPSHOT, $snapshot);
|
||||
// 私有资料在上传后即绑定到源文章:审核通过后可直接交付,也不会被
|
||||
// 未关联附件的定时清理误删。
|
||||
$this->claim_snapshot_assets($snapshot, $source_id, $user_id);
|
||||
}
|
||||
update_post_meta($version_id, self::META_REV, $next_rev);
|
||||
update_post_meta($version_id, self::META_STATUS, $next_status);
|
||||
if ($submit) {
|
||||
update_post_meta($version_id, self::META_SUBMITTED_AT, current_time('mysql'));
|
||||
update_post_meta($version_id, self::META_SOURCE_REV, self::source_revision($source_id));
|
||||
delete_post_meta($version_id, self::META_NOTE);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'version_id' => $version_id,
|
||||
'rev' => $next_rev,
|
||||
'status' => $next_status,
|
||||
'message' => $submit
|
||||
? __('改稿已提交审核,线上文章保持不变。', 'jio')
|
||||
: ($stays_pending ? __('待审改稿已更新', 'jio') : __('改稿已保存', 'jio')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核通过:把版本快照一次性应用到源文章。
|
||||
*
|
||||
* @return array{success:bool,message?:string,post_id?:int}|\WP_Error
|
||||
*/
|
||||
public function approve(int $version_id, int $reviewer_id, bool $force = false) {
|
||||
$version = get_post($version_id);
|
||||
if (!$version instanceof \WP_Post || $version->post_type !== self::POST_TYPE) {
|
||||
return new \WP_Error('jio_version_missing', __('改稿版本不存在。', 'jio'), ['status' => 404]);
|
||||
}
|
||||
if (!self::can_review($reviewer_id)) {
|
||||
return new \WP_Error('jio_review_forbidden', __('没有审核权限。', 'jio'), ['status' => 403]);
|
||||
}
|
||||
|
||||
$source_id = self::source_id($version_id);
|
||||
$source = $source_id > 0 ? get_post($source_id) : null;
|
||||
if (!$source instanceof \WP_Post || $source->post_type !== 'post') {
|
||||
return new \WP_Error('jio_version_source_missing', __('原文章不存在。', 'jio'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$snapshot_rev = (string) get_post_meta($version_id, self::META_SOURCE_REV, true);
|
||||
if (!$force && $snapshot_rev !== '' && $snapshot_rev !== self::source_revision($source_id)) {
|
||||
return new \WP_Error(
|
||||
'jio_version_source_changed',
|
||||
__('原文章在审核期间已被修改,请先确认后再应用。', 'jio'),
|
||||
['status' => 409]
|
||||
);
|
||||
}
|
||||
|
||||
$snapshot = self::snapshot($version_id);
|
||||
$update = [
|
||||
'ID' => $source_id,
|
||||
'post_title' => (string) $version->post_title,
|
||||
'post_content' => (string) $version->post_content,
|
||||
'post_excerpt' => (string) $version->post_excerpt,
|
||||
];
|
||||
$result = wp_update_post($update, true);
|
||||
if (is_wp_error($result)) {
|
||||
return new \WP_Error('jio_version_apply_failed', $result->get_error_message(), ['status' => 500]);
|
||||
}
|
||||
|
||||
$this->apply_snapshot($source_id, $snapshot, (int) $source->post_author);
|
||||
|
||||
wp_update_post([
|
||||
'ID' => $version_id,
|
||||
'post_status' => 'private',
|
||||
]);
|
||||
update_post_meta($version_id, self::META_STATUS, self::STATUS_APPROVED);
|
||||
update_post_meta($version_id, self::META_REV, self::revision($version_id) + 1);
|
||||
|
||||
do_action('jio_submission_version_approved', $source_id, $version_id, $reviewer_id);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'post_id' => $source_id,
|
||||
'message' => __('改稿已应用到线上文章。', 'jio'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 作者直接应用自己的改稿(具备免审发布权限时)。
|
||||
*/
|
||||
public function publish_working(int $source_id, int $user_id): array {
|
||||
$version = self::get_working($source_id, $user_id);
|
||||
if (!$version instanceof \WP_Post) {
|
||||
return ['success' => false, 'message' => __('没有待应用的改稿。', 'jio')];
|
||||
}
|
||||
$source = get_post($source_id);
|
||||
if (!$source instanceof \WP_Post || (int) $source->post_author !== $user_id) {
|
||||
return ['success' => false, 'message' => __('没有编辑此文章的权限。', 'jio')];
|
||||
}
|
||||
|
||||
$result = wp_update_post([
|
||||
'ID' => $source_id,
|
||||
'post_title' => (string) $version->post_title,
|
||||
'post_content' => (string) $version->post_content,
|
||||
'post_excerpt' => (string) $version->post_excerpt,
|
||||
], true);
|
||||
if (is_wp_error($result)) {
|
||||
return ['success' => false, 'message' => $result->get_error_message()];
|
||||
}
|
||||
|
||||
$this->apply_snapshot($source_id, self::snapshot((int) $version->ID), $user_id);
|
||||
|
||||
wp_update_post(['ID' => (int) $version->ID, 'post_status' => 'private']);
|
||||
update_post_meta((int) $version->ID, self::META_STATUS, self::STATUS_APPROVED);
|
||||
update_post_meta((int) $version->ID, self::META_REV, self::revision((int) $version->ID) + 1);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'post_id' => $source_id,
|
||||
'message' => __('改稿已发布。', 'jio'),
|
||||
'url' => get_permalink($source_id),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回改稿。
|
||||
*/
|
||||
public function reject(int $version_id, int $reviewer_id, string $note = ''): array {
|
||||
if (!self::can_review($reviewer_id)) {
|
||||
return ['success' => false, 'message' => __('没有审核权限。', 'jio')];
|
||||
}
|
||||
$version = get_post($version_id);
|
||||
if (!$version instanceof \WP_Post || $version->post_type !== self::POST_TYPE) {
|
||||
return ['success' => false, 'message' => __('改稿版本不存在。', 'jio')];
|
||||
}
|
||||
|
||||
wp_update_post(['ID' => $version_id, 'post_status' => 'draft']);
|
||||
update_post_meta($version_id, self::META_STATUS, self::STATUS_REJECTED);
|
||||
update_post_meta($version_id, self::META_NOTE, sanitize_textarea_field($note));
|
||||
update_post_meta($version_id, self::META_REV, self::revision($version_id) + 1);
|
||||
|
||||
do_action('jio_submission_version_rejected', $version_id, $reviewer_id, $note);
|
||||
|
||||
return ['success' => true, 'message' => __('已退回改稿,线上文章未变动。', 'jio')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入版本快照中的分类、标签、自定义筛选、封面与付费设置。
|
||||
*
|
||||
* @param array<string, mixed> $snapshot
|
||||
*/
|
||||
private function apply_snapshot(int $post_id, array $snapshot, int $author_id): void {
|
||||
if ($snapshot === []) {
|
||||
return;
|
||||
}
|
||||
$settings = FrontSubmit::get_instance()->settings();
|
||||
|
||||
if (isset($snapshot['categories']) && is_array($snapshot['categories'])) {
|
||||
$cats = array_values(array_unique(array_filter(array_map('absint', $snapshot['categories']))));
|
||||
$allowed = is_array($settings['allowed_cat_ids'] ?? null) ? array_map('intval', $settings['allowed_cat_ids']) : [];
|
||||
if ($allowed !== [] && $cats !== []) {
|
||||
$cats = array_values(array_intersect($cats, $allowed));
|
||||
}
|
||||
wp_set_post_categories($post_id, $cats);
|
||||
}
|
||||
if (array_key_exists('tags', $snapshot)) {
|
||||
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : [];
|
||||
wp_set_post_tags($post_id, array_values(array_filter(array_map('sanitize_text_field', $tags))));
|
||||
}
|
||||
if (isset($snapshot['custom']) && is_array($snapshot['custom'])) {
|
||||
foreach ($snapshot['custom'] as $key => $value) {
|
||||
$meta_key = '_jio_cf_' . sanitize_key((string) $key);
|
||||
if (is_array($value)) {
|
||||
$vals = array_values(array_filter(array_map('sanitize_key', $value)));
|
||||
$vals ? update_post_meta($post_id, $meta_key, $vals) : delete_post_meta($post_id, $meta_key);
|
||||
} else {
|
||||
$val = sanitize_key((string) $value);
|
||||
$val !== '' ? update_post_meta($post_id, $meta_key, $val) : delete_post_meta($post_id, $meta_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$front = FrontSubmit::get_instance();
|
||||
if (isset($snapshot['featured']) && is_array($snapshot['featured'])) {
|
||||
$front->apply_creator_featured($post_id, $snapshot['featured'], $author_id);
|
||||
}
|
||||
if (isset($snapshot['pay']) && is_array($snapshot['pay'])) {
|
||||
$pay_data = [
|
||||
'pay_enabled' => !empty($snapshot['pay']['enabled']) ? '1' : '',
|
||||
'jio_pay' => is_array($snapshot['pay']['fields'] ?? null) ? $snapshot['pay']['fields'] : [],
|
||||
];
|
||||
$front->apply_pay_payload($post_id, $pay_data, $author_id, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $snapshot
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function sanitize_snapshot(array $snapshot): array {
|
||||
$clean = [];
|
||||
if (isset($snapshot['categories']) && is_array($snapshot['categories'])) {
|
||||
$clean['categories'] = array_values(array_unique(array_filter(array_map('absint', $snapshot['categories']))));
|
||||
}
|
||||
if (isset($snapshot['tags'])) {
|
||||
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : preg_split('/[,,\n]+/u', (string) $snapshot['tags']);
|
||||
$clean['tags'] = array_values(array_filter(array_map('sanitize_text_field', (array) $tags)));
|
||||
}
|
||||
if (isset($snapshot['custom']) && is_array($snapshot['custom'])) {
|
||||
$custom = [];
|
||||
foreach ($snapshot['custom'] as $key => $value) {
|
||||
$key = sanitize_key((string) $key);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
$custom[$key] = is_array($value)
|
||||
? array_values(array_filter(array_map('sanitize_key', $value)))
|
||||
: sanitize_key((string) $value);
|
||||
}
|
||||
$clean['custom'] = $custom;
|
||||
}
|
||||
if (isset($snapshot['featured']) && is_array($snapshot['featured'])) {
|
||||
$clean['featured'] = $snapshot['featured'];
|
||||
}
|
||||
if (isset($snapshot['pay']) && is_array($snapshot['pay'])) {
|
||||
$clean['pay'] = [
|
||||
'enabled' => !empty($snapshot['pay']['enabled']),
|
||||
'fields' => is_array($snapshot['pay']['fields'] ?? null) ? $snapshot['pay']['fields'] : [],
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把版本快照里引用的私有资料绑定到源文章。
|
||||
*
|
||||
* @param array<string, mixed> $snapshot
|
||||
*/
|
||||
private function claim_snapshot_assets(array $snapshot, int $source_id, int $user_id): void {
|
||||
$keys = [];
|
||||
foreach ((array) ($snapshot['pay']['fields']['downloads'] ?? []) as $row) {
|
||||
if (is_array($row)) {
|
||||
$key = PrivateAssets::sanitize_asset_key((string) ($row['asset_key'] ?? ''));
|
||||
if ($key !== '') {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
}
|
||||
PrivateAssets::claim_many($keys, $source_id, $user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否具备审核权限。
|
||||
*/
|
||||
public static function can_review(int $user_id = 0): bool {
|
||||
$user_id = $user_id > 0 ? $user_id : get_current_user_id();
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
return user_can($user_id, 'manage_options') || user_can($user_id, 'edit_others_posts');
|
||||
}
|
||||
|
||||
public static function status_label(string $status): string {
|
||||
switch ($status) {
|
||||
case self::STATUS_PENDING:
|
||||
return __('待审核', 'jio');
|
||||
case self::STATUS_APPROVED:
|
||||
return __('已应用', 'jio');
|
||||
case self::STATUS_REJECTED:
|
||||
return __('已退回', 'jio');
|
||||
default:
|
||||
return __('草稿', 'jio');
|
||||
}
|
||||
}
|
||||
|
||||
private function user_can_cap(string $cap, int $user_id): bool {
|
||||
return FrontSubmit::get_instance()->user_can_cap($cap, $user_id);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user