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:
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
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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))),
|
||||
|
||||
Reference in new issue
Block a user