Files
wordpress-theme-aksrt/src/Core/FrontSubmitSave.php
T
kate522 264f2c51b2 fix(admin): replicate reference front-submit settings page 1:1
- 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
2026-09-24 12:29:19 +08:00

342 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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;
}
}