- Rewrite post_list/submit schema (PHP + JS) with the reference theme's field set, order, defaults and dependencies; topics fields excluded - Map PostDisplay::get_submit_settings() and the FrontSubmit runtime to the new keys without legacy fallback - Guest submission follows the capability matrix; category required on submit; pay limits read from the cash/points fieldset - Drop dead front_submit_* settings, template branches and JS config - Add submit settings reference contract test
342 lines
13 KiB
PHP
342 lines
13 KiB
PHP
<?php
|
||
/**
|
||
* @package AKSRT
|
||
*/
|
||
|
||
namespace AKSRT\Core;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* Create / update / status machine for front-end article submit.
|
||
*/
|
||
trait FrontSubmitSave {
|
||
|
||
/**
|
||
* Persist a front-end submission (new or edit).
|
||
*
|
||
* @param array<string, mixed> $data Request payload.
|
||
* @param string $action posts_save|posts_draft
|
||
* @return array{success:bool,message:string,post_id?:int,url?:string,goto?:string,reload?:bool,time?:string,posts_id?:int}
|
||
*/
|
||
public function persist(array $data, string $action = 'posts_save'): array {
|
||
$user_id = get_current_user_id();
|
||
$settings = $this->settings();
|
||
$posts_id = absint($data['posts_id'] ?? $data['edit_id'] ?? $data['post_id'] ?? 0);
|
||
$is_guest = $user_id <= 0;
|
||
$is_edit = $posts_id > 0;
|
||
$is_draft = $action === 'posts_draft';
|
||
|
||
if (empty($settings['enabled'])) {
|
||
return ['success' => false, 'message' => __('投稿功能已关闭', 'aksrt')];
|
||
}
|
||
|
||
if ($is_guest) {
|
||
if ((int) $settings['default_user_id'] <= 0) {
|
||
return ['success' => false, 'message' => __('请先登录', 'aksrt')];
|
||
}
|
||
if ($is_draft) {
|
||
return ['success' => false, 'message' => __('未登录无法保存草稿', 'aksrt')];
|
||
}
|
||
if ($is_edit) {
|
||
return ['success' => false, 'message' => __('未登录无法编辑文章', 'aksrt')];
|
||
}
|
||
}
|
||
|
||
// 小黑屋(suspend):剥夺发布权限,发帖/草稿/编辑均拦截(对齐参考主题)。
|
||
if (!$is_guest && \AKSRT\User\Restriction::get_instance()->is_suspended($user_id)) {
|
||
$suspend = \AKSRT\User\Restriction::get_instance()->get_active_suspend($user_id);
|
||
$message = __('您已被关入小黑屋,暂时无法发布内容', 'aksrt');
|
||
if (!empty($suspend['reason'])) {
|
||
$message .= ',原因:' . $suspend['reason'];
|
||
}
|
||
return ['success' => false, 'message' => $message];
|
||
}
|
||
|
||
if ($is_edit) {
|
||
if (!$this->can_edit_post($posts_id, $user_id)) {
|
||
return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
|
||
}
|
||
} elseif (!$this->can_submit($is_guest ? 0 : $user_id)) {
|
||
return ['success' => false, 'message' => __('没有投稿权限', 'aksrt')];
|
||
}
|
||
|
||
$title = sanitize_text_field((string) ($data['post_title'] ?? $data['title'] ?? ''));
|
||
$content = (string) ($data['post_content'] ?? $data['content'] ?? '');
|
||
$excerpt = sanitize_textarea_field((string) ($data['excerpt'] ?? $data['post_excerpt'] ?? ''));
|
||
|
||
if ($title === '') {
|
||
return ['success' => false, 'message' => __('请填写文章标题', 'aksrt')];
|
||
}
|
||
if (trim(wp_strip_all_tags($content)) === '') {
|
||
return ['success' => false, 'message' => __('还未填写任何内容', 'aksrt')];
|
||
}
|
||
|
||
$categories = $this->normalize_category_ids($data);
|
||
$tags_raw = (string) ($data['tags'] ?? '');
|
||
|
||
if (!$is_draft) {
|
||
$title_len = $this->text_length($title);
|
||
if ($title_len < (int) $settings['title_min']) {
|
||
return ['success' => false, 'message' => __('标题太短!', 'aksrt')];
|
||
}
|
||
if ($title_len > (int) $settings['title_max']) {
|
||
return [
|
||
'success' => false,
|
||
'message' => sprintf(
|
||
/* translators: %d: max title length */
|
||
__('标题太长了,不能超过%d个字', 'aksrt'),
|
||
(int) $settings['title_max']
|
||
),
|
||
];
|
||
}
|
||
if ($this->text_length(wp_strip_all_tags($content)) < 10) {
|
||
return ['success' => false, 'message' => __('文章内容过少', 'aksrt')];
|
||
}
|
||
if (empty($categories)) {
|
||
return ['success' => false, 'message' => __('请选择文章分类', 'aksrt')];
|
||
}
|
||
}
|
||
|
||
// Allowed category whitelist.
|
||
$allowed = $settings['allowed_cat_ids'] ?? [];
|
||
if (!empty($allowed) && !empty($categories)) {
|
||
$categories = array_values(array_intersect($categories, $allowed));
|
||
if (!$is_draft && empty($categories)) {
|
||
return ['success' => false, 'message' => __('所选分类不可用', 'aksrt')];
|
||
}
|
||
}
|
||
|
||
$author_id = $user_id;
|
||
if ($is_guest) {
|
||
$guest_name = sanitize_text_field((string) ($data['user_name'] ?? ''));
|
||
if ($guest_name === '') {
|
||
return ['success' => false, 'message' => __('请输入昵称', 'aksrt')];
|
||
}
|
||
$contact = sanitize_text_field((string) ($data['contact_details'] ?? ''));
|
||
$suffix = $contact !== ''
|
||
? sprintf(__('[投稿-姓名:%1$s,联系:%2$s]', 'aksrt'), $guest_name, $contact)
|
||
: sprintf(__('[投稿-姓名:%s]', 'aksrt'), $guest_name);
|
||
$title = $title . $suffix;
|
||
$author_id = (int) $settings['default_user_id'];
|
||
}
|
||
|
||
$status = 'draft';
|
||
if (!$is_draft) {
|
||
$status = $this->resolve_publish_status($user_id);
|
||
}
|
||
|
||
$can_iframe = $this->user_can_cap('new_post_iframe_video', $user_id);
|
||
if ($can_iframe) {
|
||
add_filter('wp_kses_allowed_html', [$this, 'allow_iframe_kses'], 99, 2);
|
||
}
|
||
$content = wp_kses_post($content);
|
||
if ($can_iframe) {
|
||
remove_filter('wp_kses_allowed_html', [$this, 'allow_iframe_kses'], 99);
|
||
}
|
||
|
||
$postarr = [
|
||
'post_type' => 'post',
|
||
'post_title' => $title,
|
||
'post_content' => $content,
|
||
'post_status' => $status,
|
||
'comment_status' => 'open',
|
||
];
|
||
if ($excerpt !== '' || array_key_exists('excerpt', $data) || array_key_exists('post_excerpt', $data)) {
|
||
$postarr['post_excerpt'] = $excerpt;
|
||
}
|
||
if (!empty($settings['allow_tags']) && $tags_raw !== '') {
|
||
$tag_names = preg_split('/[,,\n]+/u', $tags_raw) ?: [];
|
||
$tag_names = array_values(array_filter(array_map('trim', $tag_names)));
|
||
$postarr['tags_input'] = $tag_names;
|
||
}
|
||
if (!empty($categories)) {
|
||
$postarr['post_category'] = $categories;
|
||
}
|
||
|
||
if ($is_edit) {
|
||
$postarr['ID'] = $posts_id;
|
||
$in_id = wp_update_post($postarr, true);
|
||
} else {
|
||
$postarr['post_author'] = $author_id;
|
||
$in_id = wp_insert_post($postarr, true);
|
||
}
|
||
|
||
if (is_wp_error($in_id)) {
|
||
return ['success' => false, 'message' => $in_id->get_error_message()];
|
||
}
|
||
if (!$in_id) {
|
||
return ['success' => false, 'message' => __('文章保存失败,请稍后再试', 'aksrt')];
|
||
}
|
||
|
||
// Re-apply categories when empty update should clear (logged-in only).
|
||
if (isset($data['category'])) {
|
||
wp_set_post_categories((int) $in_id, $categories);
|
||
}
|
||
if (!empty($settings['allow_tags']) && array_key_exists('tags', $data) && $tags_raw === '') {
|
||
wp_set_post_tags((int) $in_id, []);
|
||
}
|
||
|
||
$this->apply_custom_filters((int) $in_id, $data, $settings);
|
||
|
||
$pay_result = $this->save_pay_meta((int) $in_id, $data, $user_id, $settings);
|
||
if (!$pay_result['success']) {
|
||
$pay_result['post_id'] = (int) $in_id;
|
||
$pay_result['posts_id'] = (int) $in_id;
|
||
return $pay_result;
|
||
}
|
||
|
||
$this->save_featured_meta((int) $in_id, $data, $user_id);
|
||
|
||
$post = get_post((int) $in_id);
|
||
if ($post instanceof \WP_Post) {
|
||
do_action($is_edit ? 'aksrt_new_edit_posts' : 'aksrt_new_add_posts', $post);
|
||
if ($status === 'pending') {
|
||
do_action('aksrt_new_posts_pending', $post);
|
||
}
|
||
}
|
||
|
||
$url = get_permalink((int) $in_id) ?: '';
|
||
$send = [
|
||
'success' => true,
|
||
'post_id' => (int) $in_id,
|
||
'posts_id' => (int) $in_id,
|
||
'url' => $url,
|
||
];
|
||
|
||
switch ($status) {
|
||
case 'pending':
|
||
$send['message'] = __('内容已提交,正在等待审核', 'aksrt');
|
||
if ($user_id > 0 && function_exists('aksrt_get_user_home_url')) {
|
||
$send['reload'] = true;
|
||
$send['goto'] = add_query_arg(
|
||
['tab' => 'post', 'post_status' => 'pending'],
|
||
aksrt_get_user_home_url($user_id)
|
||
);
|
||
}
|
||
break;
|
||
case 'draft':
|
||
$send['message'] = __('草稿已保存', 'aksrt');
|
||
$send['time'] = current_time('mysql');
|
||
break;
|
||
default:
|
||
$send['message'] = __('文章已发布', 'aksrt');
|
||
$send['reload'] = true;
|
||
$send['goto'] = $url;
|
||
break;
|
||
}
|
||
|
||
return $send;
|
||
}
|
||
|
||
/**
|
||
* Publish status for a non-draft submission.
|
||
*
|
||
* Mirrors the reference theme: pending unless the user holds the
|
||
* "publish without review" capability.
|
||
*/
|
||
private function resolve_publish_status(int $user_id): string {
|
||
if ($this->user_can_cap('new_post_audit_no', $user_id)) {
|
||
return 'publish';
|
||
}
|
||
return 'pending';
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $data
|
||
* @return int[]
|
||
*/
|
||
private function normalize_category_ids(array $data): array {
|
||
$raw = $data['category'] ?? $data['categories'] ?? [];
|
||
if (!is_array($raw)) {
|
||
$raw = $raw === '' || $raw === null ? [] : [$raw];
|
||
}
|
||
$ids = array_values(array_unique(array_filter(array_map('absint', $raw))));
|
||
return $ids;
|
||
}
|
||
|
||
private function text_length(string $text): int {
|
||
$text = trim(wp_strip_all_tags($text));
|
||
if (function_exists('mb_strlen')) {
|
||
return (int) mb_strlen($text);
|
||
}
|
||
return strlen($text);
|
||
}
|
||
|
||
/**
|
||
* @param array<string,mixed> $data
|
||
* @param array<string,mixed> $settings
|
||
*/
|
||
private function apply_custom_filters(int $post_id, array $data, array $settings): void {
|
||
if (empty($settings['custom_filter_s'])) {
|
||
return;
|
||
}
|
||
|
||
$dims = [];
|
||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||
$filter_all = \AKSRT\Settings\Settings::get_post_filter_settings();
|
||
$dims = is_array($filter_all['custom_filter'] ?? null) ? $filter_all['custom_filter'] : [];
|
||
}
|
||
|
||
$allowed_keys = [];
|
||
foreach ($dims as $dim) {
|
||
if (!empty($dim['key'])) {
|
||
$allowed_keys[] = sanitize_key((string) $dim['key']);
|
||
}
|
||
}
|
||
|
||
foreach ($data as $key => $val) {
|
||
$dim_key = '';
|
||
if (strpos((string) $key, 'cf_') === 0) {
|
||
$dim_key = sanitize_key(substr((string) $key, 3));
|
||
} elseif (strpos((string) $key, '_aksrt_cf_') === 0) {
|
||
$dim_key = sanitize_key(substr((string) $key, 10));
|
||
} elseif (in_array(sanitize_key((string) $key), $allowed_keys, true)) {
|
||
$dim_key = sanitize_key((string) $key);
|
||
}
|
||
if ($dim_key === '' || (!empty($allowed_keys) && !in_array($dim_key, $allowed_keys, true))) {
|
||
continue;
|
||
}
|
||
$meta_key = '_aksrt_cf_' . $dim_key;
|
||
if (is_array($val)) {
|
||
$vals = array_values(array_filter(array_map('sanitize_key', $val)));
|
||
if ($vals) {
|
||
update_post_meta($post_id, $meta_key, $vals);
|
||
} else {
|
||
delete_post_meta($post_id, $meta_key);
|
||
}
|
||
} else {
|
||
$cf_val = sanitize_key((string) $val);
|
||
if ($cf_val !== '') {
|
||
update_post_meta($post_id, $meta_key, $cf_val);
|
||
} else {
|
||
delete_post_meta($post_id, $meta_key);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param array<string,string> $tags
|
||
* @return array<string,string>
|
||
*/
|
||
public function allow_iframe_kses(array $tags, $context = ''): array {
|
||
if ($context !== 'post' && $context !== '') {
|
||
return $tags;
|
||
}
|
||
$tags['iframe'] = [
|
||
'src' => true,
|
||
'width' => true,
|
||
'height' => true,
|
||
'frameborder' => true,
|
||
'allowfullscreen' => true,
|
||
'allow' => true,
|
||
'style' => true,
|
||
'class' => true,
|
||
'id' => true,
|
||
'scrolling' => true,
|
||
];
|
||
return $tags;
|
||
}
|
||
}
|