Files
wordpress-theme-aksrt/src/Core/CreatorRest.php
T
2026-11-06 22:30:08 +08:00

666 lines
25 KiB
PHP

<?php
/**
* Jio Creator Studio 的 REST 接口。
*
* 只做前台创作体验需要的编排:读取编辑上下文、保存/自动保存、提交审核、
* 预览链接、私有资料上传。所有文章写入仍复用 FrontSubmit 的权限、字段清洗
* 与状态机;已发布文章的改动走 SubmissionVersion,不直接覆盖线上内容。
*
* @package Jio
*/
namespace Jio\Core;
use Jio\Content\PrivateAssets;
defined('ABSPATH') || exit;
final class CreatorRest {
public const NS = 'jio/v1';
public function register_routes(): void {
register_rest_route(self::NS, '/creator/bootstrap', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'rest_bootstrap'],
'permission_callback' => [$this, 'can_use_studio'],
'args' => [
'post_id' => ['default' => 0, 'sanitize_callback' => 'absint'],
],
]);
register_rest_route(self::NS, '/creator/save', [
'methods' => 'POST',
'callback' => [$this, 'rest_save'],
'permission_callback' => [$this, 'can_use_studio'],
]);
register_rest_route(self::NS, '/creator/submit', [
'methods' => 'POST',
'callback' => [$this, 'rest_submit'],
'permission_callback' => [$this, 'can_use_studio'],
]);
register_rest_route(self::NS, '/creator/preview', [
'methods' => 'POST',
'callback' => [$this, 'rest_preview'],
'permission_callback' => [$this, 'can_use_studio'],
]);
register_rest_route(self::NS, '/creator/private-upload', [
'methods' => 'POST',
'callback' => [$this, 'rest_private_upload'],
'permission_callback' => [$this, 'can_upload_private'],
]);
register_rest_route(self::NS, '/creator/private-uploads', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'rest_list_private_uploads'],
'permission_callback' => [$this, 'can_upload_private'],
]);
}
public function can_use_studio($request = null): bool {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return false;
}
$front = FrontSubmit::get_instance();
if (empty($front->settings()['enabled'])) {
return false;
}
$post_id = 0;
if ($request instanceof \WP_REST_Request) {
$post_id = absint($request->get_param('post_id'));
} elseif (isset($_REQUEST['post_id'])) {
$post_id = absint(wp_unslash($_REQUEST['post_id']));
}
if ($post_id > 0) {
return $front->can_edit_post($post_id, $user_id);
}
return $front->can_submit($user_id);
}
public function can_upload_private(): bool {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return false;
}
return FrontSubmit::get_instance()->user_can_cap('new_post_pay_download', $user_id);
}
/**
* 编辑上下文:作者打开创作页时的一切初始数据。
*/
public function rest_bootstrap(\WP_REST_Request $request): \WP_REST_Response {
$post_id = absint($request->get_param('post_id'));
$user_id = get_current_user_id();
return new \WP_REST_Response($this->bootstrap_payload($post_id, $user_id), 200);
}
/**
* @return array<string, mixed>
*/
public function bootstrap_payload(int $post_id, int $user_id): array {
$front = FrontSubmit::get_instance();
$settings = $front->settings();
$source = $post_id > 0 ? get_post($post_id) : null;
$version = null;
$version_id = 0;
if ($source instanceof \WP_Post && SubmissionVersion::requires_version($post_id)) {
$version = SubmissionVersion::get_working($post_id, $user_id);
$version_id = $version instanceof \WP_Post ? (int) $version->ID : 0;
}
$editable = $version instanceof \WP_Post
? $version
: ($source instanceof \WP_Post ? $source : null);
$pay_meta = $editable instanceof \WP_Post && function_exists('jio_get_pay_meta')
? jio_get_pay_meta($editable->ID)
: DomainModel::default_pay_meta();
$snapshot = $version_id > 0 ? SubmissionVersion::snapshot($version_id) : [];
$post_meta = $source instanceof \WP_Post
? (array) get_post_meta($source->ID, DomainModel::POST_META_KEY, true)
: [];
$terms = $this->term_payload($settings, $source, $snapshot);
return [
'sourceId' => $post_id,
'versionId' => $version_id,
'versionRev' => $version_id > 0 ? SubmissionVersion::revision($version_id) : 0,
'status' => $version_id > 0 ? SubmissionVersion::status($version_id) : ($source->post_status ?? ''),
'reviewNote' => $version_id > 0 ? (string) get_post_meta($version_id, SubmissionVersion::META_NOTE, true) : '',
'title' => $editable instanceof \WP_Post ? $editable->post_title : '',
'excerpt' => $editable instanceof \WP_Post ? $editable->post_excerpt : '',
'content' => $editable instanceof \WP_Post ? $editable->post_content : '',
'permalink' => $source instanceof \WP_Post ? get_permalink($source) : '',
'isPublished'=> $source instanceof \WP_Post && $source->post_status === 'publish',
'categories' => $terms['categories'],
'tags' => $terms['tags'],
'custom' => $terms['custom'],
'customDefs' => $terms['customDefs'],
'featured' => $this->creator_featured_payload($source, $post_meta),
'pay' => $this->pay_payload((array) $pay_meta, $snapshot, $settings),
'capabilities' => $this->capability_payload($front, $user_id, $post_id),
'limits' => [
'titleMin' => (int) $settings['title_min'],
'titleMax' => (int) $settings['title_max'],
'titlePlaceholder' => (string) $settings['title_placeholder'],
'contentPlaceholder' => (string) $settings['content_placeholder'],
],
'privateUploads' => $this->private_upload_rows($user_id, $post_id),
'privateStorage' => PrivateAssets::is_available(),
'media' => [
'listUrl' => esc_url_raw(rest_url(self::NS . '/front-submit/media')),
'uploadUrl' => [
'image' => esc_url_raw(rest_url(self::NS . '/front-submit/upload/image')),
],
'maxBytes' => FrontSubmit::upload_limit_bytes('image'),
'batch' => class_exists('\Jio\Attachment\UploadLimits') ? \Jio\Attachment\UploadLimits::image_batch_limit() : 6,
],
'rest' => [
'root' => esc_url_raw(rest_url()),
'nonce' => wp_create_nonce('wp_rest'),
],
'urls' => [
'submit' => function_exists('jio_get_new_post_url') ? jio_get_new_post_url() : '',
'newPost' => function_exists('jio_get_new_post_url') ? jio_get_new_post_url() : '',
'center' => function_exists('jio_get_user_center_url') ? jio_get_user_center_url('posts') : '',
],
];
}
/**
* 保存或自动保存。已发布文章写入版本记录;新文章走既有 persist()。
*/
public function rest_save(\WP_REST_Request $request) {
$user_id = get_current_user_id();
$post_id = absint($request->get_param('post_id'));
$is_auto = (bool) $request->get_param('autosave');
$payload = [
'title' => (string) $request->get_param('title'),
'content' => (string) $request->get_param('content'),
'categories' => (array) $request->get_param('categories'),
'tags' => (array) $request->get_param('tags'),
'custom' => (array) $request->get_param('custom'),
'featured' => (array) $request->get_param('featured'),
'schedule' => (array) $request->get_param('schedule'),
'pay' => (array) $request->get_param('pay'),
];
if ($post_id > 0 && SubmissionVersion::requires_version($post_id)) {
$result = SubmissionVersion::get_instance()->save_working($post_id, $user_id, [
'title' => $payload['title'] !== '' ? $payload['title'] : null,
'content' => $payload['content'],
'base_rev' => absint($request->get_param('rev')),
'snapshot' => $this->snapshot_from_payload($payload),
], false);
if (is_wp_error($result)) {
return $result;
}
$result['autosave'] = $is_auto;
return new \WP_REST_Response($result, 200);
}
$front = FrontSubmit::get_instance();
$data = [
'posts_id' => $post_id,
'post_title' => $payload['title'],
'post_content' => $payload['content'],
'category' => $payload['categories'],
'tags' => implode(',', array_map('strval', $payload['tags'])),
'featured_data'=> $payload['featured'],
'schedule' => $payload['schedule'],
];
foreach ($payload['custom'] as $key => $value) {
$data['cf_' . sanitize_key((string) $key)] = $value;
}
if ($payload['pay'] !== []) {
$data['pay_enabled'] = !empty($payload['pay']['enabled']) ? '1' : '';
$data['jio_pay'] = (array) ($payload['pay']['fields'] ?? []);
}
$result = $front->persist($data, 'posts_draft');
if (empty($result['success'])) {
return new \WP_Error('jio_creator_save_failed', (string) ($result['message'] ?? __('保存失败', 'jio')), ['status' => 400]);
}
$result['autosave'] = $is_auto;
$result['versionRev'] = max(1, (int) ($result['rev'] ?? 1));
return new \WP_REST_Response($result, 200);
}
/**
* 提交审核 / 直接发布。
*/
public function rest_submit(\WP_REST_Request $request) {
$user_id = get_current_user_id();
$post_id = absint($request->get_param('post_id'));
$front = FrontSubmit::get_instance();
// 与 admin-ajax 投稿入口保持同一道人机验证闸门(启用 front_submit 场景时)。
if (function_exists('jio_require_captcha')) {
$captcha = jio_require_captcha('front_submit', [
'jio_captcha_proof' => sanitize_text_field((string) $request->get_param('captcha_proof')),
]);
if ($captcha !== true) {
$message = is_wp_error($captcha) ? $captcha->get_error_message() : __('人机验证失败,请重试', 'jio');
return new \WP_Error('jio_captcha_failed', $message, ['status' => 400]);
}
}
$payload = [
'title' => (string) $request->get_param('title'),
'content' => (string) $request->get_param('content'),
'categories' => (array) $request->get_param('categories'),
'tags' => (array) $request->get_param('tags'),
'custom' => (array) $request->get_param('custom'),
'featured' => (array) $request->get_param('featured'),
'schedule' => (array) $request->get_param('schedule'),
'pay' => (array) $request->get_param('pay'),
];
if ($post_id > 0 && SubmissionVersion::requires_version($post_id)) {
$invalid = $this->validate_submission($payload, $user_id);
if ($invalid !== '') {
return new \WP_Error('jio_creator_invalid', $invalid, ['status' => 400]);
}
$version = SubmissionVersion::get_instance()->save_working($post_id, $user_id, [
'title' => $payload['title'],
'content' => $payload['content'],
'base_rev' => absint($request->get_param('rev')),
'snapshot' => $this->snapshot_from_payload($payload),
], true);
if (is_wp_error($version)) {
return $version;
}
// 具备免审发布权限且明确选择立即发布时,才直接应用到线上文章。
if ($request->get_param('publish_now') && $front->user_can_cap('new_post_audit_no', $user_id)) {
$applied = SubmissionVersion::get_instance()->publish_working($post_id, $user_id);
if (empty($applied['success'])) {
return new \WP_Error('jio_creator_publish_failed', (string) ($applied['message'] ?? __('发布失败', 'jio')), ['status' => 400]);
}
return new \WP_REST_Response($applied, 200);
}
return new \WP_REST_Response($version, 200);
}
$data = [
'posts_id' => $post_id,
'post_title' => $payload['title'],
'post_content' => $payload['content'],
'category' => $payload['categories'],
'tags' => implode(',', array_map('strval', $payload['tags'])),
'featured_data' => $payload['featured'],
'schedule' => $payload['schedule'],
];
foreach ($payload['custom'] as $key => $value) {
$data['cf_' . sanitize_key((string) $key)] = $value;
}
if ($payload['pay'] !== []) {
$data['pay_enabled'] = !empty($payload['pay']['enabled']) ? '1' : '';
$data['jio_pay'] = (array) ($payload['pay']['fields'] ?? []);
}
$result = $front->persist($data, 'posts_save');
if (empty($result['success'])) {
return new \WP_Error('jio_creator_submit_failed', (string) ($result['message'] ?? __('提交失败', 'jio')), ['status' => 400]);
}
$this->claim_private_assets($payload, (int) ($result['post_id'] ?? 0), $user_id);
return new \WP_REST_Response($result, 200);
}
/**
* 预览链接:已发布文章的改稿版本使用一次性签名地址,不公开版本内容。
*/
public function rest_preview(\WP_REST_Request $request) {
$user_id = get_current_user_id();
$post_id = absint($request->get_param('post_id'));
$version_id = absint($request->get_param('version_id'));
if ($version_id > 0) {
$version = get_post($version_id);
if (!$version instanceof \WP_Post || $version->post_type !== SubmissionVersion::POST_TYPE) {
return new \WP_Error('jio_review_preview_missing', __('改稿不存在。', 'jio'), ['status' => 404]);
}
if ((int) $version->post_author !== $user_id && !SubmissionVersion::can_review($user_id)) {
return new \WP_Error('jio_review_preview_forbidden', __('没有预览权限。', 'jio'), ['status' => 403]);
}
$token = PreviewToken::issue($version_id, $user_id);
$source_id = SubmissionVersion::source_id($version_id);
$source = $source_id > 0 ? get_post($source_id) : null;
$base = $source instanceof \WP_Post ? (string) get_permalink($source) : home_url('/');
return new \WP_REST_Response([
'url' => add_query_arg('jio_studio_preview', $token, $base),
], 200);
}
if ($post_id <= 0) {
return new \WP_Error('jio_review_preview_missing', __('尚未保存文章。', 'jio'), ['status' => 400]);
}
$post = get_post($post_id);
if (!$post instanceof \WP_Post) {
return new \WP_Error('jio_review_preview_missing', __('文章不存在。', 'jio'), ['status' => 404]);
}
return new \WP_REST_Response([
'url' => (string) get_preview_post_link($post),
], 200);
}
/**
* 私有资料上传(付费附件)。
*/
public function rest_private_upload(\WP_REST_Request $request) {
$file = $request->get_file_params()['file'] ?? null;
if (!is_array($file)) {
return new \WP_Error('jio_private_upload_failed', __('请选择要上传的资料文件。', 'jio'), ['status' => 400]);
}
$post_id = absint($request->get_param('post_id'));
$result = PrivateAssets::get_instance()->store_upload($file, get_current_user_id(), $post_id);
if (is_wp_error($result)) {
return $result;
}
return new \WP_REST_Response($result, 201);
}
public function rest_list_private_uploads(\WP_REST_Request $request): \WP_REST_Response {
return new \WP_REST_Response([
'items' => $this->private_upload_rows(get_current_user_id(), absint($request->get_param('post_id'))),
], 200);
}
/**
* @return list<array<string,mixed>>
*/
private function private_upload_rows(int $user_id, int $post_id): array {
global $wpdb;
$table = DBMigrator::table('private_asset');
if ($table === '' || $user_id <= 0) {
return [];
}
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT asset_key, post_id, file_name, mime, size_bytes, created_at FROM {$table}
WHERE owner_id = %d AND (post_id = %d OR post_id = 0) ORDER BY id DESC LIMIT 50",
$user_id,
$post_id
), ARRAY_A);
$items = [];
foreach (is_array($rows) ? $rows : [] as $row) {
$items[] = [
'assetKey' => (string) $row['asset_key'],
'postId' => (int) $row['post_id'],
'fileName' => (string) $row['file_name'],
'mime' => (string) $row['mime'],
'sizeBytes' => (int) $row['size_bytes'],
'sizeLabel' => size_format((int) $row['size_bytes']),
];
}
return $items;
}
/**
* 提交前的必填校验,与 FrontSubmitSave 保持同一口径。
*
* @param array<string, mixed> $payload
*/
private function validate_submission(array $payload, int $user_id): string {
$settings = FrontSubmit::get_instance()->settings();
$title = trim(wp_strip_all_tags((string) ($payload['title'] ?? '')));
$content = trim(wp_strip_all_tags((string) ($payload['content'] ?? '')));
if ($title === '') {
return __('请填写文章标题', 'jio');
}
$length = function_exists('mb_strlen') ? mb_strlen($title) : strlen($title);
if ($length < (int) $settings['title_min']) {
return __('标题太短!', 'jio');
}
if ($length > (int) $settings['title_max']) {
return sprintf(__('标题太长了,不能超过%d个字', 'jio'), (int) $settings['title_max']);
}
if ((function_exists('mb_strlen') ? mb_strlen($content) : strlen($content)) < 10) {
return __('文章内容过少', 'jio');
}
if (empty($payload['categories'])) {
return __('请选择文章分类', 'jio');
}
return '';
}
/**
* 组合提交时的版本快照(分类、标签、封面、付费)。
*
* @param array<string, mixed> $payload
* @return array<string, mixed>
*/
private function snapshot_from_payload(array $payload): array {
$downloads = [];
foreach ((array) ($payload['pay']['fields']['downloads'] ?? []) as $row) {
if (!is_array($row)) {
continue;
}
$key = PrivateAssets::sanitize_asset_key((string) ($row['asset_key'] ?? ''));
if ($key !== '') {
$asset = PrivateAssets::get($key);
if ($asset === null || $asset['owner_id'] !== get_current_user_id()) {
continue;
}
$downloads[] = [
'type' => 'protected',
'asset_key' => $key,
'file_name' => $asset['file_name'],
'size_bytes' => $asset['size_bytes'],
'label' => (string) ($row['label'] ?? ''),
];
continue;
}
$url = esc_url_raw((string) ($row['url'] ?? ''));
if ($url !== '') {
$downloads[] = [
'type' => 'direct',
'url' => $url,
'label' => (string) ($row['label'] ?? ''),
'extract_code' => (string) ($row['extract_code'] ?? ''),
];
}
}
return [
'categories' => array_values(array_filter(array_map('absint', (array) $payload['categories']))),
'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $payload['tags']))),
'custom' => is_array($payload['custom']) ? $payload['custom'] : [],
'featured' => is_array($payload['featured']) ? $payload['featured'] : [],
'pay' => [
'enabled' => !empty($payload['pay']['enabled']),
'fields' => array_merge(
(array) ($payload['pay']['fields'] ?? []),
['downloads' => $downloads]
),
],
];
}
private function claim_private_assets(array $payload, int $post_id, int $user_id): void {
if ($post_id <= 0) {
return;
}
$keys = [];
foreach ((array) ($payload['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, $post_id, $user_id);
}
/**
* @param array<string, mixed> $settings
* @return array<string, mixed>
*/
private function term_payload(array $settings, ?\WP_Post $source, array $snapshot): array {
$cat_args = [
'taxonomy' => 'category',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
];
if (!empty($settings['allowed_cat_ids'])) {
$cat_args['include'] = $settings['allowed_cat_ids'];
} else {
$cat_args['parent'] = 0;
}
$terms = get_terms($cat_args);
$categories = [];
foreach (is_array($terms) ? $terms : [] as $term) {
$categories[] = ['id' => (int) $term->term_id, 'name' => (string) $term->name];
}
$current = $source instanceof \WP_Post && $snapshot === []
? array_map('intval', wp_get_post_categories($source->ID))
: array_map('intval', (array) ($snapshot['categories'] ?? []));
$tags = $source instanceof \WP_Post && $snapshot === []
? wp_get_post_tags($source->ID, ['fields' => 'names'])
: array_map('strval', (array) ($snapshot['tags'] ?? []));
$custom_defs = [];
$custom = [];
if (!empty($settings['custom_filter_s']) && class_exists('\Jio\Settings\Settings')) {
$filter_all = \Jio\Settings\Settings::get_post_filter_settings();
$dims = is_array($filter_all['custom_filter'] ?? null) ? $filter_all['custom_filter'] : [];
foreach ($dims as $dim) {
$key = sanitize_key((string) ($dim['key'] ?? ''));
if ($key === '') {
continue;
}
$vals = [];
foreach ((array) ($dim['vals'] ?? []) as $value) {
$vkey = (string) ($value['key'] ?? '');
if ($vkey === '') {
continue;
}
$vals[] = ['key' => $vkey, 'name' => (string) ($value['name'] ?? $vkey)];
}
$custom_defs[] = ['key' => $key, 'name' => (string) ($dim['name'] ?? $key), 'values' => $vals];
$saved = $snapshot !== [] ? ($snapshot['custom'][$key] ?? []) : ($source instanceof \WP_Post ? get_post_meta($source->ID, '_jio_cf_' . $key, true) : []);
$custom[$key] = is_array($saved) ? array_values(array_map('strval', $saved)) : ($saved !== '' ? [(string) $saved] : []);
}
}
return [
'categories' => ['items' => $categories, 'selected' => $current],
'tags' => ['items' => is_array($tags) ? array_values($tags) : [], 'enabled' => !empty($settings['allow_tags'])],
'custom' => $custom,
'customDefs' => $custom_defs,
];
}
/**
* @param array<string, mixed> $post_meta
* @return array<string, mixed>
*/
private function creator_featured_payload(?\WP_Post $source, array $post_meta): array {
$type = '';
$data = [];
$id = 0;
if ($source instanceof \WP_Post) {
$thumb_id = (int) get_post_thumbnail_id($source->ID);
$url = $thumb_id > 0 ? (string) wp_get_attachment_image_url($thumb_id, 'full') : (string) ($post_meta['thumbnail_url'] ?? '');
if ($url !== '') {
$type = 'image';
$id = $thumb_id;
$data = ['url' => $url, 'id' => $thumb_id];
}
}
$front = FrontSubmit::get_instance();
$user = get_current_user_id();
return [
'type' => $type,
'data' => $data,
'mediaId' => $id,
'canImage' => $front->user_can_cap('new_post_image_cover', $user),
'canSlide' => $front->user_can_cap('new_post_slide_cover', $user),
'canVideo' => $front->user_can_cap('new_post_video_cover', $user),
];
}
/**
* @param array<string, mixed> $pay_meta
* @param array<string, mixed> $settings
* @return array<string, mixed>
*/
private function pay_payload(array $pay_meta, array $snapshot, array $settings): array {
$front = FrontSubmit::get_instance();
$user = get_current_user_id();
$pay = $snapshot['pay']['fields'] ?? $pay_meta;
$enabled = $snapshot !== [] ? !empty($snapshot['pay']['enabled']) : (($pay_meta['pay_type'] ?? 'no') !== 'no');
$downloads = [];
foreach ((array) ($pay['downloads'] ?? []) as $row) {
if (!is_array($row)) {
continue;
}
$key = PrivateAssets::sanitize_asset_key((string) ($row['asset_key'] ?? ''));
$downloads[] = [
'type' => $key !== '' ? 'protected' : (string) ($row['type'] ?? 'direct'),
'assetKey' => $key,
'url' => $key !== '' ? '' : (string) ($row['url'] ?? ''),
'label' => (string) ($row['label'] ?? ''),
'fileName' => (string) ($row['file_name'] ?? ''),
'sizeBytes' => (int) ($row['size_bytes'] ?? 0),
];
}
return [
'enabled' => (bool) $enabled,
'canPay' => $front->user_can_cap('new_post_pay', $user),
'canDownload' => $front->user_can_cap('new_post_pay_download', $user),
'payModo' => (($pay['pay_modo'] ?? '0') === 'points') ? 'points' : 'cash',
'price' => (string) ($pay['price'] ?? '0.00'),
'points' => (int) ($pay['points_price'] ?? 0),
'vip1Price' => (string) ($pay['vip1_price'] ?? '0.00'),
'vip2Price' => (string) ($pay['vip2_price'] ?? '0.00'),
'payLimit' => (int) ($pay['pay_limit'] ?? 0),
'downloads' => $downloads,
'freeAllowed' => !empty($settings['pay_free_s']),
'priceMin' => (int) ($settings['pay_price_min'] ?? 0),
'priceMax' => (int) ($settings['pay_price_max'] ?? 0),
'pointsMin' => (int) ($settings['pay_points_min'] ?? 0),
'pointsMax' => (int) ($settings['pay_points_max'] ?? 0),
'privateStorage' => PrivateAssets::is_available(),
];
}
/**
* @return array<string, bool>
*/
private function capability_payload(FrontSubmit $front, int $user_id, int $post_id): array {
$cap = static function (string $name) use ($front, $user_id, $post_id): bool {
return $front->user_can_cap($name, $user_id, $post_id > 0 ? $post_id : null);
};
return [
'canUploadImage' => $cap('upload_submit_image'),
'canUploadVideo' => $cap('new_post_upload_video'),
'canUploadFile' => $cap('upload_resource_attachment'),
'canEmbed' => $cap('new_post_iframe_video'),
'canHide' => $cap('new_post_hide'),
'canPay' => $cap('new_post_pay'),
'canPayDownload' => $cap('new_post_pay_download'),
'canPublishNow' => $cap('new_post_audit_no'),
// 定时发布只有能直接发布的账号可用;其余账号提交后仍走审核队列。
'canSchedule' => $cap('new_post_audit_no'),
'canEdit' => $post_id > 0 ? $front->can_edit_post($post_id, $user_id) : $front->can_submit($user_id),
'isLoggedIn' => $user_id > 0,
];
}
}