refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('wp_ajax_aksrt_submit_post', [$this, 'ajax_submit_post']);
|
||||
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']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
$settings = $this->settings();
|
||||
if (empty($settings['enabled'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user_id === null) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
if ($user_id <= 0) {
|
||||
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 || $post->post_type !== 'post') {
|
||||
return false;
|
||||
}
|
||||
if ((int) $post->post_author !== $user_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
return $this->user_can_cap('new_post_edit', $user_id, $post_id);
|
||||
}
|
||||
|
||||
public function can_delete_post(int $post_id, ?int $user_id = null): bool {
|
||||
if ($user_id === null) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (user_can($user_id, 'manage_options')) {
|
||||
return true;
|
||||
}
|
||||
$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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
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';
|
||||
}
|
||||
$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;
|
||||
}
|
||||
$attachment_id = media_sideload_image($url, $post_id, '', 'id');
|
||||
if (!is_wp_error($attachment_id)) {
|
||||
set_post_thumbnail($post_id, (int) $attachment_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $classes
|
||||
* @return string[]
|
||||
*/
|
||||
public function body_class(array $classes): array {
|
||||
if ($this->is_submit_template()) {
|
||||
$classes[] = 'aksrt-page-submit';
|
||||
$classes[] = 'aksrt-hide-float';
|
||||
$classes[] = 'aksrt-hide-tabbar';
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,bool> $robots
|
||||
* @return array<string,bool>
|
||||
*/
|
||||
public function robots_noindex(array $robots): array {
|
||||
if ($this->is_submit_template()) {
|
||||
$robots['noindex'] = true;
|
||||
$robots['nofollow'] = true;
|
||||
}
|
||||
return $robots;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $enabled
|
||||
* @return mixed
|
||||
*/
|
||||
public function disable_chrome_on_submit($enabled) {
|
||||
if ($this->is_submit_template()) {
|
||||
return false;
|
||||
}
|
||||
return $enabled;
|
||||
}
|
||||
|
||||
public function is_submit_template(): bool {
|
||||
return is_page_template('templates/pages/page-submit.php');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user