feat(front-submit): 1:1 写文章/投稿页对齐 zibll newposts

Register AKSRT 写文章/投稿 page template, wire header publish entry to
front submit URL, rewrite page-submit as dual-column TinyMCE compose with
featured cover, multi-category/topics/filters, pay box, drafts, and
capability-gated FrontSubmit AJAX (pay/featured traits).
This commit is contained in:
kate522 committed 2026-07-18 13:29:27 +08:00
1 parent cd4b1a73dc
commit 518e825bdd
25 files changed
+3029 -982

No files matched your search

@@ -343,7 +343,9 @@ class SchemaPostList {
['key' => 'front_submit_cover', 'label' => '封面图', 'type' => 'switch', 'default' => true],
['key' => 'front_submit_category', 'label' => '分类选择', 'type' => 'switch', 'default' => true],
['key' => 'post_article_cat', 'label' => '允许投稿分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '为空则允许全部分类'],
['key' => 'front_submit_guest_enabled', 'label' => '允许免登录投稿', 'type' => 'switch', 'default' => false, 'help' => '开启后访客可投稿;文章归属到下方用户'],
['key' => 'post_article_user', 'label' => '免登录归属用户', 'type' => 'user_select', 'default' => 0, 'help' => '免登录投稿时文章归属的用户,搜索选择'],
['key' => 'front_submit_captcha', 'label' => '提交人机验证', 'type' => 'switch', 'default' => false, 'help' => '提交正式投稿时校验人机验证'],
['key' => 'front_submit_tags', 'label' => '标签输入', 'type' => 'switch', 'default' => true],
['key' => 'post_article_topics_s', 'label' => '专题选择', 'type' => 'switch', 'default' => false, 'help' => '投稿时可选择专题分类'],
['key' => 'post_article_custom_filter_s', 'label' => '高级筛选选择', 'type' => 'switch', 'default' => true, 'help' => '投稿时可选择自定义筛选维度值'],
+283 -305
View File
@@ -7,12 +7,22 @@ namespace AKSRT\Core;
defined('ABSPATH') || exit;
/**
* Front-end article submit (写文章 / 投稿).
*
* AJAX authority for create / draft / update / delete; capability-gated.
*/
class FrontSubmit {
use FrontSubmitSave;
use FrontSubmitPay;
use FrontSubmitFeatured;
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) self::$instance = new self();
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
@@ -21,368 +31,336 @@ class FrontSubmit {
add_action('wp_ajax_aksrt_save_draft', [$this, 'ajax_save_draft']);
add_action('wp_ajax_aksrt_update_post', [$this, 'ajax_update_post']);
add_action('wp_ajax_aksrt_delete_draft', [$this, 'ajax_delete_draft']);
add_action('wp_ajax_aksrt_delete_post', [$this, 'ajax_delete_post']);
// Guest path (when enabled).
add_action('wp_ajax_nopriv_aksrt_submit_post', [$this, 'ajax_submit_post']);
add_filter('body_class', [$this, 'body_class']);
add_filter('wp_robots', [$this, 'robots_noindex']);
add_filter('aksrt_floating_buttons_enabled', [$this, 'disable_chrome_on_submit']);
add_filter('aksrt_footer_tabbar_enabled', [$this, 'disable_chrome_on_submit']);
}
/**
* 检查用户是否可以投稿
*
* @param int|null $user_id User ID (null = current user).
* @return bool
* @return array<string, mixed>
*/
public function settings(): array {
if (class_exists('\AKSRT\Settings\Settings')) {
return \AKSRT\Settings\Settings::get_submit_settings();
}
return [
'enabled' => true,
'title_min' => 5,
'title_max' => 30,
'allowed_cat_ids' => [],
'default_user_id' => 0,
'guest_enabled' => false,
'allow_category' => true,
'allow_tags' => true,
'allow_cover' => true,
'topics_s' => false,
'custom_filter_s' => true,
'review_mode' => 'pending',
'pay_free_s' => true,
'pay_price_min' => 0,
'pay_price_max' => 100,
'pay_vip_price_s' => true,
'pay_vip_1_discount' => 80,
'pay_vip_2_discount' => 50,
];
}
/**
* Whether the user (or guest) may open / create a submission.
*/
public function can_submit(?int $user_id = null): bool {
if ($user_id === null) {
$user_id = get_current_user_id();
}
if ($user_id <= 0) return false;
// Read submission settings
$submit_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$submit_settings = \AKSRT\Settings\Settings::get_value('post_list.submit', []);
}
// Check if front submission is enabled (fix: was checking !empty && empty which is always false)
if (isset($submit_settings['submit_enabled']) && !$submit_settings['submit_enabled']) {
$settings = $this->settings();
if (empty($settings['enabled'])) {
return false;
}
// Role whitelist: if configured, user must have one of the allowed roles
$allowed_roles = $submit_settings['submit_roles'] ?? [];
if (!empty($allowed_roles)) {
$user = get_userdata($user_id);
if (!$user) return false;
$user_roles = $user->roles;
if (empty(array_intersect($user_roles, (array) $allowed_roles))) {
// Also allow administrators
if (!in_array('administrator', $user_roles, true)) {
return false;
}
}
if ($user_id === null) {
$user_id = get_current_user_id();
}
// Credits threshold: if configured, user must have enough credits
$min_credits = (int) ($submit_settings['submit_min_credits'] ?? 0);
if ($min_credits > 0 && class_exists('\AKSRT\Credits\Credits')) {
$balance = \AKSRT\Credits\Credits::balance($user_id);
if ($balance < $min_credits) {
return false;
}
}
// Default: allow users with edit_posts capability (or any logged-in user if submission is enabled)
return user_can($user_id, 'edit_posts') || !empty($submit_settings['submit_enabled']);
}
/**
* 提交文章
*
* @param array $data 投稿数据
* @return array{success: bool, post_id?: int, message: string}
*/
public function submit(array $data): array {
$user_id = get_current_user_id();
if (!$this->can_submit($user_id)) {
return ['success' => false, 'message' => __('没有投稿权限', 'aksrt')];
}
$title = sanitize_text_field((string) ($data['title'] ?? ''));
$content = wp_kses_post((string) ($data['content'] ?? ''));
$category = (int) ($data['category'] ?? 0);
$tags = sanitize_text_field((string) ($data['tags'] ?? ''));
$status = sanitize_key((string) ($data['status'] ?? 'pending'));
if ($title === '') {
return ['success' => false, 'message' => __('标题不能为空', 'aksrt')];
}
$valid_statuses = ['pending', 'draft', 'publish'];
if (!in_array($status, $valid_statuses, true)) {
$status = 'pending';
}
// Determine review policy: if review is not required and user can publish, auto-publish
if ($status === 'pending') {
$submit_settings = [];
if (class_exists('\AKSRT\Settings\Settings')) {
$submit_settings = \AKSRT\Settings\Settings::get_value('post_list.submit', []);
}
$review_required = $submit_settings['review_required'] ?? true;
$skip_moderation = false;
if (class_exists('\AKSRT\Settings\Settings')) {
$all = \AKSRT\Settings\Settings::get_all();
$skip_moderation = !empty($all['skip_submit_moderation']);
}
if (!$review_required || $skip_moderation) {
// Auto-publish if user has publish_posts capability
if (user_can($user_id, 'publish_posts')) {
$status = 'publish';
}
}
}
$post_data = [
'post_title' => $title,
'post_content' => $content,
'post_status' => $status,
'post_author' => $user_id,
'post_type' => 'post',
];
$post_id = wp_insert_post($post_data, true);
if (is_wp_error($post_id)) {
return ['success' => false, 'message' => $post_id->get_error_message()];
}
// 设置分类
if ($category > 0) {
wp_set_post_categories($post_id, [$category]);
}
// 设置标签
if ($tags !== '') {
$tag_names = array_map('trim', explode(',', $tags));
wp_set_post_tags($post_id, $tag_names);
}
// 设置封面图
$thumbnail_url = sanitize_url((string) ($data['thumbnail'] ?? ''));
if ($thumbnail_url !== '') {
$this->set_thumbnail_from_url($post_id, $thumbnail_url);
}
// 设置专题 (topics taxonomy)
$this->apply_topics_and_filters($post_id, $data);
return [
'success' => true,
'post_id' => $post_id,
'message' => $status === 'draft' ? __('草稿已保存', 'aksrt') : __('投稿已提交,等待审核', 'aksrt'),
];
}
/**
* 更新文章
*/
public function update(int $post_id, array $data): array {
$user_id = get_current_user_id();
if ($user_id <= 0) {
return ['success' => false, 'message' => __('请先登录', 'aksrt')];
return !empty($settings['guest_enabled']) && (int) ($settings['default_user_id'] ?? 0) > 0;
}
return $this->user_can_cap('new_post_add', $user_id);
}
public function can_edit_post(int $post_id, ?int $user_id = null): bool {
if ($post_id <= 0) {
return false;
}
if ($user_id === null) {
$user_id = get_current_user_id();
}
if ($user_id <= 0) {
return false;
}
if (user_can($user_id, 'manage_options') || user_can($user_id, 'edit_others_posts')) {
return true;
}
$post = get_post($post_id);
if (!$post || (int) $post->post_author !== $user_id) {
return ['success' => false, 'message' => __('没有编辑权限', 'aksrt')];
if (!$post || $post->post_type !== 'post') {
return false;
}
if ((int) $post->post_author !== $user_id) {
return false;
}
$update_data = ['ID' => $post_id];
if (isset($data['title'])) {
$update_data['post_title'] = sanitize_text_field($data['title']);
}
if (isset($data['content'])) {
$update_data['post_content'] = wp_kses_post($data['content']);
}
if (isset($data['status'])) {
$update_data['post_status'] = sanitize_key($data['status']);
// Own drafts always editable when user can add posts.
if ($post->post_status === 'draft' && $this->user_can_cap('new_post_add', $user_id)) {
return true;
}
$result = wp_update_post($update_data, true);
return $this->user_can_cap('new_post_edit', $user_id, $post_id);
}
if (is_wp_error($result)) {
return ['success' => false, 'message' => $result->get_error_message()];
public function can_delete_post(int $post_id, ?int $user_id = null): bool {
if ($user_id === null) {
$user_id = get_current_user_id();
}
// Update category
$category = (int) ($data['category'] ?? 0);
if ($category > 0) {
wp_set_post_categories($post_id, [$category]);
if ($user_id <= 0) {
return false;
}
// Update tags
if (isset($data['tags'])) {
$tags = sanitize_text_field((string) $data['tags']);
if ($tags !== '') {
$tag_names = array_map('trim', explode(',', $tags));
wp_set_post_tags($post_id, $tag_names);
} else {
wp_set_post_tags($post_id, []);
}
if (user_can($user_id, 'manage_options')) {
return true;
}
// Update thumbnail
if (isset($data['thumbnail'])) {
$thumbnail_url = sanitize_url((string) $data['thumbnail']);
if ($thumbnail_url !== '') {
$this->set_thumbnail_from_url($post_id, $thumbnail_url);
}
$post = get_post($post_id);
if (!$post || $post->post_type !== 'post') {
return false;
}
if ((int) $post->post_author !== $user_id) {
return false;
}
if ($post->post_status === 'draft' && $this->user_can_cap('new_post_add', $user_id)) {
return true;
}
return $this->user_can_cap('new_post_delete', $user_id, $post_id);
}
// Update topics + custom filter meta
$this->apply_topics_and_filters($post_id, $data);
return ['success' => true, 'post_id' => $post_id, 'message' => __('已更新', 'aksrt')];
public function user_can_cap(string $cap, int $user_id = 0, ?int $context = null): bool {
if ($user_id > 0 && user_can($user_id, 'manage_options')) {
return true;
}
if (function_exists('aksrt_user_can')) {
return aksrt_user_can($cap, $user_id, $context);
}
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'user_can')) {
return \AKSRT\Settings\Settings::user_can($cap, $user_id, $context);
}
return $user_id > 0;
}
/**
* 从 URL 设置封面图
*
* Downloads the image from the given URL, sideloads it into the media library,
* and sets it as the post's featured image.
*
* @param int $post_id Post ID.
* @param string $url Image URL.
* @param array<string, mixed> $data
* @return array{success:bool,message:string,post_id?:int}
*/
public function submit(array $data): array {
$action = sanitize_key((string) ($data['action_type'] ?? $data['submit_action'] ?? 'posts_save'));
if ($action === 'aksrt_save_draft' || ($data['status'] ?? '') === 'draft') {
$action = 'posts_draft';
} else {
$action = 'posts_save';
}
return $this->persist($data, $action);
}
/**
* @param array<string, mixed> $data
* @return array{success:bool,message:string,post_id?:int}
*/
public function update(int $post_id, array $data): array {
$data['posts_id'] = $post_id;
$action = (($data['status'] ?? '') === 'draft') ? 'posts_draft' : 'posts_save';
return $this->persist($data, $action);
}
public function ajax_submit_post(): void {
$this->verify_nonce();
$this->maybe_verify_captcha();
$data = wp_unslash($_POST);
$result = $this->persist(is_array($data) ? $data : [], 'posts_save');
$this->send_result($result);
}
public function ajax_save_draft(): void {
$this->verify_nonce();
if (!is_user_logged_in()) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$data = wp_unslash($_POST);
$result = $this->persist(is_array($data) ? $data : [], 'posts_draft');
$this->send_result($result);
}
public function ajax_update_post(): void {
$this->verify_nonce();
$data = wp_unslash($_POST);
if (!is_array($data)) {
$data = [];
}
$post_id = absint($data['posts_id'] ?? $data['post_id'] ?? $data['edit_id'] ?? 0);
if ($post_id <= 0) {
wp_send_json_error(['message' => __('文章 ID 无效', 'aksrt')]);
}
$data['posts_id'] = $post_id;
$action = (($data['status'] ?? '') === 'draft') ? 'posts_draft' : 'posts_save';
$result = $this->persist($data, $action);
$this->send_result($result);
}
public function ajax_delete_draft(): void {
$this->verify_nonce();
$user_id = get_current_user_id();
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
$post_id = absint($_POST['draft_id'] ?? $_POST['post_id'] ?? $_POST['id'] ?? 0);
if ($post_id <= 0) {
wp_send_json_error(['message' => __('草稿 ID 无效', 'aksrt')]);
}
$post = get_post($post_id);
if (!$post || (int) $post->post_author !== $user_id) {
wp_send_json_error(['message' => __('没有删除权限', 'aksrt')]);
}
if ($post->post_status !== 'draft' && !$this->can_delete_post($post_id, $user_id)) {
wp_send_json_error(['message' => __('只能删除草稿', 'aksrt')]);
}
if ($post->post_status === 'draft') {
$ok = wp_delete_post($post_id, true);
} else {
$ok = wp_trash_post($post_id);
}
if ($ok) {
wp_send_json_success(['message' => __('已删除', 'aksrt')]);
}
wp_send_json_error(['message' => __('删除失败', 'aksrt')]);
}
public function ajax_delete_post(): void {
$this->verify_nonce();
$user_id = get_current_user_id();
$post_id = absint($_POST['id'] ?? $_POST['post_id'] ?? 0);
if (!$this->can_delete_post($post_id, $user_id)) {
wp_send_json_error(['message' => __('您没有删除此内容的权限', 'aksrt')]);
}
$post = get_post($post_id);
if (!$post || $post->post_status === 'trash') {
wp_send_json_success(['message' => __('文章已经删除', 'aksrt'), 'reload' => true]);
}
wp_trash_post($post_id);
wp_send_json_success([
'message' => __('文章已经删除', 'aksrt'),
'reload' => true,
'goto' => home_url('/'),
]);
}
private function verify_nonce(): void {
$nonce = (string) ($_REQUEST['_nonce'] ?? $_REQUEST['nonce'] ?? $_REQUEST['_wpnonce'] ?? '');
if ($nonce === '' || (
!wp_verify_nonce($nonce, 'aksrt_submit')
&& !wp_verify_nonce($nonce, 'aksrt_submit_nonce')
)) {
wp_send_json_error(['message' => __('安全验证失败,请刷新页面重试', 'aksrt')], 403);
}
}
private function maybe_verify_captcha(): void {
$settings = $this->settings();
if (empty($settings['captcha'])) {
return;
}
if (!function_exists('aksrt_require_captcha')) {
return;
}
$payload = [];
if (isset($_POST['captcha']) && is_array($_POST['captcha'])) {
$payload = wp_unslash($_POST['captcha']);
}
$verify = aksrt_require_captcha('submit', is_array($payload) ? $payload : []);
if (is_wp_error($verify)) {
wp_send_json_error(['message' => $verify->get_error_message()]);
}
}
/**
* @param array{success:bool,message?:string} $result
*/
private function send_result(array $result): void {
if (!empty($result['success'])) {
wp_send_json_success($result);
}
wp_send_json_error($result);
}
/**
* Downloads image URL into media library as featured image.
*/
private function set_thumbnail_from_url(int $post_id, string $url): void {
if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) {
return;
}
// Require media handling functions
if (!function_exists('media_sideload_image')) {
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
}
// Check if URL is an image
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION));
$allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
if ($ext !== '' && !in_array($ext, $allowed_exts, true)) {
$ext = strtolower(pathinfo((string) (parse_url($url, PHP_URL_PATH) ?? ''), PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
if ($ext !== '' && !in_array($ext, $allowed, true)) {
return;
}
// Sideload the image
$attachment_id = media_sideload_image($url, $post_id, '', 'id');
if (!is_wp_error($attachment_id)) {
set_post_thumbnail($post_id, $attachment_id);
} elseif (defined('WP_DEBUG') && WP_DEBUG) {
error_log(sprintf('[AKSRT Front Submit] Failed to set thumbnail for post %d: %s — %s', $post_id, $url, $attachment_id->get_error_message()));
set_post_thumbnail($post_id, (int) $attachment_id);
}
}
/**
* AJAX 提交投稿
* @param string[] $classes
* @return string[]
*/
public function ajax_submit_post(): void {
check_ajax_referer('aksrt_submit', '_nonce');
$data = $_POST;
$result = $this->submit($data);
if ($result['success']) {
wp_send_json_success($result);
public function body_class(array $classes): array {
if ($this->is_submit_template()) {
$classes[] = 'aksrt-page-submit';
$classes[] = 'aksrt-hide-float';
$classes[] = 'aksrt-hide-tabbar';
}
wp_send_json_error($result);
return $classes;
}
/**
* AJAX 保存草稿
* @param array<string,bool> $robots
* @return array<string,bool>
*/
public function ajax_save_draft(): void {
check_ajax_referer('aksrt_submit', '_nonce');
$_POST['status'] = 'draft';
$data = $_POST;
$result = $this->submit($data);
if ($result['success']) {
wp_send_json_success($result);
public function robots_noindex(array $robots): array {
if ($this->is_submit_template()) {
$robots['noindex'] = true;
$robots['nofollow'] = true;
}
wp_send_json_error($result);
return $robots;
}
/**
* AJAX 更新文章
* @param mixed $enabled
* @return mixed
*/
public function ajax_update_post(): void {
check_ajax_referer('aksrt_submit', '_nonce');
$post_id = (int) ($_POST['post_id'] ?? 0);
if ($post_id <= 0) {
wp_send_json_error(['message' => __('文章 ID 无效', 'aksrt')]);
public function disable_chrome_on_submit($enabled) {
if ($this->is_submit_template()) {
return false;
}
$result = $this->update($post_id, $_POST);
if ($result['success']) {
wp_send_json_success($result);
}
wp_send_json_error($result);
return $enabled;
}
/**
* AJAX handler: delete draft
*/
public function ajax_delete_draft(): void {
check_ajax_referer('aksrt_submit', '_nonce');
$user_id = get_current_user_id();
if ($user_id <= 0) {
wp_send_json_error(['message' => __('请先登录', 'aksrt')]);
}
// Fix: JS sends 'draft_id', not 'post_id'
$post_id = (int) (($_POST['draft_id'] ?? $_POST['post_id']) ?? 0);
if ($post_id <= 0) {
wp_send_json_error(['message' => __('草稿 ID 无效', 'aksrt')]);
}
$post = get_post($post_id);
if (!$post || (int) $post->post_author !== $user_id) {
wp_send_json_error(['message' => __('没有删除权限', 'aksrt')]);
}
if ($post->post_status !== 'draft') {
wp_send_json_error(['message' => __('只能删除草稿', 'aksrt')]);
}
$result = wp_delete_post($post_id, true);
if ($result) {
wp_send_json_success(['message' => __('草稿已删除', 'aksrt')]);
}
wp_send_json_error(['message' => __('删除失败', 'aksrt')]);
}
/**
* Apply topics taxonomy terms and custom filter meta values to a post.
*
* Reads 'topics' array and 'cf_{dim_key}' scalar values from $data.
* Custom filter keys are validated against the configured dimension definitions.
*
* @param int $post_id Post ID.
* @param array $data Submission data ($_POST).
*/
private function apply_topics_and_filters(int $post_id, array $data): void {
// Topics taxonomy
if (isset($data['topics']) && is_array($data['topics']) && taxonomy_exists('topics')) {
$topic_ids = array_map('absint', $data['topics']);
$topic_ids = array_filter($topic_ids);
wp_set_object_terms($post_id, array_values($topic_ids), 'topics');
}
// Custom filter meta values (_aksrt_cf_{dim_key})
foreach ($data as $key => $val) {
if (strpos($key, 'cf_') !== 0) {
continue;
}
$dim_key = sanitize_key(substr($key, 3));
if ($dim_key === '') {
continue;
}
$cf_val = sanitize_key((string) $val);
$meta_key = '_aksrt_cf_' . $dim_key;
if ($cf_val !== '') {
update_post_meta($post_id, $meta_key, $cf_val);
} else {
delete_post_meta($post_id, $meta_key);
}
}
public function is_submit_template(): bool {
return is_page_template('templates/pages/page-submit.php');
}
}
+157
View File
@@ -0,0 +1,157 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
/**
* Featured cover (image / slide / video) for front-end submit.
*/
trait FrontSubmitFeatured {
/**
* @param array<string, mixed> $data
*/
private function save_featured_meta(int $post_id, array $data, int $user_id): void {
if (empty($data['featured_data']) && empty($data['featured_type'])) {
return;
}
$featured = $data['featured_data'] ?? null;
if (is_string($featured)) {
$decoded = json_decode(wp_unslash($featured), true);
$featured = is_array($decoded) ? $decoded : null;
}
if (!is_array($featured)) {
// Flat payload: featured_type + fields.
$featured = [
'type' => sanitize_key((string) ($data['featured_type'] ?? '')),
'data' => $data['featured_payload'] ?? $data,
];
}
$type = sanitize_key((string) ($featured['type'] ?? ''));
$payload = is_array($featured['data'] ?? null) ? $featured['data'] : $featured;
$post_meta = get_post_meta($post_id, DomainModel::POST_META_KEY, true);
if (!is_array($post_meta)) {
$post_meta = DomainModel::default_post_meta();
}
switch ($type) {
case 'video':
if (!$this->user_can_cap('new_post_video_cover', $user_id) && !$this->user_can_cap('new_post_image_cover', $user_id)) {
return;
}
$video = esc_url_raw((string) ($payload['url'] ?? $payload['video'] ?? ''));
$pic = esc_url_raw((string) ($payload['pic'] ?? $payload['poster'] ?? $payload['cover'] ?? ''));
$post_meta['featured_video'] = $video;
$post_meta['featured_slide'] = '';
if ($pic !== '') {
$post_meta['thumbnail_url'] = $pic;
$post_meta['cover_images'] = $pic;
$this->maybe_set_thumbnail_url($post_id, $pic);
}
break;
case 'slide':
if (!$this->user_can_cap('new_post_slide_cover', $user_id) && !$this->user_can_cap('new_post_image_cover', $user_id)) {
return;
}
$ids = $payload['ids'] ?? $payload['slides'] ?? $payload['images'] ?? [];
$urls = [];
if (is_string($ids)) {
$ids = array_filter(array_map('trim', explode(',', $ids)));
}
if (is_array($ids)) {
foreach ($ids as $item) {
if (is_array($item)) {
$u = esc_url_raw((string) ($item['url'] ?? ''));
if ($u !== '') {
$urls[] = $u;
} elseif (!empty($item['id'])) {
$src = wp_get_attachment_image_url((int) $item['id'], 'full');
if ($src) {
$urls[] = $src;
}
}
} elseif (is_numeric($item)) {
$src = wp_get_attachment_image_url((int) $item, 'full');
if ($src) {
$urls[] = $src;
}
} else {
$u = esc_url_raw((string) $item);
if ($u !== '') {
$urls[] = $u;
}
}
}
}
$post_meta['featured_slide'] = implode(',', $urls);
$post_meta['featured_video'] = '';
if (!empty($urls[0])) {
$post_meta['thumbnail_url'] = $urls[0];
$post_meta['cover_images'] = $urls[0];
$this->maybe_set_thumbnail_url($post_id, $urls[0]);
}
break;
case 'image':
if (!$this->user_can_cap('new_post_image_cover', $user_id)) {
return;
}
$url = esc_url_raw((string) ($payload['url'] ?? $payload['image'] ?? ''));
$id = absint($payload['id'] ?? 0);
if ($url === '' && $id > 0) {
$src = wp_get_attachment_image_url($id, 'full');
if ($src) {
$url = $src;
}
}
$post_meta['featured_video'] = '';
$post_meta['featured_slide'] = '';
if ($url !== '') {
$post_meta['thumbnail_url'] = $url;
$post_meta['cover_images'] = $url;
if ($id > 0) {
set_post_thumbnail($post_id, $id);
} else {
$this->maybe_set_thumbnail_url($post_id, $url);
}
}
break;
case 'close':
case 'none':
case 'clear':
$post_meta['featured_video'] = '';
$post_meta['featured_slide'] = '';
$post_meta['thumbnail_url'] = '';
$post_meta['cover_images'] = '';
delete_post_thumbnail($post_id);
break;
default:
return;
}
update_post_meta($post_id, DomainModel::POST_META_KEY, $post_meta);
}
private function maybe_set_thumbnail_url(int $post_id, string $url): void {
if ($url === '') {
return;
}
// Prefer attachment if URL is local media.
$attach_id = attachment_url_to_postid($url);
if ($attach_id > 0) {
set_post_thumbnail($post_id, $attach_id);
return;
}
$this->set_thumbnail_from_url($post_id, $url);
}
}
+197
View File
@@ -0,0 +1,197 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
/**
* Front-end pay meta save for article submit.
*/
trait FrontSubmitPay {
/**
* @param array<string, mixed> $data
* @param array<string, mixed> $settings
* @return array{success:bool,message?:string}
*/
private function save_pay_meta(int $post_id, array $data, int $user_id, array $settings): array {
if (!$this->user_can_cap('new_post_pay', $user_id)) {
return ['success' => true];
}
// Only touch pay meta when the form sent pay fields.
$has_pay_payload = isset($data['aksrt_pay']) || isset($data['pay_enabled']) || isset($data['zibpay_s']) || isset($data['pay_type']);
if (!$has_pay_payload) {
return ['success' => true];
}
$raw = is_array($data['aksrt_pay'] ?? null) ? $data['aksrt_pay'] : [];
$enabled = !empty($data['pay_enabled']) || !empty($data['zibpay_s']);
$existing = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post_id) : DomainModel::default_pay_meta();
$meta = is_array($existing) ? $existing : DomainModel::default_pay_meta();
if (!$enabled) {
$meta['pay_type'] = DomainModel::PAY_TYPE_NO;
$this->write_pay_meta($post_id, $meta);
return ['success' => true];
}
$pay_modo = sanitize_key((string) ($raw['pay_modo'] ?? $data['pay_modo'] ?? '0'));
$pay_modo = $pay_modo === 'points' ? 'points' : '0';
$pay_type = sanitize_key((string) ($raw['pay_type'] ?? $data['pay_type'] ?? DomainModel::PAY_TYPE_READING));
if ($pay_type === '1' || $pay_type === 'reading' || $pay_type === 'payshow') {
$pay_type = DomainModel::PAY_TYPE_READING;
} elseif ($pay_type === '2' || $pay_type === 'download') {
$pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
} else {
$pay_type = DomainModel::normalize_pay_type($pay_type);
if ($pay_type === DomainModel::PAY_TYPE_NO) {
$pay_type = DomainModel::PAY_TYPE_READING;
}
}
$downloads = $this->normalize_front_downloads($raw['downloads'] ?? $data['pay_download'] ?? []);
if (!empty($downloads) && $this->user_can_cap('new_post_pay_download', $user_id)) {
$pay_type = DomainModel::PAY_TYPE_DOWNLOAD;
$meta['downloads'] = $downloads;
} elseif ($pay_type === DomainModel::PAY_TYPE_DOWNLOAD && !$this->user_can_cap('new_post_pay_download', $user_id)) {
$pay_type = DomainModel::PAY_TYPE_READING;
$meta['downloads'] = [];
}
$meta['pay_type'] = $pay_type;
$meta['pay_modo'] = $pay_modo;
$min = (float) ($settings['pay_price_min'] ?? 0);
$max = (float) ($settings['pay_price_max'] ?? 0);
$allow_free = !empty($settings['pay_free_s']);
$vip_manual = !empty($settings['pay_vip_price_s']);
$vip1_disc = (int) ($settings['pay_vip_1_discount'] ?? 100);
$vip2_disc = (int) ($settings['pay_vip_2_discount'] ?? 100);
if ($pay_modo === 'points') {
$points = isset($raw['points_price']) ? (int) $raw['points_price'] : (int) ($data['points_price'] ?? 0);
if ($points < 0) {
return ['success' => false, 'message' => __('请设置正确的积分价格', 'aksrt')];
}
if (!$allow_free && $points <= 0) {
return ['success' => false, 'message' => __('请设置付费内容的积分价格', 'aksrt')];
}
if ($min > 0 && $points < $min) {
return ['success' => false, 'message' => sprintf(__('积分价格不能低于:%s', 'aksrt'), (string) $min)];
}
if ($max > 0 && $points > $max) {
return ['success' => false, 'message' => sprintf(__('积分价格不能高于:%s', 'aksrt'), (string) $max)];
}
if ($vip_manual) {
$vip1 = isset($raw['vip1_points']) ? (int) $raw['vip1_points'] : (int) ($raw['vip_1_points'] ?? 0);
$vip2 = isset($raw['vip2_points']) ? (int) $raw['vip2_points'] : (int) ($raw['vip_2_points'] ?? 0);
} else {
$vip1 = (int) floor($points * $vip1_disc / 100);
$vip2 = (int) floor($points * $vip2_disc / 100);
}
$vip1 = max(0, min($points, $vip1));
$vip2 = max(0, min($vip1, $vip2));
$meta['points_price'] = $points;
$meta['price'] = 0.0;
$meta['vip1_points'] = $vip1;
$meta['vip2_points'] = $vip2;
$meta['vip1_price'] = 0.0;
$meta['vip2_price'] = 0.0;
} else {
$price = isset($raw['price']) ? (float) $raw['price'] : (float) ($raw['pay_price'] ?? $data['pay_price'] ?? 0);
$price = round($price, 2);
if ($price < 0) {
return ['success' => false, 'message' => __('请设置正确的价格', 'aksrt')];
}
if (!$allow_free && $price <= 0) {
return ['success' => false, 'message' => __('请设置付费内容的价格', 'aksrt')];
}
if ($min > 0 && $price < $min) {
return ['success' => false, 'message' => sprintf(__('价格不能低于:%s', 'aksrt'), (string) $min)];
}
if ($max > 0 && $price > $max) {
return ['success' => false, 'message' => sprintf(__('价格不能高于:%s', 'aksrt'), (string) $max)];
}
if ($vip_manual) {
$vip1 = isset($raw['vip1_price']) ? (float) $raw['vip1_price'] : (float) ($raw['vip_1_price'] ?? 0);
$vip2 = isset($raw['vip2_price']) ? (float) $raw['vip2_price'] : (float) ($raw['vip_2_price'] ?? 0);
$vip1 = round($vip1, 2);
$vip2 = round($vip2, 2);
if ($vip1 > $price) {
return ['success' => false, 'message' => __('会员价不能高于普通价', 'aksrt')];
}
if ($vip2 > $vip1) {
return ['success' => false, 'message' => __('更高等级会员价不能高于较低等级会员价', 'aksrt')];
}
} else {
$vip1 = round($price * $vip1_disc / 100, 2);
$vip2 = round($price * $vip2_disc / 100, 2);
}
$vip1 = max(0, min($price, $vip1));
$vip2 = max(0, min($vip1, $vip2));
$meta['price'] = $price;
$meta['points_price'] = 0;
$meta['vip1_price'] = $vip1;
$meta['vip2_price'] = $vip2;
$meta['vip1_points'] = 0;
$meta['vip2_points'] = 0;
}
$this->write_pay_meta($post_id, $meta);
return ['success' => true];
}
/**
* @param mixed $raw
* @return array<int, array<string, string>>
*/
private function normalize_front_downloads($raw): array {
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
// Accept zibll-ish keys + AKSRT keys.
$url = (string) ($row['url'] ?? $row['link'] ?? '');
$url = esc_url_raw(trim($url));
if ($url === '') {
continue;
}
$out[] = [
'label' => sanitize_text_field((string) ($row['label'] ?? $row['name'] ?? '')),
'url' => $url,
'type' => 'external',
'extract_code' => sanitize_text_field((string) ($row['extract_code'] ?? $row['copy_val'] ?? $row['more'] ?? '')),
];
}
return DomainModel::sanitize_downloads($out);
}
/**
* @param array<string, mixed> $meta
*/
private function write_pay_meta(int $post_id, array $meta): void {
$meta = DomainModel::sanitize_pay_meta($meta);
update_post_meta($post_id, DomainModel::PAY_META_KEY, $meta);
update_post_meta($post_id, DomainModel::PAY_TYPE_KEY, (string) ($meta['pay_type'] ?? DomainModel::PAY_TYPE_NO));
update_post_meta($post_id, DomainModel::PAY_PRICE_KEY, (float) ($meta['price'] ?? 0));
update_post_meta(
$post_id,
DomainModel::RESOURCE_MARKER_KEY,
DomainModel::is_resource_pay_type((string) ($meta['pay_type'] ?? 'no')) ? 1 : 0
);
}
}
+354
View File
@@ -0,0 +1,354 @@
<?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 (empty($settings['guest_enabled']) || (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')];
}
}
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($settings['allow_category']) && empty($categories)) {
return ['success' => false, 'message' => __('请选择文章分类', 'aksrt')];
}
if (!empty($settings['cover_required']) && empty($data['featured_data']) && empty($data['thumbnail'])) {
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) && !empty($settings['allow_category'])) {
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, $settings);
}
$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 (!empty($settings['allow_category']) && 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_topics_and_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);
// Legacy thumbnail URL fallback.
$thumbnail_url = esc_url_raw((string) ($data['thumbnail'] ?? ''));
if ($thumbnail_url !== '' && empty($data['featured_data'])) {
$this->set_thumbnail_from_url((int) $in_id, $thumbnail_url);
}
$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;
}
/**
* @param array<string, mixed> $settings
*/
private function resolve_publish_status(int $user_id, array $settings): string {
$mode = (string) ($settings['review_mode'] ?? 'pending');
if ($mode === 'draft') {
return 'draft';
}
if ($mode === 'publish') {
return 'publish';
}
// pending mode: skip audit when capability grants it.
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_topics_and_filters(int $post_id, array $data, array $settings): void {
if (!empty($settings['topics_s']) && taxonomy_exists('topics')) {
$topics = $data['topics'] ?? [];
if (!is_array($topics)) {
$topics = $topics === '' || $topics === null ? [] : [$topics];
}
$topic_ids = array_values(array_filter(array_map('absint', $topics)));
wp_set_object_terms($post_id, $topic_ids, 'topics');
}
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;
}
}
+2
View File
@@ -42,6 +42,8 @@ class Templates {
'templates/pages/page-archives.php' => __('AKSRT - 文章归档', 'aksrt'),
'templates/pages/page-categories.php' => __('AKSRT - 分类汇总', 'aksrt'),
'templates/pages/page-newposts.php' => __('AKSRT - 最新文章', 'aksrt'),
// 注意:page-newposts = 最新文章列表;投稿页是 page-submit(写文章/投稿)
'templates/pages/page-submit.php' => __('AKSRT - 写文章/投稿', 'aksrt'),
'templates/pages/page-posts-navs.php' => __('AKSRT - 文章导航', 'aksrt'),
'templates/pages/page-friends.php' => __('AKSRT - 友情链接', 'aksrt'),
'templates/pages/page-orders.php' => __('AKSRT - 订单中心', 'aksrt'),
+36 -5
View File
@@ -378,18 +378,49 @@ class PostDisplay {
public static function get_submit_settings(): array {
$settings = Settings::get_all();
$title_min = max(1, min(100, (int) ($settings['post_article_title_strlen_limit_min'] ?? 5)));
$title_max = max(5, min(200, (int) ($settings['post_article_title_strlen_limit_max'] ?? 30)));
if ($title_max < $title_min) {
$title_max = $title_min;
}
$review_mode = sanitize_key((string) ($settings['front_submit_review_mode'] ?? 'pending'));
if (!in_array($review_mode, ['pending', 'publish', 'draft'], true)) {
$review_mode = 'pending';
}
$editor_mode = sanitize_key((string) ($settings['front_submit_editor_mode'] ?? 'rich'));
if (!in_array($editor_mode, ['rich', 'markdown', 'plain'], true)) {
$editor_mode = 'rich';
}
return [
'enabled' => !isset($settings['front_submit_enabled']) || !empty($settings['front_submit_enabled']),
'btn_text' => sanitize_text_field((string) ($settings['post_article_btn_txte'] ?? '')) ?: __('发布文章', 'aksrt'),
'title_min' => max(1, min(100, (int) ($settings['post_article_title_strlen_limit_min'] ?? 5))),
'title_max' => max(5, min(200, (int) ($settings['post_article_title_strlen_limit_max'] ?? 30))),
'title_min' => $title_min,
'title_max' => $title_max,
'title_placeholder' => sanitize_text_field((string) ($settings['post_t_placeholder'] ?? '')) ?: __('请输入标题', 'aksrt'),
'content_placeholder' => sanitize_textarea_field((string) ($settings['post_c_placeholder'] ?? '')) ?: __('请输入内容', 'aksrt'),
'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_map('intval', $settings['post_article_cat']) : [],
'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_values(array_filter(array_map('intval', $settings['post_article_cat']))) : [],
'default_user_id' => max(0, (int) ($settings['post_article_user'] ?? 0)),
'guest_enabled' => !empty($settings['front_submit_guest_enabled']),
'allow_title' => !isset($settings['front_submit_title']) || !empty($settings['front_submit_title']),
'allow_content' => !isset($settings['front_submit_content']) || !empty($settings['front_submit_content']),
'allow_excerpt' => !isset($settings['front_submit_excerpt']) || !empty($settings['front_submit_excerpt']),
'allow_cover' => !isset($settings['front_submit_cover']) || !empty($settings['front_submit_cover']),
'allow_category' => !isset($settings['front_submit_category']) || !empty($settings['front_submit_category']),
'allow_tags' => !isset($settings['front_submit_tags']) || !empty($settings['front_submit_tags']),
'tags_s' => !isset($settings['front_submit_tags']) || !empty($settings['front_submit_tags']),
'cover_required' => !empty($settings['front_submit_cover_required']),
'max_images' => max(0, min(50, (int) ($settings['front_submit_max_images'] ?? 5))),
'review_mode' => $review_mode,
'editor_mode' => $editor_mode,
'draft_save' => !isset($settings['front_submit_draft_save']) || !empty($settings['front_submit_draft_save']),
'leave_warning' => !isset($settings['front_submit_leave_warning']) || !empty($settings['front_submit_leave_warning']),
'captcha' => !empty($settings['front_submit_captcha']) || !empty($settings['verification_newposts_s']),
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
'pay_price_min' => max(0, (int) ($settings['post_article_pay_price_min'] ?? 0)),
'pay_price_max' => max(0, (int) ($settings['post_article_pay_price_max'] ?? 100)),
'pay_price_min' => max(0, (float) ($settings['post_article_pay_price_min'] ?? 0)),
'pay_price_max' => max(0, (float) ($settings['post_article_pay_price_max'] ?? 100)),
'pay_vip_price_s' => !isset($settings['post_article_pay_vip_price_s']) || !empty($settings['post_article_pay_vip_price_s']),
'pay_vip_1_discount' => max(0, min(100, (int) ($settings['post_article_pay_vip_1_discount'] ?? 80))),
'pay_vip_2_discount' => max(0, min(100, (int) ($settings['post_article_pay_vip_2_discount'] ?? 50))),