导入 AKSRT 主题开发源码
This commit is contained in:
1 parent
9681dd3d48
commit
1268a55dd0
595 files changed
+127705
No files matched your search
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — AI Controller
|
||||
*
|
||||
* Owns REST routes for the "AI 子系统" domain.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_AI_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// AI status
|
||||
register_rest_route(self::API_NAMESPACE, '/ai/status', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// AI settings update
|
||||
register_rest_route(self::API_NAMESPACE, '/ai/settings', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// AI SEO suggest
|
||||
register_rest_route(self::API_NAMESPACE, '/ai/seo-suggest', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'seo_suggest'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// AI summary suggest
|
||||
register_rest_route(self::API_NAMESPACE, '/ai/summary-suggest', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'summary_suggest'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// AI tags suggest
|
||||
register_rest_route(self::API_NAMESPACE, '/ai/tags-suggest', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'tags_suggest'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /ai/status — AI 状态
|
||||
*/
|
||||
public function get_status(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_AI')) {
|
||||
return $this->success(['data' => [
|
||||
'enabled' => false,
|
||||
'provider' => 'none',
|
||||
'configured' => false,
|
||||
'available' => false,
|
||||
]]);
|
||||
}
|
||||
|
||||
return $this->success(['data' => AKSRT_AI::get_instance()->get_status()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ai/settings — 更新 AI 设置
|
||||
*/
|
||||
public function update_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_AI')) {
|
||||
return $this->error('AI 模块未加载', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
|
||||
$allowed = [
|
||||
'enabled' => 'bool',
|
||||
'provider' => 'string',
|
||||
'api_key' => 'string',
|
||||
'api_base' => 'url',
|
||||
'model' => 'string',
|
||||
'max_tokens' => 'int',
|
||||
'rate_limit' => 'int',
|
||||
];
|
||||
|
||||
$config = [];
|
||||
foreach ($allowed as $key => $type) {
|
||||
if (!isset($params[$key])) continue;
|
||||
switch ($type) {
|
||||
case 'bool':
|
||||
$config[$key] = !empty($params[$key]);
|
||||
break;
|
||||
case 'int':
|
||||
$config[$key] = (int) $params[$key];
|
||||
break;
|
||||
case 'url':
|
||||
$config[$key] = esc_url_raw((string) $params[$key]);
|
||||
break;
|
||||
default:
|
||||
$config[$key] = sanitize_text_field((string) $params[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$success = AKSRT_AI::get_instance()->update_config($config);
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('AI 设置已保存', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('保存失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ai/seo-suggest — SEO 建议
|
||||
*/
|
||||
public function seo_suggest(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_AI_SEO')) {
|
||||
return $this->error('AI SEO 模块未加载', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$title = sanitize_text_field((string) ($params['title'] ?? ''));
|
||||
$content = wp_kses_post((string) ($params['content'] ?? ''));
|
||||
|
||||
if (empty($title)) {
|
||||
return $this->error('标题不能为空');
|
||||
}
|
||||
|
||||
$result = AKSRT_AI_SEO::get_instance()->suggest($title, $content);
|
||||
|
||||
if ($result['success']) {
|
||||
return $this->success(['data' => $result['data']]);
|
||||
}
|
||||
|
||||
return $this->error($result['message'] ?? '生成失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ai/summary-suggest — 摘要建议
|
||||
*/
|
||||
public function summary_suggest(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_AI') || !class_exists('AKSRT_AI_Prompt')) {
|
||||
return $this->error('AI 模块未加载', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$title = sanitize_text_field((string) ($params['title'] ?? ''));
|
||||
$content = wp_kses_post((string) ($params['content'] ?? ''));
|
||||
$max_len = max(50, min(500, (int) ($params['max_length'] ?? 200)));
|
||||
|
||||
if (empty($content)) {
|
||||
return $this->error('内容不能为空');
|
||||
}
|
||||
|
||||
$prompt = AKSRT_AI_Prompt::get_instance()->build_summary_prompt($content, $max_len);
|
||||
$result = AKSRT_AI::get_instance()->generate($prompt);
|
||||
|
||||
if ($result['success']) {
|
||||
return $this->success(['data' => ['summary' => $result['content']]]);
|
||||
}
|
||||
|
||||
return $this->error($result['message'] ?? '生成失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /ai/tags-suggest — 标签建议
|
||||
*/
|
||||
public function tags_suggest(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_AI') || !class_exists('AKSRT_AI_Prompt')) {
|
||||
return $this->error('AI 模块未加载', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$title = sanitize_text_field((string) ($params['title'] ?? ''));
|
||||
$content = wp_kses_post((string) ($params['content'] ?? ''));
|
||||
|
||||
if (empty($title) && empty($content)) {
|
||||
return $this->error('标题或内容不能为空');
|
||||
}
|
||||
|
||||
$prompt = AKSRT_AI_Prompt::get_instance()->build_tags_prompt($title, $content);
|
||||
$result = AKSRT_AI::get_instance()->generate($prompt);
|
||||
|
||||
if ($result['success']) {
|
||||
$tags = json_decode($result['content'], true);
|
||||
if (!is_array($tags)) {
|
||||
$tags = array_filter(array_map('trim', explode(',', $result['content'])));
|
||||
}
|
||||
return $this->success(['data' => ['tags' => $tags]]);
|
||||
}
|
||||
|
||||
return $this->error($result['message'] ?? '生成失败');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin REST Controller — Comments
|
||||
*
|
||||
* Provides REST endpoints for comment management under aksrt/v1/comments.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Comment_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// List comments (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'list_comments'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['default' => 20, 'sanitize_callback' => 'absint'],
|
||||
'post_id' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'status' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'search' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Create comment (admin or frontend)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_comment'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
'args' => [
|
||||
'post_id' => ['required' => true, 'sanitize_callback' => 'absint'],
|
||||
'content' => ['required' => true, 'sanitize_callback' => 'sanitize_textarea_field'],
|
||||
'parent' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Update comment status (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments/(?P<id>\d+)/status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_comment_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'id' => ['required' => true, 'sanitize_callback' => 'absint'],
|
||||
'status' => ['required' => true, 'sanitize_callback' => 'sanitize_text_field'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Edit comment content (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_comment'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'id' => ['required' => true, 'sanitize_callback' => 'absint'],
|
||||
'content' => ['required' => true, 'sanitize_callback' => 'sanitize_textarea_field'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Delete comment (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_comment'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'id' => ['required' => true, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Bulk action (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments/bulk', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'bulk_action'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Frontend: list comments for a post (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/comments/post/(?P<post_id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_post_comments'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'post_id' => ['required' => true, 'sanitize_callback' => 'absint'],
|
||||
'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['default' => 10, 'sanitize_callback' => 'absint'],
|
||||
'order' => ['default' => 'DESC', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /comments — List comments with filtering.
|
||||
*/
|
||||
public function list_comments(WP_REST_Request $request): WP_REST_Response {
|
||||
$args = [
|
||||
'number' => min(100, (int) $request->get_param('per_page')),
|
||||
'offset' => ((int) $request->get_param('page') - 1) * (int) $request->get_param('per_page'),
|
||||
'orderby' => 'comment_date_gmt',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
|
||||
$post_id = (int) $request->get_param('post_id');
|
||||
if ($post_id > 0) {
|
||||
$args['post_id'] = $post_id;
|
||||
}
|
||||
|
||||
$status = $request->get_param('status');
|
||||
if ($status !== '') {
|
||||
$args['status'] = $status;
|
||||
}
|
||||
|
||||
$search = $request->get_param('search');
|
||||
if ($search !== '') {
|
||||
$args['search'] = $search;
|
||||
}
|
||||
|
||||
$query = new WP_Comment_Query($args);
|
||||
|
||||
// Batch prefetch to avoid N+1 queries
|
||||
$user_ids = array_unique(array_filter(array_map(fn($c) => (int) $c->user_id, $query->comments)));
|
||||
$post_ids = array_unique(array_map(fn($c) => (int) $c->comment_post_ID, $query->comments));
|
||||
$users_map = [];
|
||||
$posts_map = [];
|
||||
if (!empty($user_ids)) {
|
||||
$users = get_users(['include' => $user_ids, 'fields' => ['ID', 'display_name']]);
|
||||
foreach ($users as $u) $users_map[$u->ID] = $u;
|
||||
}
|
||||
if (!empty($post_ids)) {
|
||||
global $wpdb;
|
||||
$ids_csv = implode(',', array_map('intval', $post_ids));
|
||||
$titles = $wpdb->get_results("SELECT ID, post_title FROM {$wpdb->posts} WHERE ID IN ({$ids_csv})", OBJECT_K);
|
||||
foreach ($titles as $t) $posts_map[$t->ID] = $t->post_title;
|
||||
}
|
||||
|
||||
$comments = [];
|
||||
foreach ($query->comments as $comment) {
|
||||
$comments[] = $this->format_comment($comment, $users_map, $posts_map);
|
||||
}
|
||||
|
||||
// Total count for pagination
|
||||
$count_args = $args;
|
||||
unset($count_args['number'], $count_args['offset'], $count_args['orderby'], $count_args['order']);
|
||||
$count_args['count'] = true;
|
||||
$total = (int) (new WP_Comment_Query($count_args))->get_comments('count');
|
||||
|
||||
return $this->success([
|
||||
'data' => $comments,
|
||||
'total' => $total,
|
||||
'pages' => ceil($total / (int) $request->get_param('per_page')),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /comments — Create a comment.
|
||||
*/
|
||||
public function create_comment(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$post_id = (int) $request->get_param('post_id');
|
||||
$content = trim((string) $request->get_param('content'));
|
||||
$parent = (int) $request->get_param('parent');
|
||||
|
||||
if (!$content) {
|
||||
return $this->error('content_required', '评论内容不能为空。', 400);
|
||||
}
|
||||
|
||||
$post = get_post($post_id);
|
||||
if (!$post || $post->post_status !== 'publish') {
|
||||
return $this->error('invalid_post', '文章不存在或未发布。', 400);
|
||||
}
|
||||
|
||||
$comment_data = [
|
||||
'comment_post_ID' => $post_id,
|
||||
'comment_content' => $content,
|
||||
'user_id' => $user_id,
|
||||
'comment_parent' => $parent,
|
||||
'comment_approved'=> 1, // Admin-created comments are auto-approved
|
||||
];
|
||||
|
||||
$comment_id = wp_insert_comment($comment_data);
|
||||
if (is_wp_error($comment_id)) {
|
||||
return $this->error('create_failed', $comment_id->get_error_message(), 500);
|
||||
}
|
||||
|
||||
$comment = get_comment($comment_id);
|
||||
return $this->success(['data' => $this->format_comment($comment)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /comments/{id}/status — Approve/unapprove/spam/trash.
|
||||
*/
|
||||
public function update_comment_status(WP_REST_Request $request): WP_REST_Response {
|
||||
$comment_id = (int) $request->get_param('id');
|
||||
$new_status = $request->get_param('status');
|
||||
$comment = get_comment($comment_id);
|
||||
|
||||
if (!$comment) {
|
||||
return $this->error('not_found', '评论不存在。', 404);
|
||||
}
|
||||
|
||||
$valid_statuses = ['approve', 'hold', 'spam', 'trash'];
|
||||
if (!in_array($new_status, $valid_statuses, true)) {
|
||||
return $this->error('invalid_status', '无效的状态值。', 400);
|
||||
}
|
||||
|
||||
$result = wp_set_comment_status($comment_id, $new_status);
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error('update_failed', $result->get_error_message(), 500);
|
||||
}
|
||||
|
||||
// Handle spam/trash
|
||||
if ($new_status === 'spam') {
|
||||
wp_spam_comment($comment_id);
|
||||
} elseif ($new_status === 'trash') {
|
||||
wp_trash_comment($comment_id);
|
||||
}
|
||||
|
||||
$comment = get_comment($comment_id);
|
||||
return $this->success(['data' => $this->format_comment($comment)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /comments/{id} — Edit comment content.
|
||||
*/
|
||||
public function update_comment(WP_REST_Request $request): WP_REST_Response {
|
||||
$comment_id = (int) $request->get_param('id');
|
||||
$content = trim((string) $request->get_param('content'));
|
||||
$comment = get_comment($comment_id);
|
||||
|
||||
if (!$comment) {
|
||||
return $this->error('not_found', '评论不存在。', 404);
|
||||
}
|
||||
|
||||
if (!$content) {
|
||||
return $this->error('content_required', '评论内容不能为空。', 400);
|
||||
}
|
||||
|
||||
$result = wp_update_comment(['comment_ID' => $comment_id, 'comment_content' => $content]);
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error('update_failed', $result->get_error_message(), 500);
|
||||
}
|
||||
|
||||
$comment = get_comment($comment_id);
|
||||
return $this->success(['data' => $this->format_comment($comment)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /comments/{id} — Delete a comment.
|
||||
*/
|
||||
public function delete_comment(WP_REST_Request $request): WP_REST_Response {
|
||||
$comment_id = (int) $request->get_param('id');
|
||||
$comment = get_comment($comment_id);
|
||||
|
||||
if (!$comment) {
|
||||
return $this->error('not_found', '评论不存在。', 404);
|
||||
}
|
||||
|
||||
$result = wp_delete_comment($comment_id, true);
|
||||
if (!$result) {
|
||||
return $this->error('delete_failed', '删除评论失败。', 500);
|
||||
}
|
||||
|
||||
return $this->success(['data' => ['deleted' => true, 'id' => $comment_id]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /comments/bulk — Bulk action on comments.
|
||||
*/
|
||||
public function bulk_action(WP_REST_Request $request): WP_REST_Response {
|
||||
$body = $request->get_json_params();
|
||||
$ids = array_map('absint', $body['ids'] ?? []);
|
||||
$action = sanitize_text_field($body['action'] ?? '');
|
||||
|
||||
if (empty($ids) || !$action) {
|
||||
return $this->error('missing_params', '缺少参数。', 400);
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
$failed = 0;
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$comment = get_comment($id);
|
||||
if (!$comment) { $failed++; continue; }
|
||||
|
||||
switch ($action) {
|
||||
case 'approve':
|
||||
wp_set_comment_status($id, 'approve');
|
||||
$success++;
|
||||
break;
|
||||
case 'hold':
|
||||
wp_set_comment_status($id, 'hold');
|
||||
$success++;
|
||||
break;
|
||||
case 'spam':
|
||||
wp_spam_comment($id);
|
||||
$success++;
|
||||
break;
|
||||
case 'trash':
|
||||
wp_trash_comment($id);
|
||||
$success++;
|
||||
break;
|
||||
case 'delete':
|
||||
wp_delete_comment($id, true);
|
||||
$success++;
|
||||
break;
|
||||
default:
|
||||
$failed++;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success(['data' => ['success' => $success, 'failed' => $failed]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /comments/post/{post_id} — Public: list comments for a post with nesting.
|
||||
*/
|
||||
public function get_post_comments(WP_REST_Request $request): WP_REST_Response {
|
||||
$post_id = (int) $request->get_param('post_id');
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = min(50, max(1, (int) $request->get_param('per_page')));
|
||||
$order = in_array(strtoupper((string) $request->get_param('order')), ['ASC', 'DESC'], true)
|
||||
? strtoupper((string) $request->get_param('order'))
|
||||
: 'DESC';
|
||||
|
||||
$args = [
|
||||
'post_id' => $post_id,
|
||||
'status' => 'approve',
|
||||
'number' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
'orderby' => 'comment_date_gmt',
|
||||
'order' => $order,
|
||||
'hierarchical' => 'threaded',
|
||||
];
|
||||
|
||||
$query = new WP_Comment_Query($args);
|
||||
|
||||
// Batch prefetch users
|
||||
$user_ids = array_unique(array_filter(array_map(fn($c) => (int) $c->user_id, $query->comments)));
|
||||
$users_map = [];
|
||||
if (!empty($user_ids)) {
|
||||
$users = get_users(['include' => $user_ids, 'fields' => ['ID', 'display_name']]);
|
||||
foreach ($users as $u) $users_map[$u->ID] = $u;
|
||||
}
|
||||
|
||||
$comments = [];
|
||||
foreach ($query->comments as $comment) {
|
||||
$comments[] = $this->format_comment_public($comment, $users_map);
|
||||
}
|
||||
|
||||
// Total approved comments for this post
|
||||
$total = (int) get_comments_number($post_id);
|
||||
$total_approved = get_comments([
|
||||
'post_id' => $post_id,
|
||||
'status' => 'approve',
|
||||
'count' => true,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'data' => $comments,
|
||||
'total' => (int) $total_approved,
|
||||
'pages' => ceil((int) $total_approved / $per_page),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format comment for admin views.
|
||||
*/
|
||||
private function format_comment(WP_Comment $comment, array $users_map = [], array $posts_map = []): array {
|
||||
$uid = (int) $comment->user_id;
|
||||
$pid = (int) $comment->comment_post_ID;
|
||||
$user = $users_map[$uid] ?? ($uid ? get_userdata($uid) : null);
|
||||
return [
|
||||
'id' => (int) $comment->comment_ID,
|
||||
'post_id' => $pid,
|
||||
'post_title' => $posts_map[$pid] ?? get_the_title($pid),
|
||||
'author' => $comment->comment_author,
|
||||
'author_email' => $comment->comment_author_email,
|
||||
'user_id' => (int) $comment->user_id,
|
||||
'user_name' => $user ? $user->display_name : '',
|
||||
'content' => $comment->comment_content,
|
||||
'status' => $comment->comment_approved,
|
||||
'parent' => (int) $comment->comment_parent,
|
||||
'date' => $comment->comment_date_gmt,
|
||||
'author_ip' => $comment->comment_author_IP,
|
||||
'avatar_url' => get_avatar_url($comment->comment_author_email, ['size' => 48]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format comment for public views (no email/IP).
|
||||
*/
|
||||
private function format_comment_public(WP_Comment $comment, array $users_map = []): array {
|
||||
$uid = (int) $comment->user_id;
|
||||
$user = $users_map[$uid] ?? ($uid ? get_userdata($uid) : null);
|
||||
return [
|
||||
'id' => (int) $comment->comment_ID,
|
||||
'author' => $comment->comment_author,
|
||||
'user_id' => (int) $comment->user_id,
|
||||
'content' => $comment->comment_content,
|
||||
'date' => $comment->comment_date_gmt,
|
||||
'parent' => (int) $comment->comment_parent,
|
||||
'avatar_url' => get_avatar_url($comment->comment_author_email, ['size' => 48]),
|
||||
'children' => [], // Will be populated by JS if threaded
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: check logged-in permission (any authenticated user).
|
||||
*/
|
||||
public function check_logged_in_permission(): bool {
|
||||
return is_user_logged_in();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin Controller Base
|
||||
*
|
||||
* 所有后台业务域控制器的基类。
|
||||
* 提供通用的权限检查、响应格式化、设置读写等基础设施。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
abstract class AKSRT_Admin_Controller_Base {
|
||||
|
||||
/**
|
||||
* REST API namespace (shared with AKSRT_Admin).
|
||||
*/
|
||||
const API_NAMESPACE = 'aksrt/v1';
|
||||
|
||||
/**
|
||||
* Register REST routes for this domain.
|
||||
* Subclasses must implement this method.
|
||||
*/
|
||||
abstract public function register_routes(): void;
|
||||
|
||||
/**
|
||||
* Permission callback — requires manage_options capability.
|
||||
*/
|
||||
public function check_permission(): bool {
|
||||
return current_user_can('manage_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standard success response.
|
||||
*/
|
||||
protected function success($data = [], int $status = 200): WP_REST_Response {
|
||||
return new WP_REST_Response(array_merge(['success' => true], $data), $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standard error response.
|
||||
*/
|
||||
protected function error(string $message, int $status = 400): WP_REST_Response {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
], $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read JSON request body safely.
|
||||
*/
|
||||
protected function get_json_params(WP_REST_Request $request): array {
|
||||
$params = $request->get_json_params();
|
||||
return is_array($params) ? $params : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a settings array using a whitelist of allowed keys.
|
||||
*
|
||||
* @param array $input Raw input data.
|
||||
* @param array $allowed Allowed keys with their types (string|int|bool|float|array).
|
||||
* @return array Sanitized data.
|
||||
*/
|
||||
protected function sanitize_fields(array $input, array $allowed): array {
|
||||
$out = [];
|
||||
foreach ($allowed as $key => $type) {
|
||||
if (!array_key_exists($key, $input)) continue;
|
||||
$val = $input[$key];
|
||||
switch ($type) {
|
||||
case 'string':
|
||||
$out[$key] = sanitize_text_field($val);
|
||||
break;
|
||||
case 'html':
|
||||
$out[$key] = wp_kses_post($val);
|
||||
break;
|
||||
case 'code':
|
||||
$out[$key] = is_string($val) ? wp_unslash($val) : '';
|
||||
break;
|
||||
case 'url':
|
||||
$out[$key] = esc_url_raw($val);
|
||||
break;
|
||||
case 'int':
|
||||
$out[$key] = (int) $val;
|
||||
break;
|
||||
case 'bool':
|
||||
$out[$key] = !empty($val);
|
||||
break;
|
||||
case 'float':
|
||||
$out[$key] = (float) $val;
|
||||
break;
|
||||
case 'array':
|
||||
$out[$key] = is_array($val) ? $val : [];
|
||||
break;
|
||||
default:
|
||||
$out[$key] = sanitize_text_field($val);
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
protected function register_section_routes(string $route_slug, string $domain): void {
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/' . $route_slug . '/(?P<section>[a-z0-9_-]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => function (WP_REST_Request $request) use ($domain) {
|
||||
return $this->get_section_settings($request, $domain);
|
||||
},
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/' . $route_slug . '/(?P<section>[a-z0-9_-]+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => function (WP_REST_Request $request) use ($domain) {
|
||||
return $this->save_section_settings($request, $domain);
|
||||
},
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function get_section_settings(WP_REST_Request $request, string $domain): WP_REST_Response {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$section = (string) $request->get_param('section');
|
||||
$schema = $provider->get_section_schema($domain, $section);
|
||||
|
||||
if (!$schema) {
|
||||
return $this->error('未知的设置分组: ' . esc_html($section), 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => $provider->read_settings($domain, $section),
|
||||
'schema' => $schema,
|
||||
'payload' => [
|
||||
'domain' => $provider->normalize_domain($domain),
|
||||
'section' => $provider->normalize_section($section),
|
||||
'version' => $schema['version'] ?? '1.0.0',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function save_section_settings(WP_REST_Request $request, string $domain): WP_REST_Response {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$section = (string) $request->get_param('section');
|
||||
$schema = $provider->get_section_schema($domain, $section);
|
||||
|
||||
if (!$schema) {
|
||||
return $this->error('未知的设置分组: ' . esc_html($section), 404);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$payload = $params['data'] ?? $params;
|
||||
if (!is_array($payload)) {
|
||||
return $this->error('无效的设置载荷');
|
||||
}
|
||||
|
||||
$sanitized = $this->sanitize_fields($payload, $provider->get_allowed_fields($domain, $section));
|
||||
$validation = $provider->validate($domain, $sanitized, $section);
|
||||
if (!$validation['valid']) {
|
||||
return $this->error(implode('; ', $validation['errors']), 422);
|
||||
}
|
||||
|
||||
$provider->write_settings($domain, $sanitized, $section);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf('%s/%s 设置已保存', $provider->normalize_domain($domain), $provider->normalize_section($section)),
|
||||
'data' => $provider->read_settings($domain, $section),
|
||||
'payload' => [
|
||||
'domain' => $provider->normalize_domain($domain),
|
||||
'section' => $provider->normalize_section($section),
|
||||
'version' => $schema['version'] ?? '1.0.0',
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Extension Controller
|
||||
*
|
||||
* Owns REST routes for the "扩展&增强" domain.
|
||||
* Aggregates real service status from SMS, WeChat, Captcha, IP Geo, Content Audit, Search Push.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Extension_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
private $admin;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function set_admin(AKSRT_Admin $admin): void {
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Extensions overview
|
||||
register_rest_route(self::API_NAMESPACE, '/extensions/overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Docs page stats
|
||||
register_rest_route(self::API_NAMESPACE, '/docs/stats', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_docs_stats'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Docs search (public, for frontend AJAX)
|
||||
register_rest_route(self::API_NAMESPACE, '/docs/search', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'search_docs'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Extensions service status summary (aggregated)
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/extensions', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_extensions_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Security settings summary
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/security', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_security_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Security settings — save risk switches
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/security', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'save_security_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Security audit log
|
||||
register_rest_route(self::API_NAMESPACE, '/security/audit-log', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_audit_log'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// IP geo test tool
|
||||
register_rest_route(self::API_NAMESPACE, '/extensions/ip-test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'test_ip_geo'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Content audit test tool
|
||||
register_rest_route(self::API_NAMESPACE, '/extensions/audit-test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'test_content_audit'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /extensions/overview — real extension status aggregation.
|
||||
*/
|
||||
public function get_overview(): WP_REST_Response {
|
||||
$services = $this->collect_service_statuses();
|
||||
|
||||
$installed = 0;
|
||||
$available = count($services);
|
||||
$unconfigured = 0;
|
||||
|
||||
foreach ($services as $svc) {
|
||||
if (!empty($svc['enabled'])) $installed++;
|
||||
if (!empty($svc['enabled']) && !empty($svc['configured']) && empty($svc['verified'])) {
|
||||
$unconfigured++;
|
||||
}
|
||||
}
|
||||
|
||||
// Build message
|
||||
if ($installed === 0) {
|
||||
$message = sprintf(__('已注册 %d 项扩展,均未启用。', 'aksrt'), $available);
|
||||
} elseif ($unconfigured > 0) {
|
||||
$message = sprintf(__('已启用 %d/%d 项扩展,%d 项待补全配置。', 'aksrt'), $installed, $available, $unconfigured);
|
||||
} else {
|
||||
$message = sprintf(__('已启用 %d/%d 项扩展,全部就绪。', 'aksrt'), $installed, $available);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'installed' => $installed,
|
||||
'available' => $available,
|
||||
'message' => $message,
|
||||
'services' => $services,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_docs_stats(): WP_REST_Response {
|
||||
$nav_count = 0;
|
||||
$special_count = 0;
|
||||
|
||||
$nav_query = new WP_Query([
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_wp_page_template',
|
||||
'meta_value' => 'templates/pages/page-documentnav.php',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => false,
|
||||
]);
|
||||
$nav_count = (int) $nav_query->found_posts;
|
||||
|
||||
$special_query = new WP_Query([
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_wp_page_template',
|
||||
'meta_value' => 'templates/pages/page-special.php',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => false,
|
||||
]);
|
||||
$special_count = (int) $special_query->found_posts;
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'total' => $nav_count + $special_count,
|
||||
'nav_count' => $nav_count,
|
||||
'special_count' => $special_count,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /docs/search — search document pages by keyword.
|
||||
*/
|
||||
public function search_docs(WP_REST_Request $request): WP_REST_Response {
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$root_id = (int) $request->get_param('root_id');
|
||||
$limit = max(1, min(50, (int) ($request->get_param('limit') ?: 20)));
|
||||
|
||||
if ($keyword === '') {
|
||||
return $this->success(['data' => ['results' => [], 'total' => 0]]);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Docs_Presenter')) {
|
||||
return $this->success(['data' => ['results' => [], 'total' => 0]]);
|
||||
}
|
||||
|
||||
$results = AKSRT_Docs_Presenter::get_instance()->search_pages($keyword, $root_id, $limit);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'results' => $results,
|
||||
'total' => count($results),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /settings/extensions — aggregated extension service status.
|
||||
*/
|
||||
public function get_extensions_status(): WP_REST_Response {
|
||||
$services = $this->collect_service_statuses();
|
||||
$flat = [];
|
||||
|
||||
foreach ($services as $key => $svc) {
|
||||
$flat["{$key}_enabled"] = !empty($svc['enabled']);
|
||||
$flat["{$key}_configured"] = !empty($svc['configured']);
|
||||
$flat["{$key}_available"] = !empty($svc['available']);
|
||||
$flat["{$key}_verified"] = !empty($svc['verified']);
|
||||
$flat["{$key}_provider"] = (string) ($svc['provider'] ?? '');
|
||||
}
|
||||
|
||||
// 保留旧布尔字段兼容
|
||||
$flat['sms_enabled'] = !empty($services['sms']['enabled']);
|
||||
$flat['wechat_enabled'] = !empty($services['wechat']['enabled']);
|
||||
$flat['captcha_enabled'] = !empty($services['captcha']['enabled']);
|
||||
$flat['baidu_push_enabled'] = !empty($services['push']['enabled']);
|
||||
$flat['content_audit_enabled'] = !empty($services['audit']['enabled']);
|
||||
|
||||
return $this->success([
|
||||
'data' => $flat,
|
||||
'services' => $services,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /settings/security — security settings summary with real service status.
|
||||
*/
|
||||
public function get_security_settings(): WP_REST_Response {
|
||||
$services = $this->collect_service_statuses();
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
|
||||
$email_gate_enabled = true;
|
||||
if (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {
|
||||
$email_gate_enabled = false;
|
||||
}
|
||||
|
||||
$captcha_comment_enabled = !empty($settings['captcha_comment_enabled']);
|
||||
$captcha_download_enabled = !empty($settings['captcha_download_enabled']);
|
||||
|
||||
$risk_switches = [
|
||||
'show_unapproved_comments' => !empty($settings['show_unapproved_comments']),
|
||||
'skip_submit_moderation' => !empty($settings['skip_submit_moderation']),
|
||||
'guest_comment' => !empty($settings['guest_comment']),
|
||||
];
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'email_gate_enabled' => $email_gate_enabled,
|
||||
'captcha_comment_enabled' => $captcha_comment_enabled,
|
||||
'captcha_download_enabled' => $captcha_download_enabled,
|
||||
'content_audit_enabled' => !empty($services['audit']['enabled']),
|
||||
'content_audit_configured' => !empty($services['audit']['configured']),
|
||||
'ip_geo_enabled' => !empty($services['ip_geo']['enabled']),
|
||||
'ip_geo_configured' => !empty($services['ip_geo']['configured']),
|
||||
'captcha_enabled' => $captcha_comment_enabled || $captcha_download_enabled,
|
||||
'risk_switches' => $risk_switches,
|
||||
'services' => $services,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /settings/security — save risk switches.
|
||||
*/
|
||||
public function save_security_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$risk_switches = $params['risk_switches'] ?? [];
|
||||
|
||||
if (!is_array($risk_switches)) {
|
||||
return $this->error('无效的风险设置数据');
|
||||
}
|
||||
|
||||
$allowed = [
|
||||
'show_unapproved_comments' => 'bool',
|
||||
'skip_submit_moderation' => 'bool',
|
||||
'guest_comment' => 'bool',
|
||||
];
|
||||
|
||||
$sanitized = $this->sanitize_fields($risk_switches, $allowed);
|
||||
|
||||
// Merge into main settings
|
||||
$all = AKSRT_Settings::get_all();
|
||||
foreach ($sanitized as $key => $val) {
|
||||
$all[$key] = $val;
|
||||
}
|
||||
|
||||
update_option('aksrt_settings', $all);
|
||||
|
||||
return $this->success([
|
||||
'message' => __('风险设置已保存', 'aksrt'),
|
||||
'data' => $sanitized,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /security/audit-log — recent security audit entries.
|
||||
*/
|
||||
public function get_audit_log(): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$limit = 20;
|
||||
$logs = [];
|
||||
|
||||
// Recent user registrations
|
||||
$recent_users = get_users([
|
||||
'number' => $limit,
|
||||
'orderby' => 'registered',
|
||||
'order' => 'DESC',
|
||||
'fields' => ['ID', 'display_name', 'user_email', 'user_registered'],
|
||||
]);
|
||||
|
||||
foreach ($recent_users as $user) {
|
||||
$logs[] = [
|
||||
'id' => 'reg_' . $user->ID,
|
||||
'action' => '用户注册',
|
||||
'detail' => sprintf('%s (%s) 完成注册', $user->display_name, $user->user_email),
|
||||
'time' => $user->user_registered,
|
||||
'type' => 'auth',
|
||||
];
|
||||
}
|
||||
|
||||
// Content audit log
|
||||
$audit_log = get_option('aksrt_content_audit_log', []);
|
||||
foreach (array_slice(array_reverse($audit_log), 0, 10) as $i => $entry) {
|
||||
$logs[] = [
|
||||
'id' => 'audit_' . $i,
|
||||
'action' => '内容审核',
|
||||
'detail' => sprintf('%s — %s', $entry['content'] ?? '', ($entry['passed'] ?? false) ? '通过' : '拦截'),
|
||||
'time' => $entry['time'] ?? '',
|
||||
'type' => 'audit',
|
||||
];
|
||||
}
|
||||
|
||||
// Sort by time descending
|
||||
usort($logs, function ($a, $b) {
|
||||
return strcmp($b['time'] ?? '', $a['time'] ?? '');
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'logs' => array_slice($logs, 0, $limit),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /extensions/ip-test — test current IP provider with a given IP.
|
||||
*/
|
||||
public function test_ip_geo(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$ip = trim((string) ($params['ip'] ?? ''));
|
||||
|
||||
if ($ip === '' || !filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return $this->error(__('请输入有效的 IP 地址', 'aksrt'), 422);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_IP_Geo')) {
|
||||
return $this->error(__('IP 归属地服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$result = AKSRT_IP_Geo::get_instance()->lookup($ip);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'ip' => $ip,
|
||||
'result' => $result,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /extensions/audit-test — test text content against current audit provider.
|
||||
*/
|
||||
public function test_content_audit(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$text = trim((string) ($params['text'] ?? ''));
|
||||
|
||||
if ($text === '') {
|
||||
return $this->error(__('请输入要测试的文本内容', 'aksrt'), 422);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Content_Audit')) {
|
||||
return $this->error(__('内容审核服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$result = AKSRT_Content_Audit::get_instance()->audit_text($text);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'text' => $text,
|
||||
'result' => $result,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集所有扩展服务的运行时状态
|
||||
*/
|
||||
private function collect_service_statuses(): array {
|
||||
$services = [];
|
||||
|
||||
// SMS
|
||||
if (class_exists('AKSRT_SMS')) {
|
||||
$services['sms'] = AKSRT_SMS::get_instance()->get_provider_status();
|
||||
} else {
|
||||
$services['sms'] = ['enabled' => false, 'provider' => 'none', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
|
||||
}
|
||||
|
||||
// WeChat
|
||||
if (class_exists('AKSRT_WeChat_Notify')) {
|
||||
$services['wechat'] = AKSRT_WeChat_Notify::get_instance()->get_status();
|
||||
} else {
|
||||
$services['wechat'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
|
||||
}
|
||||
|
||||
// Captcha
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$captcha_en = class_exists('AKSRT_Captcha') && !empty($settings['captcha_enabled']);
|
||||
$services['captcha'] = [
|
||||
'enabled' => $captcha_en,
|
||||
'provider' => sanitize_key((string) ($settings['captcha_provider'] ?? '')),
|
||||
'configured' => $captcha_en,
|
||||
'available' => $captcha_en,
|
||||
'verified' => false,
|
||||
'transport' => 'http_api',
|
||||
'missing_fields' => [],
|
||||
];
|
||||
|
||||
// IP Geo
|
||||
if (class_exists('AKSRT_IP_Geo')) {
|
||||
$services['ip_geo'] = AKSRT_IP_Geo::get_instance()->get_status();
|
||||
} else {
|
||||
$services['ip_geo'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
|
||||
}
|
||||
|
||||
// Content Audit
|
||||
if (class_exists('AKSRT_Content_Audit')) {
|
||||
$services['audit'] = AKSRT_Content_Audit::get_instance()->get_status();
|
||||
} else {
|
||||
$services['audit'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
|
||||
}
|
||||
|
||||
// Search Push
|
||||
if (class_exists('AKSRT_Search_Push')) {
|
||||
$services['push'] = AKSRT_Search_Push::get_instance()->get_status();
|
||||
} else {
|
||||
$services['push'] = ['enabled' => false, 'provider' => '', 'configured' => false, 'available' => false, 'verified' => false, 'transport' => '', 'missing_fields' => []];
|
||||
}
|
||||
|
||||
// Docs Mode
|
||||
$docs_en = !empty($settings['docs_mode_enabled']);
|
||||
$services['docs_mode'] = [
|
||||
'enabled' => $docs_en,
|
||||
'provider' => 'builtin',
|
||||
'configured' => $docs_en,
|
||||
'available' => $docs_en,
|
||||
'verified' => true,
|
||||
'transport' => 'builtin',
|
||||
'missing_fields' => [],
|
||||
];
|
||||
|
||||
return $services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
<?php
|
||||
/**
|
||||
* Forum REST controller.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Forum_Controller extends AKSRT_Admin_Controller_Base {
|
||||
use AKSRT_Admin_Forum_Controller_Part_1;
|
||||
use AKSRT_Admin_Forum_Controller_Part_2;
|
||||
|
||||
|
||||
private static $instance = null;
|
||||
private $admin;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function set_admin(AKSRT_Admin $admin): void {
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/boards', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_boards'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/boards', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_board'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/boards/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_board'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/boards/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_board'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/boards/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'reorder_boards'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_topics'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_topic_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/moderate', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'moderate_topic'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/vote', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'vote_topic'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/update-topic', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_topic_legacy'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderators', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_moderators'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderators', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_moderators'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Change moderator role
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderators/role', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'change_moderator_role'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderator/apply', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'apply_moderator'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderator/applications', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_moderator_applications'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/moderator/applications/(?P<id>[A-Za-z0-9-]+)/review', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'review_moderator_application'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/favorite', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'toggle_favorite'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/score', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'score_topic'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/topics/(?P<id>\d+)/adopt', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'adopt_answer'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// §7.0 Plate CPT endpoints
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_plates'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_plate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_plate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_plate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_plate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'reorder_plates'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// §7.2 Follow plate
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/(?P<id>\d+)/follow', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'toggle_follow_plate'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// §7.2 Plate categories (分区)
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plate-categories', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_plate_categories'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// §7.2 Sorted plates
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/plates/sorted', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_sorted_plates'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// §7.2 Front-end plate CRUD (forum admin/moderator)
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/front/plates', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'frontend_create_plate'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/front/plates/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'frontend_update_plate'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/forum/front/plates/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'frontend_delete_plate'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_overview(): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => ['stats' => [], 'boards' => [], 'recent_topics' => [], 'pending_applications' => []]]);
|
||||
}
|
||||
|
||||
$pending_topics = $forum->get_topic_list([
|
||||
'limit' => 6,
|
||||
'status' => 'pending',
|
||||
'review_status' => AKSRT_Forum::REVIEW_PENDING,
|
||||
'viewer_user_id' => get_current_user_id(),
|
||||
'include_hidden' => true,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'stats' => $forum->get_stats(),
|
||||
'boards' => array_slice($forum->get_boards(), 0, 6),
|
||||
'pending_topics' => $pending_topics['items'],
|
||||
'pending_applications' => array_slice($forum->get_moderator_application_list(['status' => AKSRT_Forum::APPLICATION_PENDING]), 0, 6),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_boards(): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
$items = $forum ? $forum->get_boards() : [];
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'total' => count($items),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function create_board(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$name = sanitize_text_field((string) ($params['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
return $this->error(__('版块名称不能为空', 'aksrt'));
|
||||
}
|
||||
|
||||
$term_id = $forum->create_board($params);
|
||||
if ($term_id <= 0) {
|
||||
return $this->error(__('创建版块失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('版块已创建', 'aksrt'),
|
||||
'data' => ['id' => $term_id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update_board(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$board_id = (int) $request->get_param('id');
|
||||
if ($board_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$success = $forum->update_board($board_id, $this->get_json_params($request));
|
||||
if (!$success) {
|
||||
return $this->error(__('版块更新失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('版块已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function delete_board(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$board_id = (int) $request->get_param('id');
|
||||
if ($board_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$success = $forum->delete_board($board_id);
|
||||
if (!$success) {
|
||||
return $this->error(__('版块删除失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('版块已删除', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function reorder_boards(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$order = $params['order'] ?? [];
|
||||
if (!is_array($order) || empty($order)) {
|
||||
return $this->error(__('排序参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$forum->reorder_boards($order);
|
||||
|
||||
return $this->success(['message' => __('排序已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// §7.0 Plate CPT handlers
|
||||
// ================================================================
|
||||
|
||||
public function get_plates(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0]]);
|
||||
}
|
||||
|
||||
$args = [];
|
||||
$parent = $request->get_param('parent');
|
||||
if ($parent !== null) {
|
||||
$args['parent'] = (int) $parent;
|
||||
}
|
||||
$plate_cat = $request->get_param('plate_cat');
|
||||
if ($plate_cat) {
|
||||
$args['plate_cat'] = (int) $plate_cat;
|
||||
}
|
||||
|
||||
$items = $forum->get_plates($args);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'total' => count($items),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
$plate = $forum->get_plate($plate_id);
|
||||
if (!$plate) {
|
||||
return $this->error(__('版块不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $plate]);
|
||||
}
|
||||
|
||||
public function create_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
$params = $this->get_json_params($request);
|
||||
$name = sanitize_text_field((string) ($params['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
return $this->error(__('版块名称不能为空', 'aksrt'));
|
||||
}
|
||||
|
||||
$plate_id = $forum->create_plate($params);
|
||||
if ($plate_id <= 0) {
|
||||
return $this->error(__('创建版块失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('版块已创建', 'aksrt'),
|
||||
'data' => ['id' => $plate_id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Global & Functions Controller
|
||||
*
|
||||
* Handles REST routes for the "全局&功能" domain:
|
||||
* SEO, email/SMTP, footer, floating buttons, footer tabbar,
|
||||
* friend links, CMS features, homepage settings, category recommend.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Global_Controller extends AKSRT_Admin_Controller_Base {
|
||||
use AKSRT_Admin_Global_Controller_Part_1;
|
||||
use AKSRT_Admin_Global_Controller_Part_2;
|
||||
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all routes for the global domain.
|
||||
*/
|
||||
public function register_routes(): void {
|
||||
// Email / SMTP
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/mail', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_mail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/mail/test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_test_mail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Footer
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/footer', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_footer_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Footer Tabbar
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/footer-tabbar', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_footer_tabbar'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/footer-tabbar/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reorder_footer_tabbar'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Floating Buttons
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/floating-buttons', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_floating_buttons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/floating-buttons/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reorder_floating_buttons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Friend Links
|
||||
register_rest_route(self::API_NAMESPACE, '/friend-links', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_friend_links'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/friend-links/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'rest_delete_friend_link'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/friend-links/reorder', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reorder_friend_links'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// CMS Features
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/cms-features', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_cms_features'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Homepage settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/homepage', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_homepage_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Category recommend
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/category-recommend', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_category_recommend'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Categories list (for admin selectors)
|
||||
register_rest_route(self::API_NAMESPACE, '/categories', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_categories'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Category term meta
|
||||
register_rest_route(self::API_NAMESPACE, '/categories/(?P<id>\d+)/meta', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_update_category_meta'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Homepage tab posts (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/homepage/tab-posts', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_homepage_tab_posts'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'term_id' => ['type' => 'integer', 'required' => true, 'sanitize_callback' => 'absint'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint'],
|
||||
'sort' => ['type' => 'string', 'default' => 'modified', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'view_mode' => ['type' => 'string', 'default' => 'list', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'page_url' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'esc_url_raw'],
|
||||
'panel' => ['type' => 'string', 'default' => 'latest', 'sanitize_callback' => 'sanitize_key'],
|
||||
'source' => ['type' => 'string', 'default' => 'home', 'sanitize_callback' => 'sanitize_key'],
|
||||
'tab_orderby' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_key'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Archive AJAX filter (public) — §2.5/§2.6/§2.7
|
||||
register_rest_route(self::API_NAMESPACE, '/archive/posts', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_archive_posts'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'archive_type' => ['type' => 'string', 'default' => 'category', 'sanitize_callback' => 'sanitize_key'],
|
||||
'term_id' => ['type' => 'integer', 'default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'filter_cat' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'filter_tag' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'sort' => ['type' => 'string', 'default' => 'date', 'sanitize_callback' => 'sanitize_key'],
|
||||
'order' => ['type' => 'string', 'default' => 'DESC', 'sanitize_callback' => 'sanitize_key'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint'],
|
||||
'custom_filters' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Homepage categories (for admin tab editor)
|
||||
register_rest_route(self::API_NAMESPACE, '/homepage/categories', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_homepage_categories'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// User search (for admin user selectors)
|
||||
register_rest_route(self::API_NAMESPACE, '/users/search', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_search_users'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'search' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'number' => ['type' => 'integer', 'default' => 20, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_handle_cms_features(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
return $this->success(['cms_features' => AKSRT_Settings::get_cms_features()]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$allowed = [
|
||||
'resource_cpt_enabled' => 'bool',
|
||||
'download_login' => 'bool',
|
||||
'download_credit' => 'bool',
|
||||
'download_captcha' => 'bool',
|
||||
'search_include_resource' => 'bool',
|
||||
];
|
||||
$sanitized = $this->sanitize_fields($params, $allowed);
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$settings['cms_features'] = array_merge(
|
||||
is_array($settings['cms_features'] ?? null) ? $settings['cms_features'] : [],
|
||||
$sanitized
|
||||
);
|
||||
update_option('aksrt_settings', $settings);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success(['message' => 'CMS 功能已保存', 'cms_features' => AKSRT_Settings::get_cms_features()]);
|
||||
}
|
||||
|
||||
public function rest_handle_mail(WP_REST_Request $request): WP_REST_Response {
|
||||
$option_key = 'aksrt_mail_config';
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$config = get_option($option_key, [
|
||||
'smtp_host' => '',
|
||||
'smtp_port' => 465,
|
||||
'smtp_user' => '',
|
||||
'smtp_pass' => '',
|
||||
'smtp_secure' => 'ssl',
|
||||
'from_email' => '',
|
||||
'from_name' => '',
|
||||
]);
|
||||
return $this->success(['data' => $config, 'code' => 'success']);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$current = get_option($option_key, []);
|
||||
$merged = array_merge(is_array($current) ? $current : [], $params);
|
||||
update_option($option_key, $merged);
|
||||
|
||||
return $this->success(['message' => '邮件配置已保存', 'code' => 'success']);
|
||||
}
|
||||
|
||||
public function rest_test_mail(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$to = sanitize_email($params['to'] ?? '');
|
||||
if (!is_email($to)) {
|
||||
return $this->error(__('请输入有效的邮箱地址', 'aksrt'));
|
||||
}
|
||||
|
||||
$sent = wp_mail($to, __('AKSRT 邮件测试', 'aksrt'), __('如果你收到这封邮件,说明 SMTP 配置正确。', 'aksrt'));
|
||||
if ($sent) {
|
||||
return $this->success(['message' => '测试邮件已发送']);
|
||||
}
|
||||
return $this->error(__('邮件发送失败,请检查 SMTP 配置', 'aksrt'));
|
||||
}
|
||||
|
||||
public function rest_homepage_tab_posts(WP_REST_Request $request): WP_REST_Response {
|
||||
$term_id = (int) $request->get_param('term_id');
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = min(50, max(1, (int) $request->get_param('per_page')));
|
||||
$sort = sanitize_key($request->get_param('sort') ?: 'modified');
|
||||
$view_mode = sanitize_key($request->get_param('view_mode') ?: 'list');
|
||||
$tab_orderby = sanitize_key($request->get_param('tab_orderby') ?: '');
|
||||
$panel = sanitize_key($request->get_param('panel') ?: 'latest');
|
||||
|
||||
$post_types = function_exists('aksrt_homepage_get_post_types')
|
||||
? aksrt_homepage_get_post_types()
|
||||
: ['post'];
|
||||
|
||||
$args = [
|
||||
'post_type' => $post_types,
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $page,
|
||||
'ignore_sticky_posts' => $term_id > 0,
|
||||
];
|
||||
|
||||
if ($term_id > 0) {
|
||||
$args['cat'] = $term_id;
|
||||
}
|
||||
|
||||
$exclude_ids = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::home_exclude_post_ids()
|
||||
: [];
|
||||
if (!empty($exclude_ids)) {
|
||||
$args['post__not_in'] = $exclude_ids;
|
||||
}
|
||||
|
||||
if ($panel === 'latest' || $term_id <= 0) {
|
||||
$exclude_cat_ids = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::home_exclude_category_ids()
|
||||
: [];
|
||||
if (!empty($exclude_cat_ids)) {
|
||||
$args['category__not_in'] = $exclude_cat_ids;
|
||||
}
|
||||
}
|
||||
|
||||
$effective_sort = $tab_orderby !== '' ? $tab_orderby : $sort;
|
||||
if (function_exists('aksrt_homepage_apply_named_sort_args')) {
|
||||
$args = aksrt_homepage_apply_named_sort_args($args, $effective_sort);
|
||||
} else {
|
||||
$args['orderby'] = 'date';
|
||||
$args['order'] = 'DESC';
|
||||
}
|
||||
|
||||
$query = new WP_Query($args);
|
||||
|
||||
$mode = in_array($view_mode, ['card', 'list'], true) ? $view_mode : 'list';
|
||||
$html = '';
|
||||
if (function_exists('aksrt_render_post_list')) {
|
||||
$html = aksrt_render_post_list($query, [
|
||||
'mode' => $mode,
|
||||
'context' => 'home',
|
||||
], false);
|
||||
} elseif ($query->have_posts()) {
|
||||
$html = '<div class="posts-row aksrt-homepage__posts" data-view="' . esc_attr($mode) . '">';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
if (function_exists('aksrt_homepage_render_post_card')) {
|
||||
ob_start();
|
||||
aksrt_homepage_render_post_card();
|
||||
$html .= ob_get_clean();
|
||||
}
|
||||
}
|
||||
$html .= '</div>';
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
$has_more = $page < (int) $query->max_num_pages;
|
||||
|
||||
return $this->success([
|
||||
'html' => $html,
|
||||
'total' => (int) $query->found_posts,
|
||||
'has_more' => $has_more,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_archive_posts(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->rest_homepage_tab_posts($request);
|
||||
}
|
||||
|
||||
public function rest_homepage_categories(): WP_REST_Response {
|
||||
$cats = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
|
||||
$data = [];
|
||||
foreach ($cats as $cat) {
|
||||
$data[] = ['id' => $cat->term_id, 'name' => $cat->name, 'slug' => $cat->slug, 'count' => $cat->count, 'parent' => $cat->parent];
|
||||
}
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
public function rest_handle_friend_links(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$links = aksrt_get_friend_links();
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $links]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$links = get_option('aksrt_friend_links', []);
|
||||
$new_link = [
|
||||
'id' => uniqid('fl_'),
|
||||
'name' => sanitize_text_field($params['name'] ?? ''),
|
||||
'url' => esc_url_raw($params['url'] ?? ''),
|
||||
'logo' => esc_url_raw($params['logo'] ?? ''),
|
||||
'description' => sanitize_textarea_field($params['description'] ?? ''),
|
||||
'sort_order' => (int) ($params['sort_order'] ?? 0),
|
||||
'active' => 1,
|
||||
];
|
||||
$links[] = $new_link;
|
||||
usort($links, function ($a, $b) {
|
||||
return ($a['sort_order'] ?? 0) <=> ($b['sort_order'] ?? 0);
|
||||
});
|
||||
update_option('aksrt_friend_links', $links);
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $new_link, 'message' => __('Friend link added.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_delete_friend_link(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = $request->get_param('id');
|
||||
$links = get_option('aksrt_friend_links', []);
|
||||
$links = array_filter($links, function ($link) use ($id) {
|
||||
return ($link['id'] ?? '') !== $id;
|
||||
});
|
||||
$links = array_values($links);
|
||||
update_option('aksrt_friend_links', $links);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Friend link deleted.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_reorder_friend_links(WP_REST_Request $request): WP_REST_Response {
|
||||
$ordered_ids = $request->get_param('ids');
|
||||
if (!is_array($ordered_ids)) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
|
||||
}
|
||||
$links = get_option('aksrt_friend_links', []);
|
||||
$order_map = array_flip($ordered_ids);
|
||||
usort($links, function ($a, $b) use ($order_map) {
|
||||
$idx_a = $order_map[($a['id'] ?? '')] ?? 999;
|
||||
$idx_b = $order_map[($b['id'] ?? '')] ?? 999;
|
||||
return $idx_a <=> $idx_b;
|
||||
});
|
||||
foreach ($links as $index => &$link) {
|
||||
$link['sort_order'] = $index;
|
||||
}
|
||||
update_option('aksrt_friend_links', $links);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_footer_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$footer = get_option('aksrt_footer_settings', []);
|
||||
$defaults = [
|
||||
'contacts' => [],
|
||||
'quick_links' => [],
|
||||
'more_links' => [],
|
||||
'hot_categories' => 5,
|
||||
'copyright_text' => '',
|
||||
'layout_template' => 'default',
|
||||
'footer_t1_m_s' => ['pc', 'mobile'],
|
||||
'footer_t1_img' => '',
|
||||
'footer_t1_img_dark' => '',
|
||||
'footer_t1_t' => '',
|
||||
'fcode_t1_code' => '',
|
||||
'footer_contact_m_s' => ['pc', 'mobile'],
|
||||
'footer_contact_wechat_img' => '',
|
||||
'footer_contact_qq' => '',
|
||||
'footer_contact_weibo' => '',
|
||||
'footer_contact_email' => '',
|
||||
'footer_contact_more' => '',
|
||||
'fcode_t2_code_1' => '',
|
||||
'fcode_t2_code_2' => '',
|
||||
'footer_mini_img_m_s' => ['pc', 'mobile'],
|
||||
'footer_mini_img' => [],
|
||||
'fcode_customize_code' => '',
|
||||
];
|
||||
$data = array_merge($defaults, $footer);
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $data]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$footer = [
|
||||
'contacts' => isset($params['contacts']) ? $this->sanitize_contacts($params['contacts']) : [],
|
||||
'quick_links' => isset($params['quick_links']) ? $this->sanitize_link_list($params['quick_links']) : [],
|
||||
'more_links' => isset($params['more_links']) ? $this->sanitize_link_list($params['more_links']) : [],
|
||||
'hot_categories' => (int) ($params['hot_categories'] ?? 5),
|
||||
'copyright_text' => sanitize_textarea_field($params['copyright_text'] ?? ''),
|
||||
'layout_template' => in_array($params['layout_template'] ?? '', ['default', 'compact', 'centered'], true)
|
||||
? $params['layout_template'] : 'default',
|
||||
'footer_t1_m_s' => $this->sanitize_device_visibility($params['footer_t1_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_t1_img' => esc_url_raw($params['footer_t1_img'] ?? ''),
|
||||
'footer_t1_img_dark' => esc_url_raw($params['footer_t1_img_dark'] ?? ''),
|
||||
'footer_t1_t' => sanitize_text_field($params['footer_t1_t'] ?? ''),
|
||||
'fcode_t1_code' => wp_kses_post($params['fcode_t1_code'] ?? ''),
|
||||
'footer_contact_m_s' => $this->sanitize_device_visibility($params['footer_contact_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_contact_wechat_img' => esc_url_raw($params['footer_contact_wechat_img'] ?? ''),
|
||||
'footer_contact_qq' => sanitize_text_field($params['footer_contact_qq'] ?? ''),
|
||||
'footer_contact_weibo' => sanitize_text_field($params['footer_contact_weibo'] ?? ''),
|
||||
'footer_contact_email' => sanitize_email($params['footer_contact_email'] ?? ''),
|
||||
'footer_contact_more' => isset($params['footer_contact_more']) && is_array($params['footer_contact_more'])
|
||||
? $this->sanitize_contact_more($params['footer_contact_more']) : [],
|
||||
'fcode_t2_code_1' => wp_kses_post($params['fcode_t2_code_1'] ?? ''),
|
||||
'fcode_t2_code_2' => wp_kses_post($params['fcode_t2_code_2'] ?? ''),
|
||||
'footer_mini_img_m_s' => $this->sanitize_device_visibility($params['footer_mini_img_m_s'] ?? ['pc', 'mobile']),
|
||||
'footer_mini_img' => isset($params['footer_mini_img']) && is_array($params['footer_mini_img'])
|
||||
? $this->sanitize_mini_img_group($params['footer_mini_img']) : [],
|
||||
'fcode_customize_code' => wp_kses_post($params['fcode_customize_code'] ?? ''),
|
||||
];
|
||||
update_option('aksrt_footer_settings', $footer);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Footer settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
private function sanitize_contacts(array $contacts): array {
|
||||
$allowed_icons = ['github', 'x', 'twitter', 'email', 'wechat', 'qq', 'weibo',
|
||||
'telegram', 'discord', 'bilibili', 'linkedin', 'facebook',
|
||||
'youtube', 'instagram', 'rss', 'web', 'globe'];
|
||||
$clean = [];
|
||||
foreach ($contacts as $item) {
|
||||
$icon = sanitize_text_field($item['icon'] ?? '');
|
||||
if (!in_array($icon, $allowed_icons)) {
|
||||
continue;
|
||||
}
|
||||
$clean[] = [
|
||||
'icon' => $icon,
|
||||
'url' => esc_url_raw($item['url'] ?? ''),
|
||||
'title' => sanitize_text_field($item['title'] ?? ''),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_link_list(array $links): array {
|
||||
$clean = [];
|
||||
foreach ($links as $item) {
|
||||
$clean[] = [
|
||||
'id' => isset($item['id']) ? sanitize_text_field($item['id']) : uniqid('link_'),
|
||||
'name' => sanitize_text_field($item['name'] ?? ''),
|
||||
'url' => esc_url_raw($item['url'] ?? '#'),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'sort_order' => (int) ($item['sort_order'] ?? 0),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_device_visibility($value): array {
|
||||
$arr = is_array($value) ? $value : [$value];
|
||||
$allowed = ['pc', 'mobile'];
|
||||
$clean = array_values(array_intersect(array_map('sanitize_key', $arr), $allowed));
|
||||
return $clean ?: ['pc', 'mobile'];
|
||||
}
|
||||
|
||||
private function sanitize_contact_more(array $items): array {
|
||||
$clean = [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$clean[] = [
|
||||
'desc' => sanitize_text_field($item['desc'] ?? ''),
|
||||
'icon' => sanitize_text_field($item['icon'] ?? ''),
|
||||
'link' => esc_url_raw($item['link'] ?? ''),
|
||||
'img' => esc_url_raw($item['img'] ?? ''),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
private function sanitize_mini_img_group(array $items): array {
|
||||
$clean = [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
if (count($clean) >= 4) {
|
||||
break;
|
||||
}
|
||||
$clean[] = [
|
||||
'text' => sanitize_text_field($item['text'] ?? ''),
|
||||
'img' => esc_url_raw($item['img'] ?? ''),
|
||||
];
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
public function rest_handle_footer_tabbar(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_footer_tabbar_settings()
|
||||
: ['enabled' => true, 'scroll_hide' => false, 'scroll_down_hide' => true, 'items' => []];
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$items = isset($params['items']) ? array_map([$this, 'sanitize_tabbar_item'], (array) $params['items']) : [];
|
||||
|
||||
$max_items = max(3, min(7, (int) ($params['max_items'] ?? 5)));
|
||||
if (count($items) > $max_items) {
|
||||
return new WP_REST_Response([
|
||||
'code' => 'error',
|
||||
'message' => sprintf(__('Footer tabbar supports at most %d items.', 'aksrt'), $max_items),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$tabbar = [
|
||||
'enabled' => !empty($params['enabled']),
|
||||
'scroll_hide' => !empty($params['scroll_hide']),
|
||||
'scroll_down_hide' => !empty($params['scroll_down_hide']),
|
||||
'max_items' => $max_items,
|
||||
'icon_size' => max(16, min(32, (int) ($params['icon_size'] ?? 22))),
|
||||
'text_size' => max(8, min(14, (int) ($params['text_size'] ?? 10))),
|
||||
'show_text' => !empty($params['show_text']),
|
||||
'active_color' => sanitize_hex_color($params['active_color'] ?? '') ?: '#f04494',
|
||||
'bg_color' => sanitize_hex_color($params['bg_color'] ?? '') ?: '',
|
||||
'border' => !empty($params['border']),
|
||||
'shadow' => !empty($params['shadow']),
|
||||
'animation' => !empty($params['animation']),
|
||||
'double_click_home' => !empty($params['double_click_home']),
|
||||
'badge_dot' => !empty($params['badge_dot']),
|
||||
'items' => $items,
|
||||
];
|
||||
update_option('aksrt_footer_tabbar', $tabbar);
|
||||
|
||||
if (class_exists('AKSRT_Settings')) {
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Footer tabbar settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Import/Export Controller
|
||||
*
|
||||
* Handles REST routes for settings export, import, section reset,
|
||||
* and health diagnostics.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Import_Export_Controller extends AKSRT_Admin_Controller_Base {
|
||||
use AKSRT_Admin_Import_Export_Controller_Part_1;
|
||||
use AKSRT_Admin_Import_Export_Controller_Part_2;
|
||||
|
||||
|
||||
private static $instance = null;
|
||||
private const SCHEDULED_BACKUP_HOOK = 'aksrt_settings_scheduled_backup';
|
||||
private const MONTHLY_SCHEDULE = 'aksrt_monthly';
|
||||
|
||||
private function __construct() {
|
||||
add_filter('cron_schedules', [$this, 'register_backup_schedules']);
|
||||
add_action('init', [$this, 'sync_backup_schedule']);
|
||||
add_action(self::SCHEDULED_BACKUP_HOOK, [$this, 'run_scheduled_backup']);
|
||||
add_filter('upgrader_pre_install', [$this, 'handle_theme_upgrade_snapshot'], 10, 2);
|
||||
add_action('switch_theme', [$this, 'clear_backup_schedule']);
|
||||
}
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST routes for import/export operations.
|
||||
*/
|
||||
public function register_routes(): void {
|
||||
// Export all settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/export', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_export_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Import settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/import', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_import_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Reset a domain to defaults
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/reset/(?P<domain>[a-z_]+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reset_domain'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Reset all schema-managed settings to defaults
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/reset-all', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_reset_all'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Health check / diagnostics
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/health', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_health_check'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Migration status
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/migration-status', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_migration_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Schema introspection (for frontend to discover available fields)
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/schema/(?P<domain>[a-z_]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_schema'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/schema/(?P<domain>[a-z_]+)/(?P<section>[a-z0-9_-]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_schema_section'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/schema/(?P<domain>[a-z_]+)/(?P<section>[a-z0-9_-]+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_save_schema_section'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Backup snapshots
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/backups', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'list_setting_backups'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/backups', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_setting_backup'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/backups/(?P<id>\d+)/restore', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'restore_setting_backup'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/backups/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_setting_backup'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Widget Template Management ──
|
||||
|
||||
// Export current widget configuration
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/export', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'export_widgets'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Import widget configuration
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/import', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'import_widgets'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// List saved widget templates
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'list_widget_templates'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Save current layout as a named template
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'save_widget_template'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Apply a saved template
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)/apply', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'apply_widget_template'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Delete a saved template
|
||||
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_widget_template'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Handlers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Export all AKSRT settings as a JSON-serializable array.
|
||||
*/
|
||||
public function rest_export_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$domains = $schema_provider->get_domain_keys();
|
||||
|
||||
$export = [
|
||||
'version' => AKSRT_VERSION ?? '3.4.0',
|
||||
'exported' => gmdate('Y-m-d H:i:s'),
|
||||
'domains' => [],
|
||||
];
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$export['domains'][$domain] = [
|
||||
'option_key' => $schema_provider->get_option_key($domain),
|
||||
'data' => $schema_provider->read_settings($domain),
|
||||
];
|
||||
}
|
||||
|
||||
// Also export standalone options from the registry
|
||||
$standalone_keys = AKSRT_Settings::STANDALONE_OPTION_KEYS;
|
||||
|
||||
$export['standalone'] = [];
|
||||
foreach ($standalone_keys as $key) {
|
||||
$val = get_option($key);
|
||||
if ($val !== false) {
|
||||
$export['standalone'][$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success(['data' => $export]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import settings from a JSON payload.
|
||||
*
|
||||
* Request body: { domains: { domain: { data: {...} } }, standalone: { key: value } }
|
||||
* Optional: { dry_run: true } to validate without saving.
|
||||
*/
|
||||
public function rest_import_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$dry_run = !empty($params['dry_run']);
|
||||
|
||||
if (empty($params['domains']) && empty($params['standalone'])) {
|
||||
return $this->error('没有可导入的数据');
|
||||
}
|
||||
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$results = [];
|
||||
|
||||
// Import domain settings
|
||||
if (!$dry_run) {
|
||||
$backup = $this->create_backup_record('manual', __('导入前自动快照', 'aksrt'));
|
||||
$this->persist_setting_backup($backup);
|
||||
}
|
||||
|
||||
// Import domain settings
|
||||
if (!empty($params['domains']) && is_array($params['domains'])) {
|
||||
foreach ($params['domains'] as $domain => $payload) {
|
||||
$data = $payload['data'] ?? $payload;
|
||||
if (!is_array($data)) {
|
||||
$results[$domain] = ['status' => 'error', 'message' => 'Invalid data format'];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate
|
||||
$validation = $schema_provider->validate($domain, $data);
|
||||
if (!$validation['valid']) {
|
||||
$results[$domain] = ['status' => 'error', 'errors' => $validation['errors']];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$dry_run) {
|
||||
$schema_provider->write_settings($domain, $data);
|
||||
$results[$domain] = ['status' => 'imported'];
|
||||
} else {
|
||||
$results[$domain] = ['status' => 'valid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import standalone options
|
||||
if (!empty($params['standalone']) && is_array($params['standalone'])) {
|
||||
foreach ($params['standalone'] as $key => $value) {
|
||||
if (!preg_match('/^aksrt_/', $key)) {
|
||||
$results[$key] = ['status' => 'skipped', 'message' => 'Not an AKSRT option'];
|
||||
continue;
|
||||
}
|
||||
if (!$dry_run) {
|
||||
update_option($key, $value);
|
||||
$results[$key] = ['status' => 'imported'];
|
||||
} else {
|
||||
$results[$key] = ['status' => 'valid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dry_run) {
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'dry_run' => $dry_run,
|
||||
'results' => $results,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a domain's settings to defaults.
|
||||
*/
|
||||
public function rest_reset_domain(WP_REST_Request $request): WP_REST_Response {
|
||||
$domain = $request->get_param('domain');
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
|
||||
if (!in_array($domain, $schema_provider->get_domain_keys(), true)) {
|
||||
return $this->error('未知的设置域: ' . esc_html($domain), 404);
|
||||
}
|
||||
|
||||
$backup = $this->create_backup_record('reset_domain', sprintf(__('重置 %s 前快照', 'aksrt'), $domain));
|
||||
$this->persist_setting_backup($backup);
|
||||
|
||||
$schema_provider->reset_to_defaults($domain);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf('%s 域设置已重置为默认值', $domain),
|
||||
'data' => [
|
||||
'backup_id' => $backup['id'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_reset_all(WP_REST_Request $request): WP_REST_Response {
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$backup = $this->create_backup_record('reset_all', __('重置全部前快照', 'aksrt'));
|
||||
$this->persist_setting_backup($backup);
|
||||
|
||||
foreach ($schema_provider->get_domain_keys() as $domain) {
|
||||
$schema_provider->reset_to_defaults($domain);
|
||||
}
|
||||
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success([
|
||||
'message' => __('全部 schema 托管设置已重置为默认值', 'aksrt'),
|
||||
'data' => [
|
||||
'backup_id' => $backup['id'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check — returns system diagnostics.
|
||||
*/
|
||||
public function rest_health_check(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$checks = [];
|
||||
|
||||
// PHP version
|
||||
$checks['php_version'] = [
|
||||
'label' => 'PHP 版本',
|
||||
'value' => PHP_VERSION,
|
||||
'status' => version_compare(PHP_VERSION, '8.1', '>=') ? 'ok' : 'warning',
|
||||
];
|
||||
|
||||
// WordPress version
|
||||
$wp_version = get_bloginfo('version');
|
||||
$checks['wp_version'] = [
|
||||
'label' => 'WordPress 版本',
|
||||
'value' => $wp_version,
|
||||
'status' => version_compare($wp_version, '6.4', '>=') ? 'ok' : 'warning',
|
||||
];
|
||||
|
||||
// Theme version
|
||||
$theme = wp_get_theme();
|
||||
$checks['theme_version'] = [
|
||||
'label' => '主题版本',
|
||||
'value' => $theme->get('Version') ?? 'unknown',
|
||||
'status' => 'ok',
|
||||
];
|
||||
|
||||
// Database tables
|
||||
$expected_tables = [
|
||||
$wpdb->prefix . 'aksrt_credit_log',
|
||||
$wpdb->prefix . 'aksrt_signin_log',
|
||||
$wpdb->prefix . 'aksrt_invite',
|
||||
$wpdb->prefix . 'aksrt_dl_token',
|
||||
$wpdb->prefix . 'aksrt_notification',
|
||||
];
|
||||
$missing_tables = [];
|
||||
foreach ($expected_tables as $table) {
|
||||
if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) {
|
||||
$missing_tables[] = str_replace($wpdb->prefix, '', $table);
|
||||
}
|
||||
}
|
||||
$checks['db_tables'] = [
|
||||
'label' => '数据表',
|
||||
'value' => empty($missing_tables) ? '全部就绪' : '缺少: ' . implode(', ', $missing_tables),
|
||||
'status' => empty($missing_tables) ? 'ok' : 'error',
|
||||
];
|
||||
|
||||
// Email verification backfill
|
||||
$backfill_done = get_option('aksrt_email_verification_backfill_done', false);
|
||||
$checks['email_backfill'] = [
|
||||
'label' => '邮箱验证迁移',
|
||||
'value' => $backfill_done ? '已完成' : '未完成',
|
||||
'status' => $backfill_done ? 'ok' : 'info',
|
||||
];
|
||||
|
||||
// Avatar source
|
||||
$avatar_source = AKSRT_Settings::get_all()['avatar_source'] ?? 'cn.cravatar.com';
|
||||
$checks['avatar_source'] = [
|
||||
'label' => '头像服务',
|
||||
'value' => $avatar_source,
|
||||
'status' => $avatar_source === 'cn.cravatar.com' ? 'ok' : 'info',
|
||||
];
|
||||
|
||||
// Option count
|
||||
$option_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'aksrt_%'"
|
||||
);
|
||||
$checks['option_count'] = [
|
||||
'label' => 'AKSRT 选项数',
|
||||
'value' => $option_count,
|
||||
'status' => $option_count < 100 ? 'ok' : 'warning',
|
||||
];
|
||||
|
||||
return $this->success(['checks' => $checks]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration status — reports schema version and pending migrations.
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin REST Controller — Messages & Conversations
|
||||
*
|
||||
* Provides REST endpoints for notifications and private messages
|
||||
* under aksrt/v1/messages and aksrt/v1/conversations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Message_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// ── Notifications ──
|
||||
|
||||
// List notifications
|
||||
register_rest_route(self::API_NAMESPACE, '/messages', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'list_notifications'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
'args' => [
|
||||
'type' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['default' => 20, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Unread count
|
||||
register_rest_route(self::API_NAMESPACE, '/messages/unread', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_unread_count'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// Mark one notification read
|
||||
register_rest_route(self::API_NAMESPACE, '/messages/(?P<id>\d+)/read', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'mark_read'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// Mark all notifications read
|
||||
register_rest_route(self::API_NAMESPACE, '/messages/read-all', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'mark_all_read'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// ── Private Messages / Conversations ──
|
||||
|
||||
// List conversations
|
||||
register_rest_route(self::API_NAMESPACE, '/conversations', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'list_conversations'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
'args' => [
|
||||
'limit' => ['default' => 20, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Get thread messages
|
||||
register_rest_route(self::API_NAMESPACE, '/conversations/(?P<id>\d+)/messages', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_thread'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
'args' => [
|
||||
'page' => ['default' => 1, 'sanitize_callback' => 'absint'],
|
||||
'per_page' => ['default' => 30, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Send message in conversation
|
||||
register_rest_route(self::API_NAMESPACE, '/conversations/(?P<id>\d+)/messages', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'send_message'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
'args' => [
|
||||
'content' => ['required' => true, 'sanitize_callback' => 'sanitize_textarea_field'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Create or get conversation with a user
|
||||
register_rest_route(self::API_NAMESPACE, '/conversations/with/(?P<user_id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'get_or_create_conversation'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// Mark conversation read
|
||||
register_rest_route(self::API_NAMESPACE, '/conversations/(?P<id>\d+)/read', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'mark_conversation_read'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
// ── Notification handlers ──
|
||||
|
||||
public function list_notifications(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$type = $request->get_param('type');
|
||||
$per_page = min(100, (int) $request->get_param('per_page'));
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
|
||||
if (!class_exists('AKSRT_Message')) {
|
||||
return $this->success(['data' => [], 'total' => 0]);
|
||||
}
|
||||
|
||||
$msg = AKSRT_Message::get_instance();
|
||||
$args = [
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
];
|
||||
if ($type) {
|
||||
$args['type'] = $type;
|
||||
}
|
||||
|
||||
$notifications = $msg->get_notifications($user_id, $args);
|
||||
$unread = $msg->get_unread_count($user_id);
|
||||
|
||||
return $this->success([
|
||||
'data' => $notifications,
|
||||
'unread' => $unread,
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_unread_count(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
if (!class_exists('AKSRT_Message')) {
|
||||
return $this->success(['data' => ['unread' => 0]]);
|
||||
}
|
||||
$count = AKSRT_Message::get_instance()->get_unread_count($user_id);
|
||||
return $this->success(['data' => ['unread' => $count]]);
|
||||
}
|
||||
|
||||
public function mark_read(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$id = (string) $request->get_param('id');
|
||||
if (!class_exists('AKSRT_Message')) {
|
||||
return $this->error('not_available', '消息系统未启用。', 501);
|
||||
}
|
||||
$result = AKSRT_Message::get_instance()->mark_read($user_id, $id);
|
||||
return $this->success(['data' => ['success' => $result]]);
|
||||
}
|
||||
|
||||
public function mark_all_read(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
if (!class_exists('AKSRT_Message')) {
|
||||
return $this->error('not_available', '消息系统未启用。', 501);
|
||||
}
|
||||
$result = AKSRT_Message::get_instance()->mark_all_read($user_id);
|
||||
return $this->success(['data' => ['success' => $result]]);
|
||||
}
|
||||
|
||||
// ── Conversation handlers ──
|
||||
|
||||
public function list_conversations(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$limit = min(50, (int) $request->get_param('limit'));
|
||||
|
||||
if (!class_exists('AKSRT_Private_Message')) {
|
||||
return $this->success(['data' => []]);
|
||||
}
|
||||
|
||||
$conversations = AKSRT_Private_Message::get_instance()->get_conversations($user_id, $limit);
|
||||
return $this->success(['data' => $conversations]);
|
||||
}
|
||||
|
||||
public function get_thread(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$conv_id = (int) $request->get_param('id');
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = min(50, (int) $request->get_param('per_page'));
|
||||
|
||||
if (!class_exists('AKSRT_Private_Message')) {
|
||||
return $this->error('not_available', '私信系统未启用。', 501);
|
||||
}
|
||||
|
||||
$result = AKSRT_Private_Message::get_instance()->get_thread($conv_id, $user_id, $page, $per_page);
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error('thread_error', $result->get_error_message(), 403);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function send_message(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$conv_id = (int) $request->get_param('id');
|
||||
$content = trim((string) $request->get_param('content'));
|
||||
|
||||
if (!$content) {
|
||||
return $this->error('content_required', '消息内容不能为空。', 400);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Private_Message')) {
|
||||
return $this->error('not_available', '私信系统未启用。', 501);
|
||||
}
|
||||
|
||||
$result = AKSRT_Private_Message::get_instance()->send($conv_id, $user_id, $content);
|
||||
if (!$result['success']) {
|
||||
return $this->error('send_failed', $result['message'] ?? '发送失败。', 500);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function get_or_create_conversation(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$other_id = (int) $request->get_param('user_id');
|
||||
|
||||
if ($user_id === $other_id) {
|
||||
return $this->error('self_message', '不能与自己创建对话。', 400);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Private_Message')) {
|
||||
return $this->error('not_available', '私信系统未启用。', 501);
|
||||
}
|
||||
|
||||
$conv_id = AKSRT_Private_Message::get_instance()->get_or_create_conversation($user_id, $other_id);
|
||||
if (is_wp_error($conv_id)) {
|
||||
return $this->error('create_failed', $conv_id->get_error_message(), 500);
|
||||
}
|
||||
|
||||
return $this->success(['data' => ['conversation_id' => $conv_id]]);
|
||||
}
|
||||
|
||||
public function mark_conversation_read(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$conv_id = (int) $request->get_param('id');
|
||||
|
||||
if (!class_exists('AKSRT_Private_Message')) {
|
||||
return $this->error('not_available', '私信系统未启用。', 501);
|
||||
}
|
||||
|
||||
AKSRT_Private_Message::get_instance()->mark_conversation_read($conv_id, $user_id);
|
||||
return $this->success(['data' => ['success' => true]]);
|
||||
}
|
||||
|
||||
public function check_logged_in_permission(): bool {
|
||||
return is_user_logged_in();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Page Display Controller
|
||||
*
|
||||
* Handles REST routes for the "页面&显示" domain:
|
||||
* Header components, banners, and homepage visual modules.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Page_Display_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
private $admin;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function set_admin(AKSRT_Admin $admin): void {
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Banners (generic CRUD — hero, banner kinds)
|
||||
register_rest_route(self::API_NAMESPACE, '/banners', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_banners'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/banners', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_create_banner'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/banners/(?P<id>[\w-]+)', [
|
||||
'methods' => ['PUT', 'DELETE'],
|
||||
'callback' => [$this, 'rest_handle_banner_item'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function normalize_banner_kind($kind): string {
|
||||
return in_array($kind, ['hero', 'banner'], true) ? $kind : 'hero';
|
||||
}
|
||||
|
||||
private function get_banner_kind_query(string $kind): array {
|
||||
if ('banner' === $kind) {
|
||||
return [
|
||||
[
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'banner',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => '_aksrt_kind',
|
||||
'value' => 'hero',
|
||||
],
|
||||
[
|
||||
'key' => '_aksrt_kind',
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function rest_get_banners(WP_REST_Request $request): WP_REST_Response {
|
||||
$kind = $this->normalize_banner_kind($request->get_param('kind') ?: 'hero');
|
||||
$args = [
|
||||
'post_type' => 'aksrt_banner',
|
||||
'posts_per_page' => 100,
|
||||
'post_status' => 'any',
|
||||
'orderby' => 'menu_order',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => $this->get_banner_kind_query($kind),
|
||||
];
|
||||
$posts = get_posts($args);
|
||||
$banners = array_map(function ($post) {
|
||||
$layers_json = get_post_meta($post->ID, '_layers', true);
|
||||
return [
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'subtitle' => get_post_meta($post->ID, '_subtitle', true),
|
||||
'description' => get_post_meta($post->ID, '_description', true),
|
||||
'image_url' => get_post_meta($post->ID, '_image_url', true),
|
||||
'video_url' => get_post_meta($post->ID, '_video_url', true),
|
||||
'link_url' => get_post_meta($post->ID, '_link_url', true),
|
||||
'link_target' => get_post_meta($post->ID, '_link_target', true) ?: '_self',
|
||||
'text_align' => get_post_meta($post->ID, '_text_align', true) ?: 'left',
|
||||
'device_visibility' => get_post_meta($post->ID, '_device_visibility', true) ?: 'all',
|
||||
'enabled' => get_post_meta($post->ID, '_enabled', true) === '1',
|
||||
'show_text' => get_post_meta($post->ID, '_show_text', true) !== '0',
|
||||
'kind' => get_post_meta($post->ID, '_aksrt_kind', true) ?: 'hero',
|
||||
'sort_order' => $post->menu_order,
|
||||
'text_size_pc' => get_post_meta($post->ID, '_text_size_pc', true) ?: '',
|
||||
'text_size_m' => get_post_meta($post->ID, '_text_size_m', true) ?: '',
|
||||
'text_parallax' => get_post_meta($post->ID, '_text_parallax', true) === '1',
|
||||
'layers' => is_string($layers_json) && $layers_json !== '' ? json_decode($layers_json, true) : [],
|
||||
];
|
||||
}, $posts);
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $banners]);
|
||||
}
|
||||
|
||||
public function rest_create_banner(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$kind = $this->normalize_banner_kind($params['kind'] ?? 'hero');
|
||||
$title = sanitize_text_field($params['title'] ?? '');
|
||||
$post_id = wp_insert_post([
|
||||
'post_title' => '' !== $title ? $title : ('banner' === $kind ? 'Banner Slide' : 'Hero Slide'),
|
||||
'post_type' => 'aksrt_banner',
|
||||
'post_status' => 'publish',
|
||||
'menu_order' => (int) ($params['sort_order'] ?? 0),
|
||||
]);
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => $post_id->get_error_message()], 400);
|
||||
}
|
||||
|
||||
update_post_meta($post_id, '_image_url', esc_url_raw($params['image_url'] ?? ''));
|
||||
update_post_meta($post_id, '_link_url', esc_url_raw($params['link_url'] ?? ''));
|
||||
update_post_meta($post_id, '_link_target', in_array($params['link_target'] ?? '', ['_self', '_blank'], true) ? $params['link_target'] : '_self');
|
||||
update_post_meta($post_id, '_subtitle', sanitize_text_field($params['subtitle'] ?? ''));
|
||||
update_post_meta($post_id, '_description', sanitize_textarea_field($params['description'] ?? ''));
|
||||
update_post_meta($post_id, '_video_url', esc_url_raw($params['video_url'] ?? ''));
|
||||
update_post_meta($post_id, '_text_align', in_array($params['text_align'] ?? '', ['left', 'center', 'right'], true) ? $params['text_align'] : 'left');
|
||||
update_post_meta($post_id, '_device_visibility', in_array($params['device_visibility'] ?? '', ['all', 'pc', 'mobile'], true) ? $params['device_visibility'] : 'all');
|
||||
update_post_meta($post_id, '_enabled', !array_key_exists('enabled', $params) || !empty($params['enabled']) ? '1' : '0');
|
||||
update_post_meta($post_id, '_show_text', !array_key_exists('show_text', $params) || !empty($params['show_text']) ? '1' : '0');
|
||||
update_post_meta($post_id, '_aksrt_kind', $kind);
|
||||
update_post_meta($post_id, '_text_size_pc', sanitize_text_field($params['text_size_pc'] ?? ''));
|
||||
update_post_meta($post_id, '_text_size_m', sanitize_text_field($params['text_size_m'] ?? ''));
|
||||
update_post_meta($post_id, '_text_parallax', !empty($params['text_parallax']) ? '1' : '0');
|
||||
if (isset($params['layers']) && is_array($params['layers'])) {
|
||||
update_post_meta($post_id, '_layers', wp_json_encode($params['layers']));
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => ['id' => $post_id], 'message' => __('幻灯片已创建', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_banner_item(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = absint($request['id']);
|
||||
if (!$id || get_post_type($id) !== 'aksrt_banner') {
|
||||
return new WP_REST_Response(['code' => 'not_found', 'message' => __('幻灯片不存在', 'aksrt')], 404);
|
||||
}
|
||||
|
||||
if ($request->get_method() === 'DELETE') {
|
||||
wp_delete_post($id, true);
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('幻灯片已删除', 'aksrt')]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$kind = $this->normalize_banner_kind($params['kind'] ?? get_post_meta($id, '_aksrt_kind', true) ?: 'hero');
|
||||
$title = sanitize_text_field($params['title'] ?? '');
|
||||
if ($title === '') {
|
||||
return new WP_REST_Response(['code' => 'validation_error', 'message' => __('标题不能为空', 'aksrt')], 422);
|
||||
}
|
||||
|
||||
wp_update_post([
|
||||
'ID' => $id,
|
||||
'post_title' => $title,
|
||||
]);
|
||||
|
||||
update_post_meta($id, '_aksrt_kind', $kind);
|
||||
update_post_meta($id, '_subtitle', sanitize_text_field($params['subtitle'] ?? ''));
|
||||
update_post_meta($id, '_description', sanitize_textarea_field($params['description'] ?? ''));
|
||||
update_post_meta($id, '_image_url', esc_url_raw($params['image_url'] ?? ''));
|
||||
update_post_meta($id, '_video_url', esc_url_raw($params['video_url'] ?? ''));
|
||||
update_post_meta($id, '_link_url', esc_url_raw($params['link_url'] ?? ''));
|
||||
update_post_meta($id, '_link_target', in_array($params['link_target'] ?? '', ['_blank', '_self'], true) ? $params['link_target'] : '_self');
|
||||
update_post_meta($id, '_text_align', in_array($params['text_align'] ?? '', ['left', 'center', 'right'], true) ? $params['text_align'] : 'left');
|
||||
update_post_meta($id, '_device_visibility', in_array($params['device_visibility'] ?? '', ['all', 'pc', 'mobile'], true) ? $params['device_visibility'] : 'all');
|
||||
update_post_meta($id, '_enabled', !empty($params['enabled']) ? '1' : '0');
|
||||
update_post_meta($id, '_show_text', !isset($params['show_text']) || !empty($params['show_text']) ? '1' : '0');
|
||||
update_post_meta($id, '_show_meta', !empty($params['show_meta']) ? '1' : '0');
|
||||
update_post_meta($id, '_text_size_pc', sanitize_text_field($params['text_size_pc'] ?? ''));
|
||||
update_post_meta($id, '_text_size_m', sanitize_text_field($params['text_size_m'] ?? ''));
|
||||
update_post_meta($id, '_text_parallax', !empty($params['text_parallax']) ? '1' : '0');
|
||||
|
||||
if (isset($params['layers']) && is_array($params['layers'])) {
|
||||
update_post_meta($id, '_layers', wp_json_encode(array_values($params['layers'])));
|
||||
} else {
|
||||
delete_post_meta($id, '_layers');
|
||||
}
|
||||
|
||||
if (isset($params['sort_order'])) {
|
||||
wp_update_post([
|
||||
'ID' => $id,
|
||||
'menu_order' => (int) $params['sort_order'],
|
||||
]);
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('幻灯片已更新', 'aksrt')]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,753 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Payment Controller
|
||||
*
|
||||
* Owns section-level REST routes for the "支付&付费" domain.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Payment_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
$this->register_section_routes('payment', 'payment');
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_payment_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Revenue stats (with optional time-series)
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/revenue', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_revenue'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
'args' => [
|
||||
'date_from' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'date_to' => ['default' => '', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'granularity' => ['default' => 'day', 'sanitize_callback' => 'sanitize_text_field'],
|
||||
'time_series' => ['default' => false, 'sanitize_callback' => 'rest_sanitize_boolean'],
|
||||
],
|
||||
]);
|
||||
|
||||
// Withdrawal list — unified pagination: page + per_page
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_withdrawals'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Withdrawal status update
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/withdrawals/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_withdrawal_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Charge Card ──
|
||||
|
||||
// Charge card list
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_charge_cards'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_charge_card_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Charge card create
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_charge_card'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Charge card batch create
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/batch', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'batch_create_charge_cards'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Charge card disable
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/charge-cards/(?P<id>\d+)/disable', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'disable_charge_card'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Rebate ──
|
||||
|
||||
// Rebate list
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/rebate/log', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_rebate_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/rebate/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_rebate_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Rebate settle
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/rebate/(?P<id>\d+)/settle', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'settle_rebate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Rebate cancel
|
||||
register_rest_route(self::API_NAMESPACE, '/payment/rebate/(?P<id>\d+)/cancel', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'cancel_rebate'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_payment_overview(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return $this->error('设置模块未初始化', 500);
|
||||
}
|
||||
|
||||
$paid = AKSRT_Settings::get_paid_content_settings();
|
||||
$recharge = AKSRT_Settings::get_payment_recharge_settings();
|
||||
$vip = AKSRT_Settings::get_payment_vip_settings();
|
||||
$revenue = AKSRT_Settings::get_payment_revenue_settings();
|
||||
$affiliate = AKSRT_Settings::get_payment_affiliate_settings();
|
||||
$gateway = AKSRT_Settings::get_payment_gateway_settings();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'paid' => $paid,
|
||||
'recharge' => $recharge,
|
||||
'vip' => $vip,
|
||||
'revenue' => $revenue,
|
||||
'affiliate' => $affiliate,
|
||||
'gateway' => $gateway,
|
||||
'currency_name' => AKSRT_Settings::get_currency_name(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /payment/revenue — revenue stats with optional time-series
|
||||
*/
|
||||
public function get_revenue(WP_REST_Request $request): WP_REST_Response {
|
||||
$revenue_stats = ['total' => 0, 'pending' => 0, 'settled' => 0, 'count' => 0];
|
||||
if (class_exists('AKSRT_Revenue')) {
|
||||
$revenue_stats = AKSRT_Revenue::get_instance()->get_stats();
|
||||
}
|
||||
|
||||
$withdrawal_stats = ['total' => 0, 'pending' => 0, 'completed' => 0, 'total_paid' => 0];
|
||||
if (class_exists('AKSRT_Withdrawal')) {
|
||||
$withdrawal_stats = AKSRT_Withdrawal::get_instance()->get_stats();
|
||||
}
|
||||
|
||||
$response = [
|
||||
'revenue' => $revenue_stats,
|
||||
'withdrawal' => $withdrawal_stats,
|
||||
];
|
||||
|
||||
// Time-series data
|
||||
if ($request->get_param('time_series')) {
|
||||
$response['time_series'] = $this->get_revenue_time_series($request);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $response]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build revenue time-series data.
|
||||
*/
|
||||
private function get_revenue_time_series(WP_REST_Request $request): array {
|
||||
global $wpdb;
|
||||
$prefix = $wpdb->prefix;
|
||||
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
$granularity = sanitize_text_field((string) $request->get_param('granularity'));
|
||||
|
||||
// Default: last 30 days
|
||||
if (!$date_from) {
|
||||
$date_from = gmdate('Y-m-d', strtotime('-30 days'));
|
||||
}
|
||||
if (!$date_to) {
|
||||
$date_to = gmdate('Y-m-d');
|
||||
}
|
||||
|
||||
// Granularity: day, week, month
|
||||
$date_format = '%Y-%m-%d';
|
||||
if ($granularity === 'week') {
|
||||
$date_format = '%x-W%v';
|
||||
} elseif ($granularity === 'month') {
|
||||
$date_format = '%Y-%m';
|
||||
}
|
||||
|
||||
$table = $prefix . 'aksrt_revenue';
|
||||
$results = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT DATE_FORMAT(created_at, %s) AS period,
|
||||
SUM(amount) AS total_amount,
|
||||
COUNT(*) AS order_count
|
||||
FROM {$table}
|
||||
WHERE created_at >= %s AND created_at < DATE_ADD(%s, INTERVAL 1 DAY)
|
||||
GROUP BY period
|
||||
ORDER BY period ASC",
|
||||
$date_format,
|
||||
$date_from,
|
||||
$date_to
|
||||
));
|
||||
|
||||
$series = [];
|
||||
if ($results) {
|
||||
foreach ($results as $row) {
|
||||
$series[] = [
|
||||
'date' => (string) $row->period,
|
||||
'amount' => (float) $row->total_amount,
|
||||
'count' => (int) $row->order_count,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $series;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /payment/withdrawals — 提现列表,统一 page + per_page 分页
|
||||
*/
|
||||
public function get_withdrawals(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Withdrawal')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'total_pages' => 1, 'stats' => []]]);
|
||||
}
|
||||
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
||||
|
||||
$items = AKSRT_Withdrawal::get_instance()->get_all([
|
||||
'status' => $status,
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
]);
|
||||
|
||||
$total = AKSRT_Withdrawal::get_instance()->get_count(['status' => $status]);
|
||||
$stats = AKSRT_Withdrawal::get_instance()->get_stats();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'total_pages' => max(1, (int) ceil($total / $per_page)),
|
||||
'stats' => $stats,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/withdrawals/{id} — 更新提现状态
|
||||
*/
|
||||
public function update_withdrawal_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Withdrawal')) {
|
||||
return $this->error('提现模块未初始化', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$new_status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
|
||||
$valid_statuses = ['pending', 'approved', 'processing', 'completed', 'rejected'];
|
||||
if (!in_array($new_status, $valid_statuses, true)) {
|
||||
return $this->error('无效的提现状态');
|
||||
}
|
||||
|
||||
if ($id <= 0) {
|
||||
return $this->error('无效的提现 ID');
|
||||
}
|
||||
|
||||
$success = AKSRT_Withdrawal::get_instance()->update_status($id, $new_status, $note);
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('提现状态已更新', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('更新失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Charge Card
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* GET /payment/charge-cards — 充值卡列表
|
||||
*/
|
||||
public function get_charge_cards(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Charge_Card')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = AKSRT_DB_Migrator::table('charge_card');
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($status !== '') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
if ($date_from !== '') {
|
||||
$where[] = 'created_at >= %s';
|
||||
$params[] = $date_from . ' 00:00:00';
|
||||
}
|
||||
if ($date_to !== '') {
|
||||
$where[] = 'created_at <= %s';
|
||||
$params[] = $date_to . ' 23:59:59';
|
||||
}
|
||||
|
||||
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
||||
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
$items = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$item = $this->build_charge_card_admin_item($row);
|
||||
if ($keyword !== '' && mb_stripos(implode(' ', array_filter([
|
||||
(string) ($item['code'] ?? ''),
|
||||
(string) ($item['note'] ?? ''),
|
||||
(string) ($item['used_by_user']['display_name'] ?? ''),
|
||||
(string) ($item['used_by_user']['email'] ?? ''),
|
||||
])), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$stats = AKSRT_Charge_Card::get_instance()->get_stats();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values(array_slice($items, $offset, $per_page)),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'max_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
||||
'stats' => $stats,
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
||||
['label' => __('未使用', 'aksrt'), 'value' => 'active'],
|
||||
['label' => __('已使用', 'aksrt'), 'value' => 'used'],
|
||||
['label' => __('已过期', 'aksrt'), 'value' => 'expired'],
|
||||
['label' => __('已禁用', 'aksrt'), 'value' => 'disabled'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/charge-cards — 创建充值卡
|
||||
*/
|
||||
public function create_charge_card(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Charge_Card')) {
|
||||
return $this->error('充值卡系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$code = sanitize_text_field((string) ($params['code'] ?? ''));
|
||||
$amount = (float) ($params['amount'] ?? 0);
|
||||
|
||||
if ($code === '' || $amount <= 0) {
|
||||
return $this->error('卡号和金额不能为空');
|
||||
}
|
||||
|
||||
$id = AKSRT_Charge_Card::get_instance()->create($params);
|
||||
|
||||
if ($id > 0) {
|
||||
return $this->success([
|
||||
'message' => __('充值卡已创建', 'aksrt'),
|
||||
'data' => ['id' => $id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
return $this->error(__('创建失败(可能卡号已存在)', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/charge-cards/batch — 批量生成充值卡
|
||||
*/
|
||||
public function batch_create_charge_cards(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Charge_Card')) {
|
||||
return $this->error('充值卡系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$amount = (float) ($params['amount'] ?? 0);
|
||||
$count = max(1, min(100, (int) ($params['count'] ?? 1)));
|
||||
$length = max(8, min(32, (int) ($params['length'] ?? 16)));
|
||||
$prefix = sanitize_text_field((string) ($params['prefix'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
$expires = sanitize_text_field((string) ($params['expires_at'] ?? ''));
|
||||
|
||||
if ($amount <= 0) {
|
||||
return $this->error('金额必须大于 0');
|
||||
}
|
||||
|
||||
$service = AKSRT_Charge_Card::get_instance();
|
||||
$codes = [];
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$code = $prefix . strtoupper(wp_generate_password($length, false));
|
||||
$id = $service->create([
|
||||
'code' => $code,
|
||||
'amount' => $amount,
|
||||
'expires_at' => $expires,
|
||||
'note' => $note,
|
||||
]);
|
||||
if ($id > 0) {
|
||||
$codes[] = $code;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf(__('已生成 %d 张充值卡', 'aksrt'), count($codes)),
|
||||
'data' => ['codes' => $codes, 'count' => count($codes)],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/charge-cards/{id}/disable — 禁用充值卡
|
||||
*/
|
||||
public function disable_charge_card(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Charge_Card')) {
|
||||
return $this->error('充值卡系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_Charge_Card::get_instance()->disable($id);
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('充值卡已禁用', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('操作失败', 'aksrt'));
|
||||
}
|
||||
|
||||
public function get_charge_card_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$table = AKSRT_DB_Migrator::table('charge_card');
|
||||
|
||||
if ($id <= 0 || !$table) {
|
||||
return $this->error(__('充值卡不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
|
||||
if (!$row) {
|
||||
return $this->error(__('充值卡不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'card' => $this->build_charge_card_admin_item($row),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* Rebate
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* GET /payment/rebate/log — 返佣记录列表
|
||||
*/
|
||||
public function get_rebate_list(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Rebate')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = AKSRT_DB_Migrator::table('rebate_log');
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$type = sanitize_key((string) $request->get_param('type'));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$user_id = (int) $request->get_param('user_id');
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($status !== '') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
if ($type !== '') {
|
||||
$where[] = 'type = %s';
|
||||
$params[] = $type;
|
||||
}
|
||||
if ($user_id > 0) {
|
||||
$where[] = 'user_id = %d';
|
||||
$params[] = $user_id;
|
||||
}
|
||||
if ($date_from !== '') {
|
||||
$where[] = 'created_at >= %s';
|
||||
$params[] = $date_from . ' 00:00:00';
|
||||
}
|
||||
if ($date_to !== '') {
|
||||
$where[] = 'created_at <= %s';
|
||||
$params[] = $date_to . ' 23:59:59';
|
||||
}
|
||||
|
||||
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
||||
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
$items = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$item = $this->build_rebate_admin_item($row);
|
||||
if ($keyword !== '' && mb_stripos(implode(' ', array_filter([
|
||||
(string) ($item['order_number'] ?? ''),
|
||||
(string) ($item['note'] ?? ''),
|
||||
(string) ($item['user']['display_name'] ?? ''),
|
||||
(string) ($item['source_user']['display_name'] ?? ''),
|
||||
])), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$stats = AKSRT_Rebate::get_instance()->get_stats();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values(array_slice($items, $offset, $per_page)),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'max_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
||||
'stats' => $stats,
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
||||
['label' => __('待结算', 'aksrt'), 'value' => 'pending'],
|
||||
['label' => __('已结算', 'aksrt'), 'value' => 'settled'],
|
||||
['label' => __('已取消', 'aksrt'), 'value' => 'cancelled'],
|
||||
],
|
||||
'types' => [
|
||||
['label' => __('全部类型', 'aksrt'), 'value' => ''],
|
||||
['label' => __('订单返佣', 'aksrt'), 'value' => 'order'],
|
||||
['label' => __('邀请返佣', 'aksrt'), 'value' => 'invite'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/rebate/{id}/settle — 结算返佣
|
||||
*/
|
||||
public function settle_rebate(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Rebate')) {
|
||||
return $this->error('返佣系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_Rebate::get_instance()->settle($id);
|
||||
|
||||
if ($success) {
|
||||
$this->append_rebate_process_log($id, 'settled', $note);
|
||||
return $this->success(['message' => __('返佣已结算', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('结算失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /payment/rebate/{id}/cancel — 取消返佣
|
||||
*/
|
||||
public function cancel_rebate(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Rebate')) {
|
||||
return $this->error('返佣系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_Rebate::get_instance()->cancel($id);
|
||||
|
||||
if ($success) {
|
||||
$this->append_rebate_process_log($id, 'cancelled', $note);
|
||||
return $this->success(['message' => __('返佣已取消', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('操作失败', 'aksrt'));
|
||||
}
|
||||
|
||||
public function get_rebate_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$table = AKSRT_DB_Migrator::table('rebate_log');
|
||||
|
||||
if ($id <= 0 || !$table) {
|
||||
return $this->error(__('返佣记录不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $id), ARRAY_A);
|
||||
if (!$row) {
|
||||
return $this->error(__('返佣记录不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'record' => $this->build_rebate_admin_item($row),
|
||||
'history' => $this->get_rebate_process_log($id),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function build_charge_card_admin_item(array $row): array {
|
||||
$used_by = (int) ($row['used_by'] ?? 0);
|
||||
$user = $used_by > 0 ? get_userdata($used_by) : null;
|
||||
$status = sanitize_key((string) ($row['status'] ?? 'active'));
|
||||
$statusMap = [
|
||||
'active' => __('未使用', 'aksrt'),
|
||||
'used' => __('已使用', 'aksrt'),
|
||||
'expired' => __('已过期', 'aksrt'),
|
||||
'disabled' => __('已禁用', 'aksrt'),
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'code' => (string) ($row['code'] ?? ''),
|
||||
'amount' => (float) ($row['amount'] ?? 0),
|
||||
'balance' => (float) ($row['balance'] ?? 0),
|
||||
'status' => $status,
|
||||
'status_label' => $statusMap[$status] ?? $status,
|
||||
'used_by' => $used_by,
|
||||
'used_at' => (string) ($row['used_at'] ?? ''),
|
||||
'expires_at' => (string) ($row['expires_at'] ?? ''),
|
||||
'created_at' => (string) ($row['created_at'] ?? ''),
|
||||
'note' => (string) ($row['note'] ?? ''),
|
||||
'used_by_user' => $user instanceof WP_User ? [
|
||||
'id' => (int) $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'email' => $user->user_email,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function build_rebate_admin_item(array $row): array {
|
||||
$user_id = (int) ($row['user_id'] ?? 0);
|
||||
$source_user_id = (int) ($row['source_user_id'] ?? 0);
|
||||
$user = $user_id > 0 ? get_userdata($user_id) : null;
|
||||
$source_user = $source_user_id > 0 ? get_userdata($source_user_id) : null;
|
||||
$status = sanitize_key((string) ($row['status'] ?? 'pending'));
|
||||
$type = sanitize_key((string) ($row['type'] ?? 'order'));
|
||||
|
||||
$statusMap = [
|
||||
'pending' => __('待结算', 'aksrt'),
|
||||
'settled' => __('已结算', 'aksrt'),
|
||||
'cancelled' => __('已取消', 'aksrt'),
|
||||
];
|
||||
$typeMap = [
|
||||
'order' => __('订单返佣', 'aksrt'),
|
||||
'invite' => __('邀请返佣', 'aksrt'),
|
||||
];
|
||||
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'order_number' => (string) ($row['order_number'] ?? ''),
|
||||
'amount' => (float) ($row['amount'] ?? 0),
|
||||
'status' => $status,
|
||||
'status_label' => $statusMap[$status] ?? $status,
|
||||
'type' => $type,
|
||||
'type_label' => $typeMap[$type] ?? $type,
|
||||
'created_at' => (string) ($row['created_at'] ?? ''),
|
||||
'settled_at' => (string) ($row['settled_at'] ?? ''),
|
||||
'note' => (string) ($row['note'] ?? ''),
|
||||
'history_count' => count($this->get_rebate_process_log((int) ($row['id'] ?? 0))),
|
||||
'user' => $user instanceof WP_User ? [
|
||||
'id' => (int) $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'email' => $user->user_email,
|
||||
] : null,
|
||||
'source_user' => $source_user instanceof WP_User ? [
|
||||
'id' => (int) $source_user->ID,
|
||||
'display_name' => $source_user->display_name,
|
||||
'email' => $source_user->user_email,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function append_rebate_process_log(int $rebate_id, string $event, string $note = ''): void {
|
||||
$log = get_option('aksrt_rebate_process_log', []);
|
||||
if (!is_array($log)) {
|
||||
$log = [];
|
||||
}
|
||||
|
||||
$rebate_id = (int) $rebate_id;
|
||||
if (!isset($log[$rebate_id]) || !is_array($log[$rebate_id])) {
|
||||
$log[$rebate_id] = [];
|
||||
}
|
||||
|
||||
$log[$rebate_id][] = [
|
||||
'event' => sanitize_key($event),
|
||||
'note' => sanitize_textarea_field($note),
|
||||
'operator' => get_current_user_id(),
|
||||
'operator_name' => ($user = wp_get_current_user()) ? $user->display_name : '',
|
||||
'time' => current_time('mysql'),
|
||||
];
|
||||
|
||||
update_option('aksrt_rebate_process_log', $log, false);
|
||||
}
|
||||
|
||||
private function get_rebate_process_log(int $rebate_id): array {
|
||||
$log = get_option('aksrt_rebate_process_log', []);
|
||||
if (!is_array($log) || !isset($log[$rebate_id]) || !is_array($log[$rebate_id])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values($log[$rebate_id]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Permissions Controller
|
||||
*
|
||||
* Owns REST routes for the "功能&权限" domain:
|
||||
* Captcha, moderation, user permissions.
|
||||
*
|
||||
* Previously these routes were registered under the User Controller.
|
||||
* This controller provides proper domain separation.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Permissions_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Capability matrix
|
||||
register_rest_route(self::API_NAMESPACE, '/permissions/capabilities', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_capabilities'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/permissions/capabilities', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'save_capabilities'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_capabilities(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return $this->error(__('能力矩阵模块不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$caps = AKSRT_Settings::get_user_capabilities();
|
||||
$defs = AKSRT_Settings::cap_definitions();
|
||||
$columns = AKSRT_Settings::cap_role_columns();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'columns' => $this->get_capability_column_labels(),
|
||||
'groups' => $this->build_capability_groups($defs, $caps, $columns),
|
||||
'summary' => [
|
||||
'cap_count' => count($defs),
|
||||
'group_count' => 3,
|
||||
'column_count' => count($columns),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function save_capabilities(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return $this->error(__('能力矩阵模块不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$input = $params['user_caps'] ?? null;
|
||||
|
||||
if (!is_array($input)) {
|
||||
return $this->error(__('无效的能力矩阵数据', 'aksrt'), 422);
|
||||
}
|
||||
|
||||
$defs = AKSRT_Settings::cap_definitions();
|
||||
$columns = AKSRT_Settings::cap_role_columns();
|
||||
$clean = [];
|
||||
|
||||
foreach ($defs as $cap_id => $def) {
|
||||
$row = is_array($input[$cap_id] ?? null) ? $input[$cap_id] : [];
|
||||
$clean[$cap_id] = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (in_array($column, ['level', 'vip'], true)) {
|
||||
$value = isset($row[$column]) ? (int) $row[$column] : -1;
|
||||
$max = $column === 'level' ? 10 : 2;
|
||||
$clean[$cap_id][$column] = max(-1, min($max, $value));
|
||||
} else {
|
||||
$clean[$cap_id][$column] = !empty($row[$column]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$all = AKSRT_Settings::get_all();
|
||||
$all['user_caps'] = $clean;
|
||||
update_option('aksrt_settings', $all);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success([
|
||||
'message' => __('能力矩阵已保存', 'aksrt'),
|
||||
'data' => [
|
||||
'user_caps' => $clean,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
private function build_capability_groups(array $defs, array $caps, array $columns): array {
|
||||
$buckets = [
|
||||
'user' => [
|
||||
'key' => 'user',
|
||||
'title' => __('用户治理', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
'post' => [
|
||||
'key' => 'post',
|
||||
'title' => __('投稿权限', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
'comment' => [
|
||||
'key' => 'comment',
|
||||
'title' => __('评论权限', 'aksrt'),
|
||||
'items' => [],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($defs as $cap_id => $def) {
|
||||
$bucket_key = 'user';
|
||||
if (str_starts_with($cap_id, 'comment_')) {
|
||||
$bucket_key = 'comment';
|
||||
} elseif (str_starts_with($cap_id, 'new_post_')) {
|
||||
$bucket_key = 'post';
|
||||
}
|
||||
|
||||
$excludes = $def['exclude_roles'] ?? [];
|
||||
$only_roles = $def['roles'] ?? null;
|
||||
$values = $caps[$cap_id] ?? [];
|
||||
|
||||
$visible_columns = [];
|
||||
foreach ($columns as $column) {
|
||||
if (in_array($column, $excludes, true)) {
|
||||
continue;
|
||||
}
|
||||
if ($only_roles !== null && !in_array($column, $only_roles, true) && !in_array($column, ['all', 'logged', 'level', 'vip', 'auth'], true)) {
|
||||
continue;
|
||||
}
|
||||
$visible_columns[] = $column;
|
||||
}
|
||||
|
||||
$buckets[$bucket_key]['items'][] = [
|
||||
'cap_id' => $cap_id,
|
||||
'label' => $def['label'],
|
||||
'visible_columns' => $visible_columns,
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
|
||||
return array_values($buckets);
|
||||
}
|
||||
|
||||
private function get_capability_column_labels(): array {
|
||||
return [
|
||||
'all' => __('所有人', 'aksrt'),
|
||||
'logged' => __('已登录', 'aksrt'),
|
||||
'level' => __('等级≥', 'aksrt'),
|
||||
'vip' => __('VIP≥', 'aksrt'),
|
||||
'auth' => __('已认证', 'aksrt'),
|
||||
'moderator' => __('版主', 'aksrt'),
|
||||
'plate_author' => __('超级版主', 'aksrt'),
|
||||
'cat_moderator' => __('分区版主', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Post List Controller
|
||||
*
|
||||
* Owns section-level REST routes for the "文章&列表" domain.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Post_List_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
$this->register_section_routes('post-list', 'post_list');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin Schema Provider
|
||||
*
|
||||
* Defines the canonical PHP-side settings contract:
|
||||
* domain -> section -> group -> field.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Provider {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
private $domain_schemas = [];
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function normalize_domain(string $domain): string {
|
||||
$domain = trim(str_replace('-', '_', $domain));
|
||||
return $domain !== '' ? $domain : 'global';
|
||||
}
|
||||
|
||||
public function normalize_section(string $section): string {
|
||||
return trim($section);
|
||||
}
|
||||
|
||||
public function get_domain_keys(): array {
|
||||
return [
|
||||
'global',
|
||||
'page_display',
|
||||
'post_list',
|
||||
'permissions',
|
||||
'users',
|
||||
'payment',
|
||||
'extensions',
|
||||
'tools',
|
||||
'shop',
|
||||
'forum',
|
||||
];
|
||||
}
|
||||
|
||||
public function get_schema(string $domain): ?array {
|
||||
$domain = $this->normalize_domain($domain);
|
||||
|
||||
if (isset($this->domain_schemas[$domain])) {
|
||||
return $this->domain_schemas[$domain];
|
||||
}
|
||||
|
||||
$builder_map = $this->get_schema_builder_map();
|
||||
if (!isset($builder_map[$domain])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $builder_map[$domain];
|
||||
$this->domain_schemas[$domain] = $builder::build();
|
||||
|
||||
return $this->domain_schemas[$domain];
|
||||
}
|
||||
|
||||
public function get_sections(string $domain): array {
|
||||
$schema = $this->get_schema($domain);
|
||||
return is_array($schema['sections'] ?? null) ? $schema['sections'] : [];
|
||||
}
|
||||
|
||||
public function get_section_schema(string $domain, string $section): ?array {
|
||||
$sections = $this->get_sections($domain);
|
||||
$section = $this->normalize_section($section);
|
||||
return $sections[$section] ?? null;
|
||||
}
|
||||
|
||||
public function get_option_key(string $domain, string $section = ''): string {
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
return (string) ($schema['option_key'] ?? 'aksrt_settings');
|
||||
}
|
||||
|
||||
$primary = [
|
||||
'global' => 'aksrt_settings',
|
||||
'page_display' => 'aksrt_settings',
|
||||
'post_list' => 'aksrt_settings',
|
||||
'permissions' => 'aksrt_settings',
|
||||
'users' => 'aksrt_settings',
|
||||
'payment' => 'aksrt_settings',
|
||||
'extensions' => 'aksrt_settings',
|
||||
'tools' => 'aksrt_settings',
|
||||
'shop' => 'aksrt_settings',
|
||||
'forum' => 'aksrt_settings',
|
||||
];
|
||||
|
||||
return $primary[$this->normalize_domain($domain)] ?? 'aksrt_settings';
|
||||
}
|
||||
|
||||
public function get_defaults(string $domain, string $section = ''): array {
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
return $schema ? $this->collect_defaults($schema) : [];
|
||||
}
|
||||
|
||||
$defaults = [];
|
||||
foreach ($this->get_sections($domain) as $section_schema) {
|
||||
$defaults = array_merge($defaults, $this->collect_defaults($section_schema));
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
public function get_allowed_fields(string $domain, string $section = ''): array {
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
return $schema ? $this->collect_allowed_fields($schema) : [];
|
||||
}
|
||||
|
||||
$allowed = [];
|
||||
foreach ($this->get_sections($domain) as $section_schema) {
|
||||
$allowed = array_merge($allowed, $this->collect_allowed_fields($section_schema));
|
||||
}
|
||||
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
public function validate(string $domain, array $data, string $section = ''): array {
|
||||
$schemas = [];
|
||||
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
if (!$schema) {
|
||||
return ['valid' => false, 'errors' => ['Unknown section']];
|
||||
}
|
||||
$schemas[] = $schema;
|
||||
} else {
|
||||
$schemas = array_values($this->get_sections($domain));
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach ($schemas as $schema) {
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
$key = $field['key'];
|
||||
if (!array_key_exists($key, $data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $data[$key];
|
||||
if (!empty($field['required']) && ($value === '' || $value === null)) {
|
||||
$errors[] = sprintf('%s is required', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $field['type'] ?? 'input';
|
||||
if (($type === 'number' || $type === 'slider') && !is_numeric($value)) {
|
||||
$errors[] = sprintf('%s must be a number', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'checkbox' && !is_array($value)) {
|
||||
$errors[] = sprintf('%s must be an array', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type === 'repeater' && !is_array($value)) {
|
||||
$errors[] = sprintf('%s must be an array of items', $field['label'] ?? $key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($field['min']) && is_numeric($value) && $value < $field['min']) {
|
||||
$errors[] = sprintf('%s must be at least %s', $field['label'] ?? $key, $field['min']);
|
||||
}
|
||||
|
||||
if (isset($field['max']) && is_numeric($value) && $value > $field['max']) {
|
||||
$errors[] = sprintf('%s must be at most %s', $field['label'] ?? $key, $field['max']);
|
||||
}
|
||||
|
||||
if (in_array($type, ['select', 'radio'], true) && !empty($field['options'])) {
|
||||
$valid_values = array_column($field['options'], 'value');
|
||||
if (!in_array($value, $valid_values, true)) {
|
||||
$errors[] = sprintf('%s has an invalid value', $field['label'] ?? $key);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'checkbox' && !empty($field['options'])) {
|
||||
$valid_values = array_column($field['options'], 'value');
|
||||
foreach ((array) $value as $item) {
|
||||
if (!in_array($item, $valid_values, true)) {
|
||||
$errors[] = sprintf('%s has an invalid value', $field['label'] ?? $key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['valid' => empty($errors), 'errors' => $errors];
|
||||
}
|
||||
|
||||
public function read_settings(string $domain, string $section = ''): array {
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
if (!$schema) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->read_section_settings($schema);
|
||||
}
|
||||
|
||||
$settings = [];
|
||||
foreach ($this->get_sections($domain) as $section_schema) {
|
||||
$settings = array_merge($settings, $this->read_section_settings($section_schema));
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
public function write_settings(string $domain, array $data, string $section = ''): bool {
|
||||
if ($section !== '') {
|
||||
$schema = $this->get_section_schema($domain, $section);
|
||||
if (!$schema) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->write_section_settings($schema, $data);
|
||||
}
|
||||
|
||||
$result = true;
|
||||
foreach ($this->get_sections($domain) as $section_schema) {
|
||||
$allowed = array_keys($this->collect_allowed_fields($section_schema));
|
||||
$payload = array_intersect_key($data, array_flip($allowed));
|
||||
if ($payload === []) {
|
||||
continue;
|
||||
}
|
||||
$result = $this->write_section_settings($section_schema, $payload) && $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function reset_to_defaults(string $domain, string $section = ''): bool {
|
||||
if ($section !== '') {
|
||||
return $this->write_settings($domain, $this->get_defaults($domain, $section), $section);
|
||||
}
|
||||
|
||||
return $this->write_settings($domain, $this->get_defaults($domain));
|
||||
}
|
||||
|
||||
private function get_schema_builder_map(): array {
|
||||
return [
|
||||
'global' => 'AKSRT_Admin_Schema_Global',
|
||||
'page_display' => 'AKSRT_Admin_Schema_Page_Display',
|
||||
'post_list' => 'AKSRT_Admin_Schema_Post_List',
|
||||
'permissions' => 'AKSRT_Admin_Schema_Permissions',
|
||||
'users' => 'AKSRT_Admin_Schema_Users',
|
||||
'payment' => 'AKSRT_Admin_Schema_Payment',
|
||||
'extensions' => 'AKSRT_Admin_Schema_Extensions',
|
||||
'tools' => 'AKSRT_Admin_Schema_Tools',
|
||||
'shop' => 'AKSRT_Admin_Schema_Shop',
|
||||
'forum' => 'AKSRT_Admin_Schema_Forum',
|
||||
];
|
||||
}
|
||||
|
||||
private function read_section_settings(array $schema): array {
|
||||
$option_key = (string) ($schema['option_key'] ?? 'aksrt_settings');
|
||||
$stored = get_option($option_key, []);
|
||||
if (!is_array($stored)) {
|
||||
$stored = [];
|
||||
}
|
||||
|
||||
$data = $this->collect_defaults($schema);
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
if (array_key_exists($field['key'], $stored)) {
|
||||
$data[$field['key']] = $stored[$field['key']];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function write_section_settings(array $schema, array $data): bool {
|
||||
$option_key = (string) ($schema['option_key'] ?? 'aksrt_settings');
|
||||
$stored = get_option($option_key, []);
|
||||
if (!is_array($stored)) {
|
||||
$stored = [];
|
||||
}
|
||||
|
||||
$allowed = $this->collect_allowed_fields($schema);
|
||||
foreach ($allowed as $key => $type) {
|
||||
if (!array_key_exists($key, $data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->sanitize_value($data[$key], $type);
|
||||
$stored[$key] = $value;
|
||||
}
|
||||
|
||||
$updated = update_option($option_key, $stored);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
if (isset($stored['site_favicon'])) {
|
||||
AKSRT_Settings::sync_native_site_icon((string) $stored['site_favicon']);
|
||||
}
|
||||
|
||||
return $updated || true;
|
||||
}
|
||||
|
||||
private function collect_defaults(array $schema): array {
|
||||
$defaults = [];
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
if (array_key_exists('default', $field)) {
|
||||
$defaults[$field['key']] = $field['default'];
|
||||
}
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
private function collect_allowed_fields(array $schema): array {
|
||||
$allowed = [];
|
||||
foreach ($this->get_all_fields($schema) as $field) {
|
||||
$allowed[$field['key']] = $field['sanitize_type'] ?? $this->infer_sanitize_type($field['type'] ?? 'input');
|
||||
}
|
||||
return $allowed;
|
||||
}
|
||||
|
||||
private function get_all_fields(array $schema): array {
|
||||
$fields = [];
|
||||
foreach ($schema['groups'] ?? [] as $group) {
|
||||
foreach ($group['fields'] ?? [] as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
private function sanitize_value($value, string $type) {
|
||||
switch ($type) {
|
||||
case 'html':
|
||||
return wp_kses_post((string) $value);
|
||||
case 'code':
|
||||
return is_string($value) ? wp_unslash($value) : '';
|
||||
case 'url':
|
||||
return esc_url_raw((string) $value);
|
||||
case 'int':
|
||||
return (int) $value;
|
||||
case 'bool':
|
||||
return !empty($value);
|
||||
case 'float':
|
||||
return (float) $value;
|
||||
case 'array':
|
||||
return is_array($value) ? $value : [];
|
||||
default:
|
||||
return sanitize_text_field((string) $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function infer_sanitize_type(string $field_type): string {
|
||||
$map = [
|
||||
'switch' => 'bool',
|
||||
'input' => 'string',
|
||||
'text' => 'string',
|
||||
'textarea' => 'string',
|
||||
'number' => 'int',
|
||||
'slider' => 'int',
|
||||
'select' => 'string',
|
||||
'radio' => 'string',
|
||||
'checkbox' => 'array',
|
||||
'repeater' => 'array',
|
||||
'color' => 'string',
|
||||
'icon' => 'string',
|
||||
'media' => 'url',
|
||||
'code' => 'code',
|
||||
];
|
||||
|
||||
return $map[$field_type] ?? 'string';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop coupon and gateway operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Coupon_Gateway_Operations {
|
||||
|
||||
/**
|
||||
* GET /shop/coupons — 优惠券列表
|
||||
*/
|
||||
public function get_coupons(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Coupon')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'total_pages' => 1]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(100, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$search = sanitize_text_field((string) $request->get_param('search'));
|
||||
|
||||
$result = AKSRT_Coupon::get_instance()->get_list([
|
||||
'status' => $status,
|
||||
'search' => $search,
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
]);
|
||||
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/coupons — 创建优惠券
|
||||
*/
|
||||
public function create_coupon(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Coupon')) {
|
||||
return $this->error('优惠券系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$code = sanitize_text_field((string) ($params['code'] ?? ''));
|
||||
|
||||
if ($code === '') {
|
||||
return $this->error('优惠码不能为空');
|
||||
}
|
||||
|
||||
$id = AKSRT_Coupon::get_instance()->create($params);
|
||||
|
||||
if ($id > 0) {
|
||||
return $this->success([
|
||||
'message' => __('优惠券已创建', 'aksrt'),
|
||||
'data' => ['id' => $id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
return $this->error(__('创建失败(可能优惠码已存在)', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/coupons/{id}/disable — 禁用优惠券
|
||||
*/
|
||||
public function disable_coupon(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Coupon')) {
|
||||
return $this->error('优惠券系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_Coupon::get_instance()->disable($id);
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('优惠券已禁用', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('操作失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /shop/gateways — 支付网关状态列表
|
||||
*/
|
||||
public function get_gateways(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Payment_Gateway')) {
|
||||
return $this->success(['data' => ['gateways' => []]]);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'gateways' => AKSRT_Payment_Gateway::get_instance()->get_status_summary(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop frontend flow operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations {
|
||||
|
||||
public function check_logged_in_permission(): bool {
|
||||
return is_user_logged_in();
|
||||
}
|
||||
|
||||
public function preview_checkout(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->preview_checkout(get_current_user_id(), $this->get_json_params($request));
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '结算预览失败');
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result['data']]);
|
||||
}
|
||||
|
||||
public function create_frontend_order(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->create_checkout_order(get_current_user_id(), $this->get_json_params($request));
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '订单创建失败');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('订单已创建', 'aksrt'),
|
||||
'data' => $result['data'],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function get_frontend_orders(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
|
||||
$view = AKSRT_Shop::get_instance()->get_order_center_view(get_current_user_id(), [
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return $this->success(['data' => $view]);
|
||||
}
|
||||
|
||||
public function get_frontend_order(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$order = AKSRT_Shop::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
|
||||
if (!$order) {
|
||||
return $this->error('订单不存在', 404);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $order]);
|
||||
}
|
||||
|
||||
public function pay_frontend_order(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance'));
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->pay_order(get_current_user_id(), $order_number, $payment_method);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '支付失败');
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result['data']]);
|
||||
}
|
||||
|
||||
public function cancel_frontend_order(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$result = AKSRT_Shop::get_instance()->cancel_order(get_current_user_id(), $order_number);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '取消订单失败');
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result['data']]);
|
||||
}
|
||||
|
||||
public function create_frontend_after_sale(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$params['order_number'] = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$result = AKSRT_Shop::get_instance()->create_after_sale_request(get_current_user_id(), $params);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '提交售后失败');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('售后申请已提交', 'aksrt'),
|
||||
'data' => $result['data'],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function get_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
$shop = AKSRT_Shop::get_instance();
|
||||
$cart = $shop->get_cart($user_id);
|
||||
|
||||
$items = [];
|
||||
foreach ($cart as $pid => $opts_map) {
|
||||
$product = $shop->build_product_card_view((int) $pid);
|
||||
if (empty($product) || !is_array($opts_map)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($opts_map as $opt_str => $qty) {
|
||||
$items[] = [
|
||||
'product_id' => (int) $pid,
|
||||
'options_string' => $opt_str === '_default' ? '' : $opt_str,
|
||||
'quantity' => (int) $qty,
|
||||
'product' => $product,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'count' => $shop->get_cart_count($user_id),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_cart_count(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
return $this->success(['data' => ['count' => AKSRT_Shop::get_instance()->get_cart_count($user_id)]]);
|
||||
}
|
||||
|
||||
public function handle_add_to_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$product_id = (int) ($params['product_id'] ?? 0);
|
||||
$options_string = sanitize_text_field((string) ($params['options_string'] ?? ''));
|
||||
$count = max(1, (int) ($params['count'] ?? 1));
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->add_to_cart(get_current_user_id(), $product_id, $options_string, $count);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return $this->error($result['error']);
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function handle_reduce_from_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$product_id = (int) ($params['product_id'] ?? 0);
|
||||
$options_string = sanitize_text_field((string) ($params['options_string'] ?? ''));
|
||||
$count = max(1, (int) ($params['count'] ?? 1));
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->reduce_from_cart(get_current_user_id(), $product_id, $options_string, $count);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return $this->error($result['error']);
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function handle_remove_from_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$product_id = (int) ($params['product_id'] ?? 0);
|
||||
$options_string = sanitize_text_field((string) ($params['options_string'] ?? ''));
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->remove_from_cart(get_current_user_id(), $product_id, $options_string);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return $this->error($result['error']);
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function handle_remove_multi_from_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$items = is_array($params['items'] ?? null) ? $params['items'] : [];
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->remove_multi_from_cart(get_current_user_id(), $items);
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return $this->error($result['error']);
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function handle_empty_cart(WP_REST_Request $request): WP_REST_Response {
|
||||
$result = AKSRT_Shop::get_instance()->empty_cart(get_current_user_id());
|
||||
|
||||
if (isset($result['error'])) {
|
||||
return $this->error($result['error']);
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
public function get_frontend_addresses(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->success(['data' => ['addresses' => []]]);
|
||||
}
|
||||
|
||||
$addresses = AKSRT_Shop::get_instance()->get_address_book(get_current_user_id());
|
||||
return $this->success(['data' => ['addresses' => $addresses]]);
|
||||
}
|
||||
|
||||
public function create_frontend_address(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$user_id = get_current_user_id();
|
||||
$result = AKSRT_Shop::get_instance()->add_address($user_id, $params);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '添加地址失败');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('地址已添加', 'aksrt'),
|
||||
'data' => ['address' => $result['address']],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function update_frontend_address(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$address_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$user_id = get_current_user_id();
|
||||
$result = AKSRT_Shop::get_instance()->update_address($user_id, $address_id, $params);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '更新地址失败');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('地址已更新', 'aksrt'),
|
||||
'data' => ['address' => $result['address']],
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete_frontend_address(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$address_id = (int) $request->get_param('id');
|
||||
$user_id = get_current_user_id();
|
||||
$result = AKSRT_Shop::get_instance()->delete_address($user_id, $address_id);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '删除地址失败');
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('地址已删除', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function set_default_frontend_address(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$address_id = (int) $request->get_param('id');
|
||||
$user_id = get_current_user_id();
|
||||
$result = AKSRT_Shop::get_instance()->set_default_address($user_id, $address_id);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '设置默认地址失败');
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('已设为默认地址', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function create_frontend_product(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$params['author_id'] = get_current_user_id();
|
||||
$frontend_request = new WP_REST_Request('POST', '/');
|
||||
$frontend_request->set_body(wp_json_encode($params));
|
||||
|
||||
return $this->create_product($frontend_request);
|
||||
}
|
||||
|
||||
public function update_frontend_product(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$post = get_post($id);
|
||||
if (!$post || $post->post_type !== 'aksrt_product') {
|
||||
return $this->error('商品不存在', 404);
|
||||
}
|
||||
|
||||
if ((int) $post->post_author !== get_current_user_id() && !current_user_can('manage_options')) {
|
||||
return $this->error('没有权限编辑该商品', 403);
|
||||
}
|
||||
|
||||
return $this->update_product($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop fulfillment operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Fulfillment_Operations {
|
||||
|
||||
/**
|
||||
* GET /shop/after-sale — 售后工单列表
|
||||
*/
|
||||
public function get_after_sale_list(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_After_Sale')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(100, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$stage = sanitize_key((string) $request->get_param('stage'));
|
||||
$order = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$type = sanitize_key((string) $request->get_param('type'));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$result = AKSRT_After_Sale::get_instance()->get_list([
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
'status' => $status,
|
||||
'stage' => $stage,
|
||||
'order_number' => $order,
|
||||
'type' => $type,
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
'include_order_context' => true,
|
||||
]);
|
||||
|
||||
$stats = AKSRT_After_Sale::get_instance()->get_stats();
|
||||
|
||||
return $this->success(['data' => array_merge($result, [
|
||||
'stats' => $stats,
|
||||
'filters' => [
|
||||
'status' => $status,
|
||||
'stage' => $stage,
|
||||
'order_number' => $order,
|
||||
'type' => $type,
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
],
|
||||
'options' => [
|
||||
'status' => AKSRT_After_Sale::get_status_options(),
|
||||
'stage' => AKSRT_After_Sale::get_stage_options(),
|
||||
'type' => AKSRT_After_Sale::get_type_options(),
|
||||
],
|
||||
])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/after-sale — 创建售后工单
|
||||
*/
|
||||
public function create_after_sale(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_After_Sale')) {
|
||||
return $this->error('售后系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$order_number = sanitize_text_field((string) ($params['order_number'] ?? ''));
|
||||
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
||||
|
||||
if ($order_number === '' || $reason === '') {
|
||||
return $this->error('订单号和原因不能为空');
|
||||
}
|
||||
|
||||
$order = class_exists('AKSRT_Order_Repository')
|
||||
? AKSRT_Order_Repository::get_instance()->get($order_number)
|
||||
: null;
|
||||
|
||||
if (!$order) {
|
||||
return $this->error(__('订单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$params['user_id'] = (int) ($params['user_id'] ?? ($order['user_id'] ?? 0));
|
||||
if ($params['user_id'] <= 0) {
|
||||
return $this->error(__('订单未绑定有效用户', 'aksrt'));
|
||||
}
|
||||
|
||||
$id = AKSRT_After_Sale::get_instance()->create($params);
|
||||
|
||||
if ($id > 0) {
|
||||
return $this->success([
|
||||
'message' => __('售后工单已创建', 'aksrt'),
|
||||
'data' => ['id' => $id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
return $this->error(__('创建失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/after-sale/{id}/update-status — 更新售后工单状态
|
||||
*/
|
||||
public function update_after_sale_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_After_Sale')) {
|
||||
return $this->error('售后系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$new_status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['operator_note'] ?? ''));
|
||||
|
||||
if ($id <= 0 || $new_status === '') {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_After_Sale::get_instance()->update_status(
|
||||
$id,
|
||||
$new_status,
|
||||
get_current_user_id(),
|
||||
$note
|
||||
);
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('状态已更新', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('更新失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /shop/shipping — 发货记录列表
|
||||
*/
|
||||
public function get_shipping_list(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shipping')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'max_pages' => 1]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(100, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$order = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
$tracking_no = sanitize_text_field((string) $request->get_param('tracking_no'));
|
||||
|
||||
$result = AKSRT_Shipping::get_instance()->get_list([
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
'status' => $status,
|
||||
'order_number' => $order,
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
'tracking_no' => $tracking_no,
|
||||
'include_order_context' => true,
|
||||
]);
|
||||
|
||||
$stats = AKSRT_Shipping::get_instance()->get_stats();
|
||||
|
||||
return $this->success(['data' => array_merge($result, [
|
||||
'stats' => $stats,
|
||||
'filters' => [
|
||||
'status' => $status,
|
||||
'order_number' => $order,
|
||||
'date_from' => $date_from,
|
||||
'date_to' => $date_to,
|
||||
'tracking_no' => $tracking_no,
|
||||
],
|
||||
'options' => [
|
||||
'status' => AKSRT_Shipping::get_status_options(),
|
||||
'carrier' => AKSRT_Shipping::get_carrier_options(),
|
||||
],
|
||||
])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/shipping — 创建发货记录
|
||||
*/
|
||||
public function create_shipping(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shipping')) {
|
||||
return $this->error('物流系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$order_number = sanitize_text_field((string) ($params['order_number'] ?? ''));
|
||||
$carrier = sanitize_text_field((string) ($params['carrier'] ?? ''));
|
||||
$tracking_no = sanitize_text_field((string) ($params['tracking_no'] ?? ''));
|
||||
|
||||
if ($order_number === '' || $carrier === '' || $tracking_no === '') {
|
||||
return $this->error('订单号、快递公司和物流单号不能为空');
|
||||
}
|
||||
|
||||
$order = class_exists('AKSRT_Order_Repository')
|
||||
? AKSRT_Order_Repository::get_instance()->get($order_number)
|
||||
: null;
|
||||
|
||||
if (!$order) {
|
||||
return $this->error(__('订单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$params['operator_id'] = get_current_user_id();
|
||||
|
||||
$id = AKSRT_Shipping::get_instance()->create($params);
|
||||
|
||||
if ($id > 0) {
|
||||
return $this->success([
|
||||
'message' => __('发货记录已创建', 'aksrt'),
|
||||
'data' => ['id' => $id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
return $this->error(__('创建失败', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/shipping/{id}/update-status — 更新发货状态
|
||||
*/
|
||||
public function update_shipping_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shipping')) {
|
||||
return $this->error('物流系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$new_status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
|
||||
if ($id <= 0 || $new_status === '') {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = AKSRT_Shipping::get_instance()->update_status($id, $new_status, $note, get_current_user_id());
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('状态已更新', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('更新失败', 'aksrt'));
|
||||
}
|
||||
|
||||
public function get_after_sale_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_After_Sale')) {
|
||||
return $this->error('售后系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$record = AKSRT_After_Sale::get_instance()->get_with_order_context($id);
|
||||
if (!$record) {
|
||||
return $this->error(__('售后工单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'record' => $record,
|
||||
'timeline' => $this->build_after_sale_timeline($record),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_shipping_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shipping')) {
|
||||
return $this->error('物流系统未启用', 500);
|
||||
}
|
||||
|
||||
$id = (int) $request->get_param('id');
|
||||
if ($id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$record = AKSRT_Shipping::get_instance()->get_with_order_context($id);
|
||||
if (!$record) {
|
||||
return $this->error(__('发货记录不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'record' => $record,
|
||||
'timeline' => $this->build_shipping_timeline($record),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function batch_update_shipping_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shipping')) {
|
||||
return $this->error('物流系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$ids = is_array($params['ids'] ?? null) ? $params['ids'] : [];
|
||||
$status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
|
||||
if (empty($ids) || $status === '') {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$result = AKSRT_Shipping::get_instance()->batch_update_status($ids, $status, $note, get_current_user_id());
|
||||
|
||||
return $this->success([
|
||||
'message' => __('批量状态更新已执行', 'aksrt'),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
private function build_shipping_timeline(array $record): array {
|
||||
$timeline = [];
|
||||
|
||||
foreach ([
|
||||
'created_at' => __('创建发货记录', 'aksrt'),
|
||||
'updated_at' => __('更新物流状态', 'aksrt'),
|
||||
'delivered_at' => __('确认签收', 'aksrt'),
|
||||
] as $field => $label) {
|
||||
if (!empty($record[$field])) {
|
||||
$timeline[] = [
|
||||
'time' => (string) $record[$field],
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($timeline, static function ($a, $b) {
|
||||
return strcmp((string) ($a['time'] ?? ''), (string) ($b['time'] ?? ''));
|
||||
});
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
private function build_after_sale_timeline(array $record): array {
|
||||
$timeline = [];
|
||||
|
||||
foreach ([
|
||||
'created_at' => __('提交售后申请', 'aksrt'),
|
||||
'updated_at' => __('更新售后状态', 'aksrt'),
|
||||
'resolved_at' => __('售后处理结束', 'aksrt'),
|
||||
] as $field => $label) {
|
||||
if (!empty($record[$field])) {
|
||||
$timeline[] = [
|
||||
'time' => (string) $record[$field],
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
usort($timeline, static function ($a, $b) {
|
||||
return strcmp((string) ($a['time'] ?? ''), (string) ($b['time'] ?? ''));
|
||||
});
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop order operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Order_Operations {
|
||||
|
||||
/**
|
||||
* GET /shop/orders — 订单列表,支持 search、status、page、per_page
|
||||
*/
|
||||
public function get_orders(WP_REST_Request $request): WP_REST_Response {
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$search = sanitize_text_field((string) $request->get_param('search'));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
|
||||
if (class_exists('AKSRT_Order_Repository')) {
|
||||
$repo = AKSRT_Order_Repository::get_instance();
|
||||
|
||||
$args = [
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
];
|
||||
if ($status !== '' && $status !== 'all') {
|
||||
$args['status'] = $status;
|
||||
}
|
||||
|
||||
$orders = $repo->get_orders_admin($args, $search);
|
||||
$total = $repo->get_orders_count($args, $search);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $orders,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'total_pages' => max(1, (int) ceil($total / $per_page)),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error('订单仓库不可用', 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /shop/orders/{order_number} — 单个订单详情(含上下文)
|
||||
*/
|
||||
public function get_order(WP_REST_Request $request): WP_REST_Response {
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
|
||||
if (!class_exists('AKSRT_Order_Repository')) {
|
||||
return $this->error('订单服务不可用', 500);
|
||||
}
|
||||
|
||||
$repo = AKSRT_Order_Repository::get_instance();
|
||||
$order = $repo->get($order_number);
|
||||
if (!$order) {
|
||||
return $this->error('订单不存在', 404);
|
||||
}
|
||||
|
||||
$context = $repo->get_admin_order_context($order_number);
|
||||
$timeline = [];
|
||||
if (class_exists('AKSRT_Shop')) {
|
||||
$shipping_records = class_exists('AKSRT_Shipping') ? AKSRT_Shipping::get_instance()->get_by_order($order_number) : [];
|
||||
$after_sale_result = class_exists('AKSRT_After_Sale') ? AKSRT_After_Sale::get_instance()->get_list(['order_number' => $order_number, 'limit' => 20]) : ['items' => []];
|
||||
$after_sale_items = $after_sale_result['items'] ?? [];
|
||||
|
||||
$timeline = [];
|
||||
$date_fields = [
|
||||
'created_at' => '订单创建',
|
||||
'paid_at' => '完成支付',
|
||||
];
|
||||
foreach ($date_fields as $field => $label) {
|
||||
if (!empty($order[$field])) {
|
||||
$timeline[] = ['time' => $order[$field], 'title' => $label, 'type' => 'order'];
|
||||
}
|
||||
}
|
||||
foreach ($shipping_records as $record) {
|
||||
$timeline[] = [
|
||||
'time' => $record['created_at'] ?? '',
|
||||
'title' => '物流更新:' . ($record['status_label'] ?? $record['status'] ?? ''),
|
||||
'detail' => ($record['carrier_label'] ?? '') . ' / ' . ($record['tracking_no'] ?? ''),
|
||||
'type' => 'shipping',
|
||||
];
|
||||
}
|
||||
foreach ($after_sale_items as $record) {
|
||||
$timeline[] = [
|
||||
'time' => $record['created_at'] ?? '',
|
||||
'title' => '售后:' . ($record['type_label'] ?? '') . ' — ' . ($record['status_label'] ?? ''),
|
||||
'detail' => $record['reason'] ?? '',
|
||||
'type' => 'after_sale',
|
||||
];
|
||||
}
|
||||
usort($timeline, static function ($a, $b) {
|
||||
return strcmp((string) ($b['time'] ?? ''), (string) ($a['time'] ?? ''));
|
||||
});
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => $order,
|
||||
'context' => $context,
|
||||
'timeline' => $timeline,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/orders/update-status — 更新订单状态
|
||||
* Canonical field: order_number (前端必须发送 order_number)
|
||||
*/
|
||||
public function update_order_status(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$order_number = sanitize_text_field((string) ($params['order_number'] ?? ''));
|
||||
$new_status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
|
||||
if ($order_number === '' || $new_status === '') {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$success = false;
|
||||
if (class_exists('AKSRT_Order_Repository')) {
|
||||
$success = AKSRT_Order_Repository::get_instance()->update_status($order_number, $new_status, [
|
||||
'note' => $note,
|
||||
'force' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
return $this->success(['message' => __('订单状态已更新', 'aksrt')]);
|
||||
}
|
||||
return $this->error(__('更新失败', 'aksrt'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop product admin list operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Product_Admin_Columns {
|
||||
|
||||
/**
|
||||
* Register custom columns for the aksrt_product admin list table.
|
||||
*/
|
||||
public function init_product_admin_columns(): void {
|
||||
add_filter('manage_edit-aksrt_product_columns', [$this, 'product_columns']);
|
||||
add_action('manage_aksrt_product_posts_custom_column', [$this, 'render_product_column'], 10, 2);
|
||||
add_filter('manage_edit-aksrt_product_sortable_columns', [$this, 'sortable_product_columns']);
|
||||
add_action('quick_edit_custom_box', [$this, 'render_product_quick_edit'], 10, 2);
|
||||
add_action('bulk_edit_custom_box', [$this, 'render_product_quick_edit'], 10, 2);
|
||||
add_action('save_post_aksrt_product', [$this, 'save_product_quick_edit'], 10, 2);
|
||||
add_action('admin_footer-edit.php', [$this, 'product_quick_edit_script']);
|
||||
}
|
||||
|
||||
public function product_columns(array $columns): array {
|
||||
$new = [];
|
||||
foreach ($columns as $key => $label) {
|
||||
$new[$key] = $label;
|
||||
if ($key === 'title') {
|
||||
$new['product_price'] = __('价格', 'aksrt');
|
||||
$new['product_stock'] = __('库存', 'aksrt');
|
||||
$new['product_sales'] = __('销量', 'aksrt');
|
||||
$new['product_stats'] = __('综合数据', 'aksrt');
|
||||
$new['product_author_name'] = __('商家', 'aksrt');
|
||||
$new['product_discount'] = __('优惠活动', 'aksrt');
|
||||
}
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
|
||||
public function render_product_column(string $column, int $post_id): void {
|
||||
switch ($column) {
|
||||
case 'product_price':
|
||||
$price = get_post_meta($post_id, '_aksrt_price', true);
|
||||
echo esc_html(number_format_i18n((float) $price, 2));
|
||||
break;
|
||||
case 'product_stock':
|
||||
$stock = get_post_meta($post_id, '_aksrt_stock', true);
|
||||
$stock_type = get_post_meta($post_id, '_aksrt_stock_type', true);
|
||||
if ($stock_type === 'opts') {
|
||||
echo esc_html__('按选项', 'aksrt');
|
||||
} elseif ((int) $stock === -1) {
|
||||
echo esc_html__('无限', 'aksrt');
|
||||
} else {
|
||||
echo esc_html((string) (int) $stock);
|
||||
}
|
||||
break;
|
||||
case 'product_sales':
|
||||
echo esc_html((string) (int) get_post_meta($post_id, '_aksrt_sales', true));
|
||||
break;
|
||||
case 'product_stats':
|
||||
$views = (int) get_post_meta($post_id, '_aksrt_views', true);
|
||||
$fav = (int) get_post_meta($post_id, '_aksrt_favorite_count', true);
|
||||
printf(
|
||||
esc_html__('阅读 %1$d / 收藏 %2$d', 'aksrt'),
|
||||
$views,
|
||||
$fav
|
||||
);
|
||||
break;
|
||||
case 'product_author_name':
|
||||
$post = get_post($post_id);
|
||||
if ($post) {
|
||||
$author = get_userdata($post->post_author);
|
||||
echo esc_html($author ? $author->display_name : '—');
|
||||
}
|
||||
break;
|
||||
case 'product_discount':
|
||||
$terms = wp_get_object_terms($post_id, 'shop_discount', ['fields' => 'all']);
|
||||
if (!is_wp_error($terms) && !empty($terms)) {
|
||||
$term_ids = wp_list_pluck($terms, 'term_id');
|
||||
$names = wp_list_pluck($terms, 'name');
|
||||
echo '<span class="aksrt-col-data" data-discount-id="' . esc_attr((string) ($term_ids[0] ?? 0)) . '"></span>';
|
||||
echo esc_html(implode(', ', $names));
|
||||
} else {
|
||||
echo '<span class="aksrt-col-data" data-discount-id="0"></span>—';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function sortable_product_columns(array $columns): array {
|
||||
$columns['product_sales'] = 'product_sales';
|
||||
return $columns;
|
||||
}
|
||||
|
||||
public function render_product_quick_edit(string $column_name, string $post_type): void {
|
||||
if ($column_name !== 'product_discount' || $post_type !== 'aksrt_product') {
|
||||
return;
|
||||
}
|
||||
wp_nonce_field('aksrt_product_quick_edit', 'aksrt_product_qe_nonce');
|
||||
$discounts = get_terms([
|
||||
'taxonomy' => 'shop_discount',
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
?>
|
||||
<fieldset class="inline-edit-col-right" id="aksrt-product-quick-edit">
|
||||
<div class="inline-edit-col">
|
||||
<h4><?php esc_html_e('商品优惠活动', 'aksrt'); ?></h4>
|
||||
<input type="hidden" name="aksrt_product_qe_flag" value="1">
|
||||
<label class="alignleft">
|
||||
<span class="title"><?php esc_html_e('优惠活动', 'aksrt'); ?></span>
|
||||
<select name="aksrt_qe_discount" class="aksrt-qe-discount">
|
||||
<option value="-1"><?php esc_html_e('— 不更改 —', 'aksrt'); ?></option>
|
||||
<option value="0"><?php esc_html_e('无优惠', 'aksrt'); ?></option>
|
||||
<?php if (!is_wp_error($discounts)) : ?>
|
||||
<?php foreach ($discounts as $term) : ?>
|
||||
<option value="<?php echo esc_attr($term->term_id); ?>"><?php echo esc_html($term->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function save_product_quick_edit(int $post_id, \WP_Post $post): void {
|
||||
if (!isset($_POST['aksrt_product_qe_nonce']) || !wp_verify_nonce($_POST['aksrt_product_qe_nonce'], 'aksrt_product_quick_edit')) {
|
||||
return;
|
||||
}
|
||||
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
||||
return;
|
||||
}
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
return;
|
||||
}
|
||||
if (!isset($_POST['aksrt_product_qe_flag'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$discount_id = isset($_POST['aksrt_qe_discount']) ? sanitize_text_field(wp_unslash($_POST['aksrt_qe_discount'])) : '-1';
|
||||
if ($discount_id === '-1') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($discount_id === '0') {
|
||||
wp_set_object_terms($post_id, [], 'shop_discount');
|
||||
} else {
|
||||
$term_id = (int) $discount_id;
|
||||
if ($term_id > 0 && term_exists($term_id, 'shop_discount')) {
|
||||
wp_set_object_terms($post_id, [$term_id], 'shop_discount');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function product_quick_edit_script(): void {
|
||||
$screen = get_current_screen();
|
||||
if (!$screen || $screen->post_type !== 'aksrt_product') {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
(function(){
|
||||
var wpInlineEdit = window.inlineEditPost ? window.inlineEditPost.edit : null;
|
||||
if (!wpInlineEdit) return;
|
||||
window.inlineEditPost.edit = function(id) {
|
||||
wpInlineEdit.apply(this, arguments);
|
||||
var postId = 0;
|
||||
if (typeof id === 'object') {
|
||||
postId = parseInt(this.getId(id));
|
||||
}
|
||||
if (!postId) return;
|
||||
var row = document.getElementById('post-' + postId);
|
||||
if (!row) return;
|
||||
var dataEl = row.querySelector('.column-product_discount .aksrt-col-data');
|
||||
if (!dataEl) return;
|
||||
var termId = dataEl.getAttribute('data-discount-id') || '0';
|
||||
var select = document.querySelector('.aksrt-qe-discount');
|
||||
if (select) {
|
||||
select.value = termId;
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop product operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Product_Operations {
|
||||
|
||||
public function get_overview(): WP_REST_Response {
|
||||
$product_count = 0;
|
||||
|
||||
$post_type_obj = get_post_type_object('aksrt_product');
|
||||
if ($post_type_obj) {
|
||||
$counts = wp_count_posts('aksrt_product');
|
||||
$product_count = (int) ($counts->publish ?? 0);
|
||||
}
|
||||
|
||||
$order_stats = ['total' => 0, 'pending' => 0, 'paid' => 0, 'revenue' => 0];
|
||||
if (class_exists('AKSRT_Order_Repository')) {
|
||||
$order_stats = AKSRT_Order_Repository::get_instance()->get_stats();
|
||||
}
|
||||
|
||||
$revenue_stats = ['total' => 0, 'pending' => 0, 'settled' => 0];
|
||||
if (class_exists('AKSRT_Revenue')) {
|
||||
$revenue_stats = AKSRT_Revenue::get_instance()->get_stats();
|
||||
}
|
||||
|
||||
$gateway_summary = [];
|
||||
if (class_exists('AKSRT_Payment_Gateway')) {
|
||||
$gateway_summary = AKSRT_Payment_Gateway::get_instance()->get_status_summary();
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'enabled' => true,
|
||||
'product_count' => $product_count,
|
||||
'order_count' => $order_stats['total'],
|
||||
'order_pending' => $order_stats['pending'] ?? 0,
|
||||
'revenue_total' => $order_stats['revenue'] ?? 0,
|
||||
'revenue_pending' => $revenue_stats['pending'] ?? 0,
|
||||
'gateways' => $gateway_summary,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /shop/products — 商品列表,支持 search、status、page、per_page
|
||||
*/
|
||||
public function get_products(WP_REST_Request $request): WP_REST_Response {
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$search = sanitize_text_field((string) $request->get_param('search'));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_product',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $page,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
|
||||
if ($status !== '') {
|
||||
$query_args['post_status'] = $status;
|
||||
} else {
|
||||
$query_args['post_status'] = ['publish', 'draft', 'pending'];
|
||||
}
|
||||
|
||||
if ($search !== '') {
|
||||
$query_args['s'] = $search;
|
||||
}
|
||||
|
||||
$query = new WP_Query($query_args);
|
||||
|
||||
$products = [];
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$id = get_the_ID();
|
||||
$products[] = [
|
||||
'id' => $id,
|
||||
'title' => get_the_title(),
|
||||
'price' => (float) get_post_meta($id, '_aksrt_price', true),
|
||||
'image' => get_the_post_thumbnail_url($id, 'medium') ?: '',
|
||||
'stock' => (int) get_post_meta($id, '_aksrt_stock', true),
|
||||
'sales' => (int) get_post_meta($id, '_aksrt_sales', true),
|
||||
'status' => get_post_status(),
|
||||
'created' => get_the_date('Y-m-d H:i'),
|
||||
];
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $products,
|
||||
'total' => (int) $query->found_posts,
|
||||
'total_pages' => (int) $query->max_num_pages,
|
||||
'page' => $page,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /shop/products/{id} — 单个商品详情
|
||||
*/
|
||||
public function get_product(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$post = get_post($id);
|
||||
|
||||
if (!$post || $post->post_type !== 'aksrt_product') {
|
||||
return $this->error('商品不存在', 404);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'content' => $post->post_content,
|
||||
'excerpt' => $post->post_excerpt,
|
||||
'price' => (float) get_post_meta($post->ID, '_aksrt_price', true),
|
||||
'image' => (string) (get_post_meta($post->ID, '_aksrt_product_image', true) ?: get_the_post_thumbnail_url($post->ID, 'medium') ?: ''),
|
||||
'stock' => (int) get_post_meta($post->ID, '_aksrt_stock', true),
|
||||
'sales' => (int) get_post_meta($post->ID, '_aksrt_sales', true),
|
||||
'status' => $post->post_status,
|
||||
'created' => get_the_date('Y-m-d H:i', $post),
|
||||
'modified' => get_the_modified_date('Y-m-d H:i', $post),
|
||||
'categories' => wp_get_post_terms($post->ID, 'aksrt_product_cat', ['fields' => 'names']) ?: [],
|
||||
'tags' => wp_get_post_terms($post->ID, 'aksrt_product_tag', ['fields' => 'names']) ?: [],
|
||||
'gallery' => get_post_meta($post->ID, '_aksrt_product_gallery', true) ?: [],
|
||||
'shipping_mode' => (string) get_post_meta($post->ID, '_aksrt_shipping_mode', true),
|
||||
'shipping_fee' => (float) get_post_meta($post->ID, '_aksrt_shipping_fee', true),
|
||||
'after_sale_policy' => (string) get_post_meta($post->ID, '_aksrt_after_sale_policy', true),
|
||||
'support_refund' => get_post_meta($post->ID, '_aksrt_support_refund', true) !== '0',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/products — 创建商品
|
||||
*/
|
||||
public function create_product(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
|
||||
$title = sanitize_text_field((string) ($params['title'] ?? ''));
|
||||
$content = wp_kses_post((string) ($params['content'] ?? ''));
|
||||
$excerpt = sanitize_textarea_field((string) ($params['excerpt'] ?? ''));
|
||||
$price = (float) ($params['price'] ?? 0);
|
||||
$stock = (int) ($params['stock'] ?? 0);
|
||||
$status = sanitize_key((string) ($params['status'] ?? 'publish'));
|
||||
$image = esc_url_raw((string) ($params['image'] ?? ''));
|
||||
$gallery = is_array($params['gallery'] ?? null) ? array_values(array_filter(array_map('esc_url_raw', $params['gallery']))) : [];
|
||||
$shipping_mode = sanitize_key((string) ($params['shipping_mode'] ?? 'express'));
|
||||
$shipping_fee = max(0, (float) ($params['shipping_fee'] ?? 0));
|
||||
$after_sale_policy = sanitize_textarea_field((string) ($params['after_sale_policy'] ?? ''));
|
||||
$support_refund = !isset($params['support_refund']) || !empty($params['support_refund']);
|
||||
$author_id = max(0, (int) ($params['author_id'] ?? 0));
|
||||
|
||||
if ($title === '') {
|
||||
return $this->error('商品名称不能为空');
|
||||
}
|
||||
|
||||
$valid_statuses = ['publish', 'draft', 'pending'];
|
||||
if (!in_array($status, $valid_statuses, true)) {
|
||||
$status = 'draft';
|
||||
}
|
||||
|
||||
$post_id = wp_insert_post([
|
||||
'post_type' => 'aksrt_product',
|
||||
'post_title' => $title,
|
||||
'post_content' => $content,
|
||||
'post_excerpt' => $excerpt,
|
||||
'post_status' => $status,
|
||||
'post_author' => $author_id > 0 ? $author_id : get_current_user_id(),
|
||||
'meta_input' => [
|
||||
'_aksrt_price' => max(0, $price),
|
||||
'_aksrt_stock' => max(0, $stock),
|
||||
'_aksrt_sales' => 0,
|
||||
'_aksrt_score' => 0,
|
||||
'_aksrt_views' => 0,
|
||||
'_aksrt_favorite_count' => 0,
|
||||
'_aksrt_product_gallery' => $gallery,
|
||||
'_aksrt_shipping_mode' => $shipping_mode,
|
||||
'_aksrt_shipping_fee' => $shipping_fee,
|
||||
'_aksrt_after_sale_policy' => $after_sale_policy,
|
||||
'_aksrt_support_refund' => $support_refund ? '1' : '0',
|
||||
'_aksrt_product_video' => is_array($params['product_video'] ?? null) ? array_values(array_filter(array_map('esc_url_raw', $params['product_video']))) : [],
|
||||
'_aksrt_product_params' => sanitize_textarea_field((string) ($params['product_params'] ?? '')),
|
||||
'_aksrt_pay_modo' => sanitize_key((string) ($params['pay_modo'] ?? '0')),
|
||||
'_aksrt_start_price' => max(0, (float) ($params['start_price'] ?? 0)),
|
||||
'_aksrt_product_rebate' => sanitize_text_field((string) ($params['product_rebate'] ?? '')),
|
||||
'_aksrt_limit_buy' => max(-1, min(9999, (int) ($params['limit_buy'] ?? -1))),
|
||||
'_aksrt_limit_buy_note' => sanitize_text_field((string) ($params['limit_buy_note'] ?? '')),
|
||||
'_aksrt_stock_type' => sanitize_key((string) ($params['stock_type'] ?? 'all')),
|
||||
'_aksrt_user_required' => sanitize_textarea_field((string) ($params['user_required'] ?? '')),
|
||||
'_aksrt_shipping_type' => sanitize_key((string) ($params['shipping_type'] ?? $shipping_mode)),
|
||||
'_aksrt_shipping_delivery_desc' => sanitize_text_field((string) ($params['shipping_delivery_desc'] ?? '')),
|
||||
'_aksrt_shipping_fee_type' => sanitize_key((string) ($params['shipping_fee_type'] ?? 'free')),
|
||||
'_aksrt_shipping_fee_free_amount'=> max(0, (float) ($params['shipping_fee_free_amount'] ?? 0)),
|
||||
'_aksrt_after_sale_matrix' => sanitize_textarea_field((string) ($params['after_sale_matrix'] ?? '')),
|
||||
'_aksrt_service' => sanitize_textarea_field((string) ($params['service'] ?? '')),
|
||||
'_aksrt_single_tab' => sanitize_textarea_field((string) ($params['single_tab'] ?? '')),
|
||||
'_aksrt_content_layout' => sanitize_key((string) ($params['content_layout'] ?? '')),
|
||||
'_aksrt_content_show_bg' => sanitize_text_field((string) ($params['content_show_bg'] ?? '')),
|
||||
'_aksrt_delivery_note' => sanitize_text_field((string) ($params['delivery_note'] ?? '')),
|
||||
'_aksrt_marketing_headline' => sanitize_text_field((string) ($params['marketing_headline'] ?? '')),
|
||||
'_aksrt_shipping_tip' => sanitize_text_field((string) ($params['shipping_tip'] ?? '')),
|
||||
'_aksrt_service_tip' => sanitize_text_field((string) ($params['service_tip'] ?? '')),
|
||||
],
|
||||
], true);
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
return $this->error($post_id->get_error_message());
|
||||
}
|
||||
|
||||
if (!empty($params['categories']) && is_array($params['categories'])) {
|
||||
wp_set_object_terms($post_id, $params['categories'], 'aksrt_product_cat');
|
||||
}
|
||||
if (!empty($params['tags']) && is_array($params['tags'])) {
|
||||
wp_set_object_terms($post_id, $params['tags'], 'aksrt_product_tag');
|
||||
}
|
||||
if ($image !== '') {
|
||||
update_post_meta($post_id, '_aksrt_product_image', $image);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('商品已创建', 'aksrt'),
|
||||
'data' => ['id' => $post_id],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /shop/products/{id} — 更新商品(含上下架)
|
||||
*/
|
||||
public function update_product(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$post = get_post($id);
|
||||
|
||||
if (!$post || $post->post_type !== 'aksrt_product') {
|
||||
return $this->error('商品不存在', 404);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$update = ['ID' => $id];
|
||||
|
||||
if (isset($params['title'])) {
|
||||
$update['post_title'] = sanitize_text_field($params['title']);
|
||||
}
|
||||
if (isset($params['content'])) {
|
||||
$update['post_content'] = wp_kses_post($params['content']);
|
||||
}
|
||||
if (isset($params['excerpt'])) {
|
||||
$update['post_excerpt'] = sanitize_textarea_field($params['excerpt']);
|
||||
}
|
||||
if (isset($params['status'])) {
|
||||
$new_status = sanitize_key($params['status']);
|
||||
$valid_statuses = ['publish', 'draft', 'pending', 'trash'];
|
||||
if (in_array($new_status, $valid_statuses, true)) {
|
||||
$update['post_status'] = $new_status;
|
||||
}
|
||||
}
|
||||
|
||||
$meta = [];
|
||||
if (isset($params['price'])) {
|
||||
$meta['_aksrt_price'] = max(0, (float) $params['price']);
|
||||
}
|
||||
if (isset($params['stock'])) {
|
||||
$meta['_aksrt_stock'] = max(0, (int) $params['stock']);
|
||||
}
|
||||
if (isset($params['gallery']) && is_array($params['gallery'])) {
|
||||
$meta['_aksrt_product_gallery'] = array_values(array_filter(array_map('esc_url_raw', $params['gallery'])));
|
||||
}
|
||||
if (isset($params['shipping_mode'])) {
|
||||
$meta['_aksrt_shipping_mode'] = sanitize_key((string) $params['shipping_mode']);
|
||||
}
|
||||
if (isset($params['shipping_fee'])) {
|
||||
$meta['_aksrt_shipping_fee'] = max(0, (float) $params['shipping_fee']);
|
||||
}
|
||||
if (isset($params['after_sale_policy'])) {
|
||||
$meta['_aksrt_after_sale_policy'] = sanitize_textarea_field((string) $params['after_sale_policy']);
|
||||
}
|
||||
if (isset($params['support_refund'])) {
|
||||
$meta['_aksrt_support_refund'] = !empty($params['support_refund']) ? '1' : '0';
|
||||
}
|
||||
if (isset($params['product_video']) && is_array($params['product_video'])) {
|
||||
$meta['_aksrt_product_video'] = array_values(array_filter(array_map('esc_url_raw', $params['product_video'])));
|
||||
}
|
||||
if (isset($params['product_params'])) {
|
||||
$meta['_aksrt_product_params'] = sanitize_textarea_field((string) $params['product_params']);
|
||||
}
|
||||
if (isset($params['pay_modo'])) {
|
||||
$meta['_aksrt_pay_modo'] = sanitize_key((string) $params['pay_modo']);
|
||||
}
|
||||
if (isset($params['start_price'])) {
|
||||
$meta['_aksrt_start_price'] = max(0, (float) $params['start_price']);
|
||||
}
|
||||
if (isset($params['product_rebate'])) {
|
||||
$meta['_aksrt_product_rebate'] = sanitize_text_field((string) $params['product_rebate']);
|
||||
}
|
||||
if (isset($params['limit_buy'])) {
|
||||
$meta['_aksrt_limit_buy'] = max(-1, min(9999, (int) $params['limit_buy']));
|
||||
}
|
||||
if (isset($params['limit_buy_note'])) {
|
||||
$meta['_aksrt_limit_buy_note'] = sanitize_text_field((string) $params['limit_buy_note']);
|
||||
}
|
||||
if (isset($params['stock_type'])) {
|
||||
$meta['_aksrt_stock_type'] = sanitize_key((string) $params['stock_type']);
|
||||
}
|
||||
if (isset($params['stock_opts']) && is_array($params['stock_opts'])) {
|
||||
$meta['_aksrt_stock_opts'] = wp_json_encode($params['stock_opts']);
|
||||
}
|
||||
if (isset($params['user_required'])) {
|
||||
$meta['_aksrt_user_required'] = sanitize_textarea_field((string) $params['user_required']);
|
||||
}
|
||||
if (isset($params['shipping_type'])) {
|
||||
$meta['_aksrt_shipping_type'] = sanitize_key((string) $params['shipping_type']);
|
||||
}
|
||||
if (isset($params['shipping_delivery_desc'])) {
|
||||
$meta['_aksrt_shipping_delivery_desc'] = sanitize_text_field((string) $params['shipping_delivery_desc']);
|
||||
}
|
||||
if (isset($params['auto_delivery']) && is_array($params['auto_delivery'])) {
|
||||
$meta['_aksrt_auto_delivery'] = wp_json_encode($params['auto_delivery']);
|
||||
}
|
||||
if (isset($params['shipping_fee_type'])) {
|
||||
$meta['_aksrt_shipping_fee_type'] = sanitize_key((string) $params['shipping_fee_type']);
|
||||
}
|
||||
if (isset($params['shipping_fee_free_amount'])) {
|
||||
$meta['_aksrt_shipping_fee_free_amount'] = max(0, (float) $params['shipping_fee_free_amount']);
|
||||
}
|
||||
if (isset($params['after_sale_matrix'])) {
|
||||
$meta['_aksrt_after_sale_matrix'] = sanitize_textarea_field((string) $params['after_sale_matrix']);
|
||||
}
|
||||
if (isset($params['service'])) {
|
||||
$meta['_aksrt_service'] = sanitize_textarea_field((string) $params['service']);
|
||||
}
|
||||
if (isset($params['single_tab'])) {
|
||||
$meta['_aksrt_single_tab'] = sanitize_textarea_field((string) $params['single_tab']);
|
||||
}
|
||||
if (isset($params['content_layout'])) {
|
||||
$meta['_aksrt_content_layout'] = sanitize_key((string) $params['content_layout']);
|
||||
}
|
||||
if (isset($params['content_show_bg'])) {
|
||||
$meta['_aksrt_content_show_bg'] = sanitize_text_field((string) $params['content_show_bg']);
|
||||
}
|
||||
if (isset($params['delivery_note'])) {
|
||||
$meta['_aksrt_delivery_note'] = sanitize_text_field((string) $params['delivery_note']);
|
||||
}
|
||||
if (isset($params['marketing_headline'])) {
|
||||
$meta['_aksrt_marketing_headline'] = sanitize_text_field((string) $params['marketing_headline']);
|
||||
}
|
||||
if (isset($params['shipping_tip'])) {
|
||||
$meta['_aksrt_shipping_tip'] = sanitize_text_field((string) $params['shipping_tip']);
|
||||
}
|
||||
if (isset($params['service_tip'])) {
|
||||
$meta['_aksrt_service_tip'] = sanitize_text_field((string) $params['service_tip']);
|
||||
}
|
||||
if (!empty($meta)) {
|
||||
$update['meta_input'] = $meta;
|
||||
}
|
||||
|
||||
$result = wp_update_post($update, true);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message());
|
||||
}
|
||||
|
||||
if (isset($params['categories']) && is_array($params['categories'])) {
|
||||
wp_set_object_terms($id, $params['categories'], 'aksrt_product_cat');
|
||||
}
|
||||
if (isset($params['tags']) && is_array($params['tags'])) {
|
||||
wp_set_object_terms($id, $params['tags'], 'aksrt_product_tag');
|
||||
}
|
||||
if (isset($params['image'])) {
|
||||
update_post_meta($id, '_aksrt_product_image', esc_url_raw((string) $params['image']));
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('商品已更新', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin shop variant and export operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Shop_Controller_Variant_Export_Operations {
|
||||
|
||||
public function get_product_variants(WP_REST_Request $request): WP_REST_Response {
|
||||
$product_id = (int) $request->get_param('id');
|
||||
$product = get_post($product_id);
|
||||
if (!$product || $product->post_type !== 'aksrt_product') {
|
||||
return $this->error('商品不存在', 404);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->success(['data' => ['variants' => []]]);
|
||||
}
|
||||
|
||||
$variants = AKSRT_Shop::get_instance()->get_variants($product_id);
|
||||
return $this->success(['data' => ['variants' => $variants]]);
|
||||
}
|
||||
|
||||
public function save_product_variants(WP_REST_Request $request): WP_REST_Response {
|
||||
$product_id = (int) $request->get_param('id');
|
||||
$product = get_post($product_id);
|
||||
if (!$product || $product->post_type !== 'aksrt_product') {
|
||||
return $this->error('商品不存在', 404);
|
||||
}
|
||||
|
||||
$params = $request->get_json_params() ?? [];
|
||||
$variants = $params['variants'] ?? [];
|
||||
if (!is_array($variants)) {
|
||||
return $this->error('变体数据格式错误');
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城系统未启用', 501);
|
||||
}
|
||||
|
||||
$saved = AKSRT_Shop::get_instance()->save_variants($product_id, $variants);
|
||||
return $this->success(['message' => '变体已保存', 'data' => ['variants' => $saved]]);
|
||||
}
|
||||
|
||||
public function delete_variant(WP_REST_Request $request): WP_REST_Response {
|
||||
$variant_id = (int) $request->get_param('id');
|
||||
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城系统未启用', 501);
|
||||
}
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->delete_variant($variant_id);
|
||||
if (!$result) {
|
||||
return $this->error('删除失败', 500);
|
||||
}
|
||||
|
||||
return $this->success(['message' => '变体已删除']);
|
||||
}
|
||||
|
||||
public function export_orders(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$args = [
|
||||
'status' => sanitize_key((string) $request->get_param('status')),
|
||||
'date_from' => sanitize_text_field((string) $request->get_param('date_from')),
|
||||
'date_to' => sanitize_text_field((string) $request->get_param('date_to')),
|
||||
'limit' => max(1, min(5000, (int) ($request->get_param('limit') ?: 1000))),
|
||||
];
|
||||
|
||||
$result = AKSRT_Shop::get_instance()->export_orders_csv($args);
|
||||
|
||||
return new WP_REST_Response($result['csv'], 200, [
|
||||
'Content-Type' => 'text/csv; charset=utf-8',
|
||||
'Content-Disposition' => 'attachment; filename="orders-' . date('Ymd-His') . '.csv"',
|
||||
'X-Order-Count' => $result['count'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function confirm_receipt(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$result = AKSRT_Shop::get_instance()->confirm_receipt(get_current_user_id(), $order_number);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '确认收货失败');
|
||||
}
|
||||
|
||||
return $this->success(['data' => $result['data']]);
|
||||
}
|
||||
|
||||
public function get_after_sale_reasons(WP_REST_Request $request): WP_REST_Response {
|
||||
if (class_exists('AKSRT_After_Sale')) {
|
||||
return $this->success(['data' => AKSRT_After_Sale::get_reason_options()]);
|
||||
}
|
||||
return $this->success(['data' => ['refund' => [], 'return' => []]]);
|
||||
}
|
||||
|
||||
public function get_frontend_after_sale_reasons(WP_REST_Request $request): WP_REST_Response {
|
||||
if (class_exists('AKSRT_After_Sale')) {
|
||||
return $this->success(['data' => AKSRT_After_Sale::get_reason_options()]);
|
||||
}
|
||||
return $this->success(['data' => ['refund' => [], 'return' => []]]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Shop Controller
|
||||
*
|
||||
* Owns REST routes for the "商城&商品" domain.
|
||||
* Provides product CRUD, order management, and shop overview.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Shop_Controller extends AKSRT_Admin_Controller_Base {
|
||||
use AKSRT_Admin_Shop_Controller_Product_Admin_Columns;
|
||||
use AKSRT_Admin_Shop_Controller_Frontend_Flow_Operations;
|
||||
use AKSRT_Admin_Shop_Controller_Product_Operations;
|
||||
use AKSRT_Admin_Shop_Controller_Order_Operations;
|
||||
use AKSRT_Admin_Shop_Controller_Fulfillment_Operations;
|
||||
use AKSRT_Admin_Shop_Controller_Coupon_Gateway_Operations;
|
||||
use AKSRT_Admin_Shop_Controller_Variant_Export_Operations;
|
||||
|
||||
private static $instance = null;
|
||||
private $admin;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function set_admin(AKSRT_Admin $admin): void {
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/checkout/preview', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'preview_checkout'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_frontend_order'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_orders'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/pay', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'pay_frontend_order'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/cancel', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'cancel_frontend_order'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_order'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/after-sale', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_frontend_after_sale'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// ── Address book CRUD (frontend) ──
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_addresses'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_frontend_address'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_frontend_address'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_frontend_address'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/address/(?P<id>\d+)/default', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'set_default_frontend_address'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/products', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_frontend_product'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/products/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_frontend_product'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// Cart endpoints (§6.4)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/add', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'handle_add_to_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/reduce', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'handle_reduce_from_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/remove', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'handle_remove_from_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/remove-multi', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'handle_remove_multi_from_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/empty', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'handle_empty_cart'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/cart/count', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_cart_count'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
// Shop overview / dashboard
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Product list
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_products'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Product create
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_product'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Product update
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products/(?P<id>\d+)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_product'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Product single read
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_product'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Order list
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/orders', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_orders'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Order single read
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/orders/(?P<order_number>[A-Za-z0-9]+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_order'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Order status update — canonical field is order_number
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/orders/update-status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_order_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Gateway status
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/gateways', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_gateways'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Coupon list (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/coupons', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_coupons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Coupon create (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/coupons', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_coupon'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Coupon disable (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/coupons/(?P<id>\d+)/disable', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'disable_coupon'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── After Sale ──
|
||||
|
||||
// After-sale list (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_after_sale_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// After-sale create (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_after_sale'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// After-sale status update (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale/(?P<id>\d+)/update-status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_after_sale_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_after_sale_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Shipping ──
|
||||
|
||||
// Shipping list (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/shipping', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_shipping_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Shipping create (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/shipping', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'create_shipping'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Shipping status update (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/shipping/(?P<id>\d+)/update-status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_shipping_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/shipping/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_shipping_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/shipping/batch-update-status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'batch_update_shipping_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
|
||||
// ── R68: Order export + confirm receipt + after-sale reasons ──
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/orders/export', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'export_orders'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── R69: Frontend after-sale reasons (§6.8) ──
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/after-sale/reasons', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_frontend_after_sale_reasons'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/frontend/orders/(?P<order_number>[A-Za-z0-9]+)/confirm-receipt', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'confirm_receipt'],
|
||||
'permission_callback' => [$this, 'check_logged_in_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/after-sale/reasons', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_after_sale_reasons'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Product Variants / SKU ──
|
||||
|
||||
// List variants for a product
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products/(?P<id>\d+)/variants', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_product_variants'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Save variants for a product (replace all)
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/products/(?P<id>\d+)/variants', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'save_product_variants'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Delete a variant
|
||||
register_rest_route(self::API_NAMESPACE, '/shop/variants/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'delete_variant'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function check_logged_in_permission(): bool {
|
||||
return is_user_logged_in();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — Tools & Diagnostics Controller
|
||||
*
|
||||
* Handles REST routes for the "备份&导入" and "扩展&增强" domains:
|
||||
* Health checks, email rollback, REST block.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Tools_Controller extends AKSRT_Admin_Controller_Base {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Health: email verification status
|
||||
register_rest_route(self::API_NAMESPACE, '/health/email-verification', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_health_email_verification'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Health: REST block toggle
|
||||
register_rest_route(self::API_NAMESPACE, '/health/rest-block', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_set_rest_block'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Email rollback: list users with recent email changes
|
||||
register_rest_route(self::API_NAMESPACE, '/email-rollback/users', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_email_rollback_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Email rollback: execute rollback
|
||||
register_rest_route(self::API_NAMESPACE, '/email-rollback/users/(?P<id>\d+)/rollback', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_email_rollback_execute'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Tools: system health (compact format for ExtensionTools.vue)
|
||||
register_rest_route(self::API_NAMESPACE, '/tools/health', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_tools_health'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Tools: maintenance actions
|
||||
register_rest_route(self::API_NAMESPACE, '/tools/maintenance', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'run_maintenance'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Tools: update center
|
||||
register_rest_route(self::API_NAMESPACE, '/tools/update-info', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_update_info'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/tools/update-check', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'detect_update'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/tools/update-dismiss', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'dismiss_update'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /tools/health — compact health data for the tools page.
|
||||
*/
|
||||
public function get_tools_health(): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$upload_dir = wp_upload_dir();
|
||||
$upload_ok = !empty($upload_dir['error']) === false && is_dir($upload_dir['basedir']);
|
||||
|
||||
$db_ok = true;
|
||||
$db_status = '正常';
|
||||
$expected_tables = [
|
||||
$wpdb->prefix . 'aksrt_credit_log',
|
||||
$wpdb->prefix . 'aksrt_signin_log',
|
||||
$wpdb->prefix . 'aksrt_invite',
|
||||
$wpdb->prefix . 'aksrt_dl_token',
|
||||
$wpdb->prefix . 'aksrt_notification',
|
||||
];
|
||||
$missing = [];
|
||||
foreach ($expected_tables as $table) {
|
||||
if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) !== $table) {
|
||||
$missing[] = str_replace($wpdb->prefix, '', $table);
|
||||
}
|
||||
}
|
||||
if (!empty($missing)) {
|
||||
$db_ok = false;
|
||||
$db_status = '缺少: ' . implode(', ', $missing);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'php_version' => PHP_VERSION,
|
||||
'wp_version' => get_bloginfo('version'),
|
||||
'db_status' => $db_status,
|
||||
'db_ok' => $db_ok,
|
||||
'upload_dir' => $upload_dir['basedir'],
|
||||
'upload_ok' => $upload_ok,
|
||||
'theme_version' => wp_get_theme()->get('Version') ?? 'unknown',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /tools/maintenance — run a maintenance action.
|
||||
*/
|
||||
public function run_maintenance(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$action = sanitize_key((string) ($params['action'] ?? ''));
|
||||
|
||||
switch ($action) {
|
||||
case 'flush_cache':
|
||||
// Flush WordPress object cache
|
||||
wp_cache_flush();
|
||||
// Clear transients
|
||||
global $wpdb;
|
||||
$wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_aksrt_%' OR option_name LIKE '_transient_timeout_aksrt_%'");
|
||||
return $this->success(['message' => __('缓存已清除', 'aksrt')]);
|
||||
|
||||
case 'optimize_db':
|
||||
global $wpdb;
|
||||
$tables = [
|
||||
$wpdb->prefix . 'aksrt_credit_log',
|
||||
$wpdb->prefix . 'aksrt_signin_log',
|
||||
$wpdb->prefix . 'aksrt_invite',
|
||||
$wpdb->prefix . 'aksrt_dl_token',
|
||||
$wpdb->prefix . 'aksrt_notification',
|
||||
];
|
||||
$optimized = 0;
|
||||
foreach ($tables as $table) {
|
||||
if ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table) {
|
||||
$wpdb->query("OPTIMIZE TABLE {$table}");
|
||||
$optimized++;
|
||||
}
|
||||
}
|
||||
return $this->success(['message' => sprintf(__('已优化 %d 个数据表', 'aksrt'), $optimized)]);
|
||||
|
||||
case 'rebuild_index':
|
||||
// Rebuild search index / recount stats
|
||||
if (class_exists('AKSRT_Forum') && method_exists(AKSRT_Forum::get_instance(), 'recount_stats')) {
|
||||
AKSRT_Forum::get_instance()->recount_stats();
|
||||
}
|
||||
return $this->success(['message' => __('索引重建完成', 'aksrt')]);
|
||||
|
||||
default:
|
||||
return $this->error('未知的维护操作: ' . esc_html($action));
|
||||
}
|
||||
}
|
||||
|
||||
public function get_update_info(): WP_REST_Response {
|
||||
$theme = wp_get_theme();
|
||||
$info = [
|
||||
'current_version' => $theme->get('Version') ?: '0.0.0',
|
||||
'name' => $theme->get('Name') ?: 'AKSRT',
|
||||
'update_available'=> false,
|
||||
'remote_version' => '',
|
||||
'download_url' => '',
|
||||
'changelog' => '',
|
||||
'last_check' => 0,
|
||||
'dismissed' => '',
|
||||
];
|
||||
|
||||
if (class_exists('AKSRT_Update_Checker')) {
|
||||
$stored = AKSRT_Update_Checker::get_instance()->get_update_info();
|
||||
if (is_array($stored)) {
|
||||
$info = array_merge($info, $stored);
|
||||
}
|
||||
}
|
||||
|
||||
$info['dismissed'] = (string) get_user_meta(get_current_user_id(), 'aksrt_dismissed_update', true);
|
||||
|
||||
return $this->success(['data' => $info]);
|
||||
}
|
||||
|
||||
public function detect_update(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Update_Checker')) {
|
||||
return $this->error(__('更新检查器不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
AKSRT_Update_Checker::get_instance()->do_check();
|
||||
$stored = AKSRT_Update_Checker::get_instance()->get_update_info();
|
||||
|
||||
return $this->success([
|
||||
'message' => __('已完成更新检测', 'aksrt'),
|
||||
'data' => is_array($stored) ? $stored : [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function dismiss_update(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$version = sanitize_text_field((string) ($params['version'] ?? ''));
|
||||
|
||||
if ($version === '') {
|
||||
return $this->error(__('缺少版本号', 'aksrt'), 422);
|
||||
}
|
||||
|
||||
update_user_meta(get_current_user_id(), 'aksrt_dismissed_update', $version);
|
||||
|
||||
return $this->success([
|
||||
'message' => __('已忽略此版本提醒', 'aksrt'),
|
||||
'data' => ['version' => $version],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_health_email_verification(): WP_REST_Response {
|
||||
$feature_enabled = class_exists('AKSRT_Settings') && AKSRT_Settings::registration_requires_email_verification();
|
||||
$pending_users = 0;
|
||||
|
||||
if (class_exists('AKSRT_Auth_Email_Verification')) {
|
||||
global $wpdb;
|
||||
$pending_users = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_email_verified' AND CAST(meta_value AS UNSIGNED) = 0"
|
||||
);
|
||||
}
|
||||
|
||||
return $this->success(['data' => [
|
||||
'feature_enabled' => $feature_enabled,
|
||||
'pending_users' => $pending_users,
|
||||
'rest_blocking_active' => !empty(get_option('aksrt_rest_block_email_verification', false)),
|
||||
]]);
|
||||
}
|
||||
|
||||
public function rest_set_rest_block(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$enabled = !empty($params['enabled']);
|
||||
update_option('aksrt_rest_block_email_verification', $enabled);
|
||||
|
||||
return $this->success([
|
||||
'message' => $enabled ? __('已启用 REST 邮箱验证阻断', 'aksrt') : __('已关闭 REST 邮箱验证阻断', 'aksrt'),
|
||||
'data' => ['enabled' => $enabled],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_email_rollback_list(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Email_Rollback')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 20]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = min(100, max(1, (int) $request->get_param('per_page')));
|
||||
$data = AKSRT_Email_Rollback::list_recent_changes($page, $per_page);
|
||||
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
public function rest_email_rollback_execute(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Email_Rollback')) {
|
||||
return $this->error(__('邮箱回滚功能不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$user_id = (int) $request->get_param('id');
|
||||
$result = AKSRT_Email_Rollback::rollback($user_id, get_current_user_id());
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['error'] ?? __('邮箱回滚失败', 'aksrt')), 422);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => (string) ($result['message'] ?? __('邮箱已回滚', 'aksrt')),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin auth and phone operations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Auth_Phone_Operations {
|
||||
|
||||
/**
|
||||
* POST /auth/modal-login — 弹窗登录
|
||||
*/
|
||||
public function modal_login(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$payload = is_array($params) ? $params : [];
|
||||
$login = sanitize_text_field((string) ($payload['log'] ?? $payload['login'] ?? ''));
|
||||
$password = (string) ($payload['pwd'] ?? $payload['password'] ?? '');
|
||||
|
||||
if ($login === '' || $password === '') {
|
||||
return $this->error('请输入用户名和密码');
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用', 500);
|
||||
}
|
||||
|
||||
$result = AKSRT_Auth_Service::login_user([
|
||||
...$payload,
|
||||
'log' => $login,
|
||||
'pwd' => $password,
|
||||
'rememberme' => !empty($payload['rememberme']) ? (string) $payload['rememberme'] : 'forever',
|
||||
], true, true);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
$error = $result['error'] ?? '登录失败';
|
||||
$verify_url = $result['verify_url'] ?? '';
|
||||
$response = ['success' => false, 'message' => $error];
|
||||
if ($verify_url) {
|
||||
$response['verify_url'] = $verify_url;
|
||||
}
|
||||
return new WP_REST_Response($response, 401);
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
return $this->success([
|
||||
'message' => __('登录成功', 'aksrt'),
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
'user' => [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/modal-register — 弹窗注册
|
||||
*/
|
||||
public function modal_register(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$payload = is_array($params) ? $params : [];
|
||||
$username = sanitize_user((string) ($payload['user_login'] ?? $payload['username'] ?? ''), true);
|
||||
$email = sanitize_email((string) ($payload['user_email'] ?? $payload['email'] ?? ''));
|
||||
$password = (string) ($payload['user_pass'] ?? $payload['password'] ?? '');
|
||||
$confirm = (string) ($payload['user_pass_confirm'] ?? $payload['password_confirm'] ?? $payload['confirm_password'] ?? $password);
|
||||
$invite_code = sanitize_text_field((string) ($payload['invite_code'] ?? ''));
|
||||
$register_nonce = sanitize_text_field((string) ($payload['_register_nonce'] ?? ''));
|
||||
$agree_terms = !empty($payload['agree_terms']) ? '1' : '';
|
||||
|
||||
if ($username === '' || $email === '' || $password === '') {
|
||||
return $this->error('请填写所有必填项');
|
||||
}
|
||||
|
||||
if (!is_email($email)) {
|
||||
return $this->error('邮箱格式不正确');
|
||||
}
|
||||
|
||||
if (strlen($password) < 6) {
|
||||
return $this->error('密码至少 6 位');
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用', 500);
|
||||
}
|
||||
|
||||
$payload['user_login'] = $username;
|
||||
$payload['user_email'] = $email;
|
||||
$payload['user_pass'] = $password;
|
||||
$payload['user_pass_confirm'] = $confirm;
|
||||
$payload['invite_code'] = $invite_code;
|
||||
$payload['_register_nonce'] = $register_nonce;
|
||||
$payload['agree_terms'] = $agree_terms;
|
||||
|
||||
$result = AKSRT_Auth_Service::register_user($payload);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '注册失败');
|
||||
}
|
||||
|
||||
$verification_required = !empty($result['verification_required']);
|
||||
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
||||
$registered_user = null;
|
||||
$current_user = wp_get_current_user();
|
||||
if (
|
||||
$current_user instanceof WP_User
|
||||
&& $current_user->ID > 0
|
||||
&& !empty($result['user_id'])
|
||||
&& (int) $result['user_id'] === (int) $current_user->ID
|
||||
) {
|
||||
$registered_user = $current_user;
|
||||
}
|
||||
$user = $current_user;
|
||||
$needs_verify = $verification_required;
|
||||
|
||||
return $this->success([
|
||||
'message' => $needs_verify ? __('注册成功,请验证邮箱', 'aksrt') : __('注册成功', 'aksrt'),
|
||||
'needs_verification' => $needs_verify,
|
||||
'verify_url' => $result['verify_url'] ?? '',
|
||||
'redirect' => $verification_required ? ($result['verify_url'] ?? '') : $login_url,
|
||||
'verification_required' => $verification_required,
|
||||
'login_url' => $login_url,
|
||||
'user_login' => $result['user_login'] ?? $username,
|
||||
'user_email' => $email,
|
||||
'user' => $registered_user ? [
|
||||
'id' => $registered_user->ID,
|
||||
'display_name' => $registered_user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($registered_user->ID, ['size' => 48])
|
||||
: get_avatar_url($registered_user->ID, ['size' => 48]),
|
||||
] : ($user->ID ? [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
] : null),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /auth/me — 当前用户信息(用于 AJAX 刷新)
|
||||
*/
|
||||
public function get_current_user_info(): WP_REST_Response {
|
||||
$user = wp_get_current_user();
|
||||
if (!$user->ID) {
|
||||
return $this->error('未登录', 401);
|
||||
}
|
||||
|
||||
$unread = 0;
|
||||
if (class_exists('AKSRT_Private_Message')) {
|
||||
$unread = AKSRT_Private_Message::get_instance()->get_unread_count($user->ID);
|
||||
}
|
||||
|
||||
$credits = 0;
|
||||
if (class_exists('AKSRT_Credits')) {
|
||||
$credits = AKSRT_Credits::balance($user->ID);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'user_email' => $user->user_email,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
'is_admin' => current_user_can('manage_options'),
|
||||
'unread' => $unread,
|
||||
'credits' => $credits,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/send-sms-code — 发送短信验证码
|
||||
*/
|
||||
public function auth_send_sms_code(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
|
||||
|
||||
if (!class_exists('AKSRT_SMS')) {
|
||||
return $this->error('短信服务不可用');
|
||||
}
|
||||
|
||||
$result = AKSRT_SMS::get_instance()->send_code($phone);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'message' => $result['message'],
|
||||
'remaining_time' => $result['remaining_time'] ?? 60,
|
||||
]);
|
||||
}
|
||||
|
||||
$response = [
|
||||
'success' => false,
|
||||
'message' => $result['message'],
|
||||
];
|
||||
if (isset($result['remaining_time'])) {
|
||||
$response['data'] = ['remaining_time' => $result['remaining_time']];
|
||||
}
|
||||
return new WP_REST_Response($response, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-register — 手机号注册
|
||||
*/
|
||||
public function auth_phone_register(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = AKSRT_Auth_Service::register_by_phone($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? '注册成功',
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
'user_id' => $result['user_id'] ?? 0,
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '注册失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-login — 手机号 + 密码登录
|
||||
*/
|
||||
public function auth_phone_login(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = AKSRT_Auth_Service::login_by_phone_password($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '登录失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-code-login — 手机号 + 验证码免密登录
|
||||
*/
|
||||
public function auth_phone_code_login(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = AKSRT_Auth_Service::login_by_phone_code($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '登录失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/bind-phone — 绑定手机号
|
||||
*/
|
||||
public function auth_bind_phone(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Auth_Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = AKSRT_Auth_Service::bind_phone($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success(['message' => $result['message']]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '绑定失败');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin follow operations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Follow_Operations {
|
||||
|
||||
/**
|
||||
* POST /follow/toggle — 切换关注状态
|
||||
*/
|
||||
public function follow_toggle(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Follow')) {
|
||||
return $this->error('关注系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$target_user_id = (int) ($params['target_user_id'] ?? 0);
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
if ($target_user_id <= 0 || $target_user_id === $current_user_id) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$target_user = get_userdata($target_user_id);
|
||||
if (!$target_user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
|
||||
$is_following = AKSRT_Follow::get_instance()->toggle($current_user_id, $target_user_id);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'is_following' => $is_following,
|
||||
'follower_count' => AKSRT_Follow::get_instance()->get_follower_count($target_user_id),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /follow/status — 查询关注状态
|
||||
*/
|
||||
public function follow_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Follow')) {
|
||||
return $this->success(['data' => ['is_following' => false]]);
|
||||
}
|
||||
|
||||
$target_user_id = (int) $request->get_param('target_user_id');
|
||||
$current_user_id = get_current_user_id();
|
||||
|
||||
if ($target_user_id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
$is_following = AKSRT_Follow::get_instance()->is_following($current_user_id, $target_user_id);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'is_following' => $is_following,
|
||||
'follower_count' => AKSRT_Follow::get_instance()->get_follower_count($target_user_id),
|
||||
'following_count' => AKSRT_Follow::get_instance()->get_following_count($target_user_id),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /follow/counts — 获取关注/粉丝数
|
||||
*/
|
||||
public function follow_counts(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Follow')) {
|
||||
return $this->success(['data' => ['followers' => 0, 'following' => 0]]);
|
||||
}
|
||||
|
||||
$user_id = (int) $request->get_param('user_id');
|
||||
if ($user_id <= 0) {
|
||||
return $this->error('参数无效');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'followers' => AKSRT_Follow::get_instance()->get_follower_count($user_id),
|
||||
'following' => AKSRT_Follow::get_instance()->get_following_count($user_id),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin invite operations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Invite_Operations {
|
||||
|
||||
/**
|
||||
* GET /invite/codes — 邀请码列表(管理后台)
|
||||
*/
|
||||
public function get_invite_codes(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$source = sanitize_key((string) $request->get_param('source'));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$usermeta_table = $wpdb->usermeta;
|
||||
$invite_table = class_exists('AKSRT_DB_Migrator') ? AKSRT_DB_Migrator::table('invite') : '';
|
||||
|
||||
$invite_rows = [];
|
||||
if ($invite_table) {
|
||||
$records = $wpdb->get_results("SELECT * FROM {$invite_table}", ARRAY_A);
|
||||
foreach ((array) $records as $record) {
|
||||
$invite_rows[(string) $record['invite_code']] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$user_rows = $wpdb->get_results(
|
||||
"SELECT user_id, meta_value AS invite_code FROM {$usermeta_table} WHERE meta_key = 'aksrt_invite_code' ORDER BY umeta_id DESC",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
foreach ((array) $user_rows as $row) {
|
||||
$user_id = (int) $row['user_id'];
|
||||
$user = get_userdata($user_id);
|
||||
$code = sanitize_key((string) $row['invite_code']);
|
||||
$record = $invite_rows[$code] ?? null;
|
||||
|
||||
$item = [
|
||||
'row_key' => 'user:' . $user_id,
|
||||
'source_type' => 'user',
|
||||
'source_label' => __('用户邀请码', 'aksrt'),
|
||||
'user' => $this->build_admin_user_summary($user_id, $user),
|
||||
'invite_code' => $code,
|
||||
'status' => $record ? 'used' : 'active',
|
||||
'status_label' => $record ? __('已使用', 'aksrt') : __('可用', 'aksrt'),
|
||||
'created_at' => $user ? (string) $user->user_registered : '',
|
||||
'used_at' => $record['created_at'] ?? '',
|
||||
'note' => '',
|
||||
'batch_id' => '',
|
||||
'created_by' => null,
|
||||
'registration_status' => $record['status'] ?? '',
|
||||
'invitee' => $this->build_admin_user_summary((int) ($record['invitee_id'] ?? 0)),
|
||||
'can_bulk' => false,
|
||||
'actions' => ['disable'],
|
||||
];
|
||||
|
||||
if ($this->invite_item_matches_filters($item, $keyword, $status, $source, $date_from, $date_to)) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->load_standalone_invite_codes() as $entry) {
|
||||
$code = sanitize_key((string) ($entry['code'] ?? ''));
|
||||
$record = $invite_rows[$code] ?? null;
|
||||
$used_by = (int) ($entry['used_by'] ?? 0);
|
||||
$disabled = !empty($entry['disabled']);
|
||||
$used = !empty($entry['used']) || $used_by > 0 || !empty($record['invitee_id']);
|
||||
$status_key = $disabled ? 'disabled' : ($used ? 'used' : 'active');
|
||||
$status_map = [
|
||||
'active' => __('可用', 'aksrt'),
|
||||
'used' => __('已使用', 'aksrt'),
|
||||
'disabled' => __('已禁用', 'aksrt'),
|
||||
];
|
||||
$invitee_id = $used_by > 0 ? $used_by : (int) ($record['invitee_id'] ?? 0);
|
||||
|
||||
$item = [
|
||||
'row_key' => 'standalone:' . $code,
|
||||
'source_type' => 'standalone',
|
||||
'source_label' => __('独立邀请码', 'aksrt'),
|
||||
'user' => null,
|
||||
'invite_code' => $code,
|
||||
'status' => $status_key,
|
||||
'status_label' => $status_map[$status_key],
|
||||
'created_at' => (string) ($entry['created_at'] ?? ''),
|
||||
'used_at' => (string) ($entry['used_at'] ?? ($record['created_at'] ?? '')),
|
||||
'note' => (string) ($entry['note'] ?? ''),
|
||||
'batch_id' => (string) ($entry['batch_id'] ?? ''),
|
||||
'created_by' => $this->build_admin_user_summary((int) ($entry['created_by'] ?? 0)),
|
||||
'registration_status' => $record['status'] ?? '',
|
||||
'invitee' => $this->build_admin_user_summary($invitee_id),
|
||||
'can_bulk' => true,
|
||||
'actions' => ['disable', 'delete'],
|
||||
];
|
||||
|
||||
if ($this->invite_item_matches_filters($item, $keyword, $status, $source, $date_from, $date_to)) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
usort($items, static function ($left, $right) {
|
||||
return strcmp((string) ($right['created_at'] ?? ''), (string) ($left['created_at'] ?? ''));
|
||||
});
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$paged = array_slice($items, $offset, $per_page);
|
||||
$stats = $this->build_invite_operation_stats($items, $invite_rows);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values($paged),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_pages' => max(1, (int) ceil(max(1, $total) / $per_page)),
|
||||
'stats' => $stats,
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('全部状态', 'aksrt'), 'value' => ''],
|
||||
['label' => __('可用', 'aksrt'), 'value' => 'active'],
|
||||
['label' => __('已使用', 'aksrt'), 'value' => 'used'],
|
||||
['label' => __('已禁用', 'aksrt'), 'value' => 'disabled'],
|
||||
],
|
||||
'sources' => [
|
||||
['label' => __('全部来源', 'aksrt'), 'value' => ''],
|
||||
['label' => __('用户邀请码', 'aksrt'), 'value' => 'user'],
|
||||
['label' => __('独立邀请码', 'aksrt'), 'value' => 'standalone'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /invite/codes — 批量生成邀请码(管理后台)
|
||||
* Body: { count: int, length: int, prefix: string }
|
||||
* 为指定用户或批量创建邀请码。
|
||||
*/
|
||||
public function generate_invite_codes(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Invite')) {
|
||||
return $this->error(__('邀请系统未启用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$count = max(1, min(100, (int) ($params['count'] ?? 1)));
|
||||
$length = max(4, min(12, (int) ($params['length'] ?? 6)));
|
||||
$prefix = sanitize_key((string) ($params['prefix'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
$batch_id = sanitize_text_field((string) ($params['batch_id'] ?? ''));
|
||||
|
||||
if ($batch_id === '') {
|
||||
$batch_id = 'INV-' . gmdate('YmdHis') . '-' . wp_generate_password(4, false, false);
|
||||
}
|
||||
|
||||
$generated = [];
|
||||
$codes = $this->load_standalone_invite_codes();
|
||||
$alphabet = 'abcdefghjkmnpqrstuvwxyz23456789';
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$code = '';
|
||||
for ($attempt = 0; $attempt < 12; $attempt++) {
|
||||
$candidate = $prefix;
|
||||
for ($j = 0; $j < $length; $j++) {
|
||||
$candidate .= $alphabet[random_int(0, strlen($alphabet) - 1)];
|
||||
}
|
||||
$candidate = sanitize_key($candidate);
|
||||
if (!$this->invite_code_exists_globally($candidate, $codes)) {
|
||||
$code = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($code === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$codes[] = [
|
||||
'code' => $code,
|
||||
'created_at' => current_time('mysql'),
|
||||
'used' => false,
|
||||
'used_by' => 0,
|
||||
'used_at' => '',
|
||||
'disabled' => false,
|
||||
'note' => $note,
|
||||
'batch_id' => $batch_id,
|
||||
'created_by' => get_current_user_id(),
|
||||
];
|
||||
$generated[] = $code;
|
||||
}
|
||||
|
||||
$this->save_standalone_invite_codes($codes);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf(__('已生成 %d 个邀请码', 'aksrt'), count($generated)),
|
||||
'data' => [
|
||||
'codes' => $generated,
|
||||
'batch_id' => $batch_id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /invite/codes/{id}/disable — 禁用邀请码
|
||||
*/
|
||||
public function disable_invite_code(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('id');
|
||||
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$removed = $this->remove_user_invite_code($user_id);
|
||||
if (!$removed) {
|
||||
return $this->error(__('邀请码不存在或已禁用', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('邀请码已禁用', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function bulk_update_invite_codes(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$action = sanitize_key((string) ($params['action'] ?? ''));
|
||||
$targets = isset($params['targets']) && is_array($params['targets']) ? $params['targets'] : [];
|
||||
|
||||
if (!in_array($action, ['disable', 'delete'], true) || empty($targets)) {
|
||||
return $this->error(__('批量操作参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$codes = $this->load_standalone_invite_codes();
|
||||
$processed = 0;
|
||||
|
||||
foreach ($targets as $target) {
|
||||
$source_type = sanitize_key((string) ($target['source_type'] ?? ''));
|
||||
if ($source_type === 'user') {
|
||||
$user_id = (int) ($target['user_id'] ?? 0);
|
||||
if ($action === 'disable' && $this->remove_user_invite_code($user_id)) {
|
||||
$processed++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($source_type !== 'standalone') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$code = sanitize_key((string) ($target['code'] ?? $target['invite_code'] ?? ''));
|
||||
foreach ($codes as $index => $entry) {
|
||||
$entry_code = sanitize_key((string) ($entry['code'] ?? ''));
|
||||
if ($entry_code !== $code) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($action === 'delete' && empty($entry['used']) && empty($entry['used_by'])) {
|
||||
unset($codes[$index]);
|
||||
} else {
|
||||
$entry['disabled'] = true;
|
||||
$codes[$index] = $entry;
|
||||
}
|
||||
$processed++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->save_standalone_invite_codes($codes);
|
||||
|
||||
return $this->success([
|
||||
'message' => sprintf(__('已处理 %d 条邀请码记录', 'aksrt'), $processed),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /invite/stats — 邀请系统统计
|
||||
*/
|
||||
public function get_invite_stats(): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$items = [];
|
||||
$user_records = $wpdb->get_results(
|
||||
"SELECT user_id, meta_value AS invite_code FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_invite_code'",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
foreach ((array) $user_records as $row) {
|
||||
$items[] = [
|
||||
'source_type' => 'user',
|
||||
'status' => 'active',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($this->load_standalone_invite_codes() as $entry) {
|
||||
$used = !empty($entry['used']) || !empty($entry['used_by']);
|
||||
$disabled = !empty($entry['disabled']);
|
||||
$items[] = [
|
||||
'source_type' => 'standalone',
|
||||
'status' => $disabled ? 'disabled' : ($used ? 'used' : 'active'),
|
||||
];
|
||||
}
|
||||
|
||||
$stats = $this->build_invite_operation_stats($items);
|
||||
return $this->success(['data' => $stats]);
|
||||
}
|
||||
|
||||
private function load_standalone_invite_codes(): array {
|
||||
$codes = get_option('aksrt_standalone_invite_codes', []);
|
||||
return is_array($codes) ? array_values($codes) : [];
|
||||
}
|
||||
|
||||
private function save_standalone_invite_codes(array $codes): void {
|
||||
update_option('aksrt_standalone_invite_codes', array_values($codes), false);
|
||||
}
|
||||
|
||||
private function invite_code_exists_globally(string $candidate, array $standalone_codes = []): bool {
|
||||
if ($candidate === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$existing_user_code = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT umeta_id FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_invite_code' AND meta_value = %s LIMIT 1",
|
||||
$candidate
|
||||
));
|
||||
if ($existing_user_code) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($standalone_codes as $entry) {
|
||||
if (sanitize_key((string) ($entry['code'] ?? '')) === $candidate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$invite_table = class_exists('AKSRT_DB_Migrator') ? AKSRT_DB_Migrator::table('invite') : '';
|
||||
if ($invite_table) {
|
||||
$existing_invite = $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT id FROM {$invite_table} WHERE invite_code = %s LIMIT 1",
|
||||
$candidate
|
||||
));
|
||||
if ($existing_invite) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function invite_item_matches_filters(array $item, string $keyword, string $status, string $source, string $date_from, string $date_to): bool {
|
||||
if ($status !== '' && ($item['status'] ?? '') !== $status) {
|
||||
return false;
|
||||
}
|
||||
if ($source !== '' && ($item['source_type'] ?? '') !== $source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$created_at = (string) ($item['created_at'] ?? '');
|
||||
if ($date_from !== '' && $created_at !== '' && strcmp($created_at, $date_from . ' 00:00:00') < 0) {
|
||||
return false;
|
||||
}
|
||||
if ($date_to !== '' && $created_at !== '' && strcmp($created_at, $date_to . ' 23:59:59') > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($keyword === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$haystack = implode(' ', array_filter([
|
||||
(string) ($item['invite_code'] ?? ''),
|
||||
(string) ($item['status_label'] ?? ''),
|
||||
(string) ($item['note'] ?? ''),
|
||||
(string) ($item['batch_id'] ?? ''),
|
||||
(string) ($item['user']['display_name'] ?? ''),
|
||||
(string) ($item['invitee']['display_name'] ?? ''),
|
||||
(string) ($item['invitee']['email'] ?? ''),
|
||||
]));
|
||||
|
||||
return mb_stripos($haystack, $keyword) !== false;
|
||||
}
|
||||
|
||||
private function build_invite_operation_stats(array $items, array $invite_rows = []): array {
|
||||
$stats = [
|
||||
'total_codes' => count($items),
|
||||
'active_codes' => 0,
|
||||
'used_codes' => 0,
|
||||
'disabled_codes' => 0,
|
||||
'user_codes' => 0,
|
||||
'standalone_codes' => 0,
|
||||
'total_invites' => count($invite_rows),
|
||||
'total_rewarded' => 0,
|
||||
];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$status = $item['status'] ?? '';
|
||||
if ($status === 'active') {
|
||||
$stats['active_codes']++;
|
||||
} elseif ($status === 'used') {
|
||||
$stats['used_codes']++;
|
||||
} elseif ($status === 'disabled') {
|
||||
$stats['disabled_codes']++;
|
||||
}
|
||||
|
||||
if (($item['source_type'] ?? '') === 'user') {
|
||||
$stats['user_codes']++;
|
||||
} elseif (($item['source_type'] ?? '') === 'standalone') {
|
||||
$stats['standalone_codes']++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($invite_rows as $row) {
|
||||
if (($row['status'] ?? '') === 'rewarded') {
|
||||
$stats['total_rewarded']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function remove_user_invite_code(int $user_id): bool {
|
||||
$user_id = (int) $user_id;
|
||||
if ($user_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = get_user_meta($user_id, 'aksrt_invite_code', true);
|
||||
if (!$code) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delete_user_meta($user_id, 'aksrt_invite_code');
|
||||
|
||||
$map = get_option('aksrt_invite_code_map', []);
|
||||
if (is_array($map) && isset($map[$code])) {
|
||||
unset($map[$code]);
|
||||
update_option('aksrt_invite_code_map', $map, false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin report operations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Report_Operations {
|
||||
|
||||
public function get_report_list(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Report_Center')) {
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => [],
|
||||
'reports' => [],
|
||||
'counts' => ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0],
|
||||
'total' => 0,
|
||||
'page' => 1,
|
||||
'options' => [
|
||||
'statuses' => $this->get_report_status_options(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$table = AKSRT_DB_Migrator::table('report');
|
||||
if (!$table) {
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => [],
|
||||
'reports' => [],
|
||||
'counts' => ['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0],
|
||||
'total' => 0,
|
||||
'page' => 1,
|
||||
'options' => [
|
||||
'statuses' => $this->get_report_status_options(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: $request->get_param('limit') ?: 20)));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$category = sanitize_key((string) $request->get_param('category'));
|
||||
$target_type = sanitize_key((string) $request->get_param('target_type'));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if ($status !== '') {
|
||||
$where[] = 'status = %s';
|
||||
$params[] = $status;
|
||||
}
|
||||
if ($category !== '') {
|
||||
$where[] = 'category = %s';
|
||||
$params[] = $category;
|
||||
}
|
||||
if ($target_type !== '') {
|
||||
$where[] = 'target_type = %s';
|
||||
$params[] = $target_type;
|
||||
}
|
||||
if ($date_from !== '') {
|
||||
$where[] = 'created_at >= %s';
|
||||
$params[] = $date_from . ' 00:00:00';
|
||||
}
|
||||
if ($date_to !== '') {
|
||||
$where[] = 'created_at <= %s';
|
||||
$params[] = $date_to . ' 23:59:59';
|
||||
}
|
||||
|
||||
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
$sql = "SELECT * FROM {$table} {$where_sql} ORDER BY created_at DESC LIMIT 500";
|
||||
$rows = $params ? $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A) : $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
$items = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$item = $this->build_report_admin_item($row);
|
||||
if ($keyword !== '' && !$this->report_matches_keyword($item, $keyword)) {
|
||||
continue;
|
||||
}
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
$paged = array_slice($items, $offset, $per_page);
|
||||
$counts = AKSRT_Report_Center::get_instance()->get_status_counts();
|
||||
$categories = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$cat = sanitize_key((string) ($row['category'] ?? ''));
|
||||
if ($cat !== '') {
|
||||
$categories[$cat] = $cat;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values($paged),
|
||||
'reports' => array_values($paged),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'counts' => $counts,
|
||||
'options' => [
|
||||
'statuses' => $this->get_report_status_options(),
|
||||
'actions' => $this->get_report_action_options(),
|
||||
'targets' => $this->get_report_target_type_options(),
|
||||
'categories' => array_map(static function ($item) {
|
||||
return ['label' => $item, 'value' => $item];
|
||||
}, array_values($categories)),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update_report_status(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Report_Center')) {
|
||||
return $this->error(__('举报系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$report_id = (int) ($params['report_id'] ?? 0);
|
||||
if ($report_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->handle_report_process($report_id, $params);
|
||||
}
|
||||
|
||||
public function get_report_counts(): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Report_Center')) {
|
||||
return $this->success(['pending' => 0, 'reviewing' => 0, 'resolved' => 0, 'dismissed' => 0, 'total' => 0]);
|
||||
}
|
||||
return $this->success(AKSRT_Report_Center::get_instance()->get_status_counts());
|
||||
}
|
||||
|
||||
public function get_report_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$report_id = (int) $request->get_param('id');
|
||||
$table = AKSRT_DB_Migrator::table('report');
|
||||
|
||||
if ($report_id <= 0 || !$table) {
|
||||
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
|
||||
if (!$row) {
|
||||
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$related = (int) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT COUNT(*) FROM {$table} WHERE target_type = %s AND target_id = %d",
|
||||
sanitize_key((string) $row['target_type']),
|
||||
(int) $row['target_id']
|
||||
));
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'report' => $this->build_report_admin_item($row),
|
||||
'related' => ['same_target_count' => $related],
|
||||
'options' => [
|
||||
'statuses' => $this->get_report_status_options(),
|
||||
'actions' => $this->get_report_action_options(sanitize_key((string) $row['target_type'])),
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function process_report(WP_REST_Request $request): WP_REST_Response {
|
||||
$report_id = (int) $request->get_param('id');
|
||||
if ($report_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->handle_report_process($report_id, $this->get_json_params($request));
|
||||
}
|
||||
|
||||
private function handle_report_process(int $report_id, array $payload): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$table = AKSRT_DB_Migrator::table('report');
|
||||
if (!$table) {
|
||||
return $this->error(__('举报系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$status = sanitize_key((string) ($payload['status'] ?? ''));
|
||||
$action_taken = sanitize_key((string) ($payload['action_taken'] ?? 'none'));
|
||||
$note = sanitize_textarea_field((string) ($payload['note'] ?? ''));
|
||||
|
||||
$valid_statuses = array_column($this->get_report_status_options(), 'value');
|
||||
$valid_actions = array_column($this->get_report_action_options(), 'value');
|
||||
|
||||
if ($status === '' || !in_array($status, $valid_statuses, true)) {
|
||||
return $this->error(__('处理状态无效', 'aksrt'));
|
||||
}
|
||||
if (!in_array($action_taken, $valid_actions, true)) {
|
||||
$action_taken = 'none';
|
||||
}
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id = %d", $report_id), ARRAY_A);
|
||||
if (!$row) {
|
||||
return $this->error(__('举报工单不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$effect = $this->apply_report_action(
|
||||
sanitize_key((string) $row['target_type']),
|
||||
(int) $row['target_id'],
|
||||
$action_taken
|
||||
);
|
||||
|
||||
$notes = json_decode((string) ($row['notes_json'] ?? '[]'), true);
|
||||
if (!is_array($notes)) {
|
||||
$notes = [];
|
||||
}
|
||||
$notes[] = [
|
||||
'note' => $note,
|
||||
'status' => $status,
|
||||
'action_taken' => $action_taken,
|
||||
'operator' => get_current_user_id(),
|
||||
'timestamp' => current_time('mysql'),
|
||||
'effect' => $effect,
|
||||
];
|
||||
|
||||
$updated = $wpdb->update(
|
||||
$table,
|
||||
[
|
||||
'status' => $status,
|
||||
'updated_at' => current_time('mysql'),
|
||||
'notes_json' => wp_json_encode($notes),
|
||||
],
|
||||
['id' => $report_id],
|
||||
['%s', '%s', '%s'],
|
||||
['%d']
|
||||
);
|
||||
|
||||
if ($updated === false) {
|
||||
return $this->error(__('处理失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => __('举报工单已处理', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function build_report_admin_item(array $row): array {
|
||||
$reporter = get_userdata((int) ($row['reporter_id'] ?? 0));
|
||||
$notes = json_decode((string) ($row['notes_json'] ?? '[]'), true);
|
||||
if (!is_array($notes)) {
|
||||
$notes = [];
|
||||
}
|
||||
|
||||
$normalized_notes = [];
|
||||
foreach ($notes as $entry) {
|
||||
$operator_id = (int) ($entry['operator'] ?? 0);
|
||||
$operator_user = $operator_id > 0 ? get_userdata($operator_id) : null;
|
||||
$normalized_notes[] = [
|
||||
'note' => (string) ($entry['note'] ?? ''),
|
||||
'status' => sanitize_key((string) ($entry['status'] ?? '')),
|
||||
'action_taken' => sanitize_key((string) ($entry['action_taken'] ?? 'none')),
|
||||
'operator' => $operator_id,
|
||||
'operator_name' => $operator_user ? $operator_user->display_name : '',
|
||||
'timestamp' => (string) ($entry['timestamp'] ?? ''),
|
||||
'effect' => (string) ($entry['effect'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$last_note = !empty($normalized_notes) ? $normalized_notes[count($normalized_notes) - 1] : null;
|
||||
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'target_type' => sanitize_key((string) ($row['target_type'] ?? '')),
|
||||
'target_id' => (int) ($row['target_id'] ?? 0),
|
||||
'reason' => (string) ($row['reason'] ?? ''),
|
||||
'category' => sanitize_key((string) ($row['category'] ?? '')),
|
||||
'status' => sanitize_key((string) ($row['status'] ?? 'pending')),
|
||||
'created_at' => (string) ($row['created_at'] ?? ''),
|
||||
'updated_at' => (string) ($row['updated_at'] ?? ''),
|
||||
'reporter' => $this->build_admin_user_summary($reporter ? (int) $reporter->ID : 0, $reporter),
|
||||
'target' => $this->build_report_target_summary(
|
||||
sanitize_key((string) ($row['target_type'] ?? '')),
|
||||
(int) ($row['target_id'] ?? 0)
|
||||
),
|
||||
'notes' => $normalized_notes,
|
||||
'note_count' => count($normalized_notes),
|
||||
'last_note' => $last_note,
|
||||
];
|
||||
}
|
||||
|
||||
private function report_matches_keyword(array $item, string $keyword): bool {
|
||||
$haystack = implode(' ', array_filter([
|
||||
(string) ($item['reason'] ?? ''),
|
||||
(string) ($item['category'] ?? ''),
|
||||
(string) ($item['reporter']['display_name'] ?? ''),
|
||||
(string) ($item['reporter']['email'] ?? ''),
|
||||
(string) ($item['target']['title'] ?? ''),
|
||||
(string) ($item['target']['display_name'] ?? ''),
|
||||
(string) ($item['target']['excerpt'] ?? ''),
|
||||
(string) ($item['target_id'] ?? ''),
|
||||
]));
|
||||
|
||||
return mb_stripos($haystack, $keyword) !== false;
|
||||
}
|
||||
|
||||
private function build_report_target_summary(string $target_type, int $target_id): array {
|
||||
$summary = [
|
||||
'type' => $target_type,
|
||||
'id' => $target_id,
|
||||
'title' => '',
|
||||
'excerpt' => '',
|
||||
'display_name' => '',
|
||||
'status' => '',
|
||||
'url' => '',
|
||||
'ban_state' => '',
|
||||
];
|
||||
|
||||
if ($target_type === 'post') {
|
||||
$post = get_post($target_id);
|
||||
if ($post instanceof WP_Post) {
|
||||
$summary['title'] = get_the_title($post);
|
||||
$summary['status'] = (string) $post->post_status;
|
||||
$summary['url'] = get_permalink($post);
|
||||
}
|
||||
} elseif ($target_type === 'comment') {
|
||||
$comment = get_comment($target_id);
|
||||
if ($comment instanceof WP_Comment) {
|
||||
$summary['display_name'] = (string) $comment->comment_author;
|
||||
$summary['excerpt'] = wp_trim_words(wp_strip_all_tags((string) $comment->comment_content), 24);
|
||||
$summary['status'] = $comment->comment_approved === '1' ? 'approved' : (string) $comment->comment_approved;
|
||||
$summary['url'] = get_comment_link($comment);
|
||||
if (!empty($comment->comment_post_ID)) {
|
||||
$summary['title'] = get_the_title((int) $comment->comment_post_ID);
|
||||
}
|
||||
}
|
||||
} elseif ($target_type === 'user') {
|
||||
$user = get_userdata($target_id);
|
||||
if ($user instanceof WP_User) {
|
||||
$summary['display_name'] = $user->display_name;
|
||||
$summary['title'] = $user->display_name;
|
||||
$summary['url'] = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($target_id) : '';
|
||||
$summary['ban_state'] = (string) get_user_meta($target_id, 'aksrt_user_ban_state', true);
|
||||
}
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
private function apply_report_action(string $target_type, int $target_id, string $action_taken): string {
|
||||
if ($target_id <= 0 || $action_taken === 'none') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($target_type === 'user') {
|
||||
if (in_array($action_taken, ['muted', 'restricted', 'banned'], true)) {
|
||||
update_user_meta($target_id, 'aksrt_user_ban_state', $action_taken);
|
||||
return sprintf(__('已同步用户限制状态:%s', 'aksrt'), $action_taken);
|
||||
}
|
||||
|
||||
if ($action_taken === 'clear') {
|
||||
delete_user_meta($target_id, 'aksrt_user_ban_state');
|
||||
return __('已清除用户限制状态', 'aksrt');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($action_taken === 'content_removed' && $target_type === 'post' && get_post($target_id)) {
|
||||
wp_update_post([
|
||||
'ID' => $target_id,
|
||||
'post_status' => 'pending',
|
||||
]);
|
||||
return __('已将内容改为待审状态', 'aksrt');
|
||||
}
|
||||
|
||||
if ($action_taken === 'content_removed' && $target_type === 'comment' && get_comment($target_id)) {
|
||||
wp_set_comment_status($target_id, 'hold', true);
|
||||
return __('已将评论改为待审状态', 'aksrt');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function get_report_status_options(): array {
|
||||
return [
|
||||
['label' => __('待处理', 'aksrt'), 'value' => 'pending'],
|
||||
['label' => __('处理中', 'aksrt'), 'value' => 'reviewing'],
|
||||
['label' => __('已解决', 'aksrt'), 'value' => 'resolved'],
|
||||
['label' => __('已驳回', 'aksrt'), 'value' => 'dismissed'],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_report_target_type_options(): array {
|
||||
return [
|
||||
['label' => __('文章', 'aksrt'), 'value' => 'post'],
|
||||
['label' => __('评论', 'aksrt'), 'value' => 'comment'],
|
||||
['label' => __('用户', 'aksrt'), 'value' => 'user'],
|
||||
];
|
||||
}
|
||||
|
||||
private function get_report_action_options(string $target_type = ''): array {
|
||||
$common = [
|
||||
['label' => __('仅更新状态', 'aksrt'), 'value' => 'none'],
|
||||
['label' => __('升级人工处理', 'aksrt'), 'value' => 'escalated'],
|
||||
];
|
||||
|
||||
if ($target_type === 'user' || $target_type === '') {
|
||||
$common = array_merge($common, [
|
||||
['label' => __('发送警告', 'aksrt'), 'value' => 'warned'],
|
||||
['label' => __('禁言', 'aksrt'), 'value' => 'muted'],
|
||||
['label' => __('限制功能', 'aksrt'), 'value' => 'restricted'],
|
||||
['label' => __('封禁账号', 'aksrt'), 'value' => 'banned'],
|
||||
['label' => __('解除限制', 'aksrt'), 'value' => 'clear'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($target_type === 'post' || $target_type === 'comment' || $target_type === '') {
|
||||
$common[] = ['label' => __('转为待审内容', 'aksrt'), 'value' => 'content_removed'];
|
||||
}
|
||||
|
||||
return $common;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /report/submit — 前台举报提交(登录用户)
|
||||
*/
|
||||
public function submit_report(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Report_Center')) {
|
||||
return $this->error('举报系统未启用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$target_type = sanitize_key((string) ($params['target_type'] ?? ''));
|
||||
$target_id = (int) ($params['target_id'] ?? 0);
|
||||
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
||||
$category = sanitize_key((string) ($params['category'] ?? ''));
|
||||
|
||||
if ($target_type === '' || $target_id <= 0 || $reason === '') {
|
||||
return $this->error('请填写完整的举报信息');
|
||||
}
|
||||
|
||||
$valid_types = ['post', 'comment', 'user'];
|
||||
if (!in_array($target_type, $valid_types, true)) {
|
||||
return $this->error('无效的举报类型');
|
||||
}
|
||||
|
||||
$report_id = AKSRT_Report_Center::get_instance()->submit(
|
||||
get_current_user_id(),
|
||||
$target_type,
|
||||
$target_id,
|
||||
$reason,
|
||||
$category
|
||||
);
|
||||
|
||||
if ($report_id > 0) {
|
||||
return $this->success([
|
||||
'message' => __('举报已提交,我们会尽快处理', 'aksrt'),
|
||||
'data' => ['report_id' => $report_id],
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error(__('举报提交失败', 'aksrt'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin user restriction operations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Restriction_Operations {
|
||||
|
||||
public function get_user_restrictions(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('user_id');
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_User_Restriction')) {
|
||||
return $this->success(['data' => ['restrictions' => []]]);
|
||||
}
|
||||
|
||||
$restrictions = AKSRT_User_Restriction::get_instance()->get_user_restrictions($user_id, false);
|
||||
return $this->success(['data' => ['restrictions' => $restrictions]]);
|
||||
}
|
||||
|
||||
public function add_user_restriction(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('user_id');
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
|
||||
$params = $request->get_json_params() ?? [];
|
||||
$type = sanitize_key((string) ($params['type'] ?? ''));
|
||||
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
||||
$scope = sanitize_key((string) ($params['scope'] ?? 'global'));
|
||||
$expires_at = sanitize_text_field((string) ($params['expires_at'] ?? ''));
|
||||
|
||||
if (!$type) {
|
||||
return $this->error('请选择限制类型');
|
||||
}
|
||||
|
||||
if (!class_exists('AKSRT_User_Restriction')) {
|
||||
return $this->error('限制系统未启用', 501);
|
||||
}
|
||||
|
||||
$result = AKSRT_User_Restriction::get_instance()->restrict(
|
||||
$user_id, $type, $reason, $scope, $expires_at, get_current_user_id()
|
||||
);
|
||||
|
||||
if (!$result['success']) {
|
||||
return $this->error($result['message']);
|
||||
}
|
||||
|
||||
return $this->success(['message' => '限制已添加', 'data' => $result]);
|
||||
}
|
||||
|
||||
public function remove_user_restriction(WP_REST_Request $request): WP_REST_Response {
|
||||
$restriction_id = (int) $request->get_param('id');
|
||||
|
||||
if (!class_exists('AKSRT_User_Restriction')) {
|
||||
return $this->error('限制系统未启用', 501);
|
||||
}
|
||||
|
||||
$result = AKSRT_User_Restriction::get_instance()->remove_restriction($restriction_id);
|
||||
if (!$result) {
|
||||
return $this->error('移除失败', 500);
|
||||
}
|
||||
|
||||
return $this->success(['message' => '限制已移除']);
|
||||
}
|
||||
|
||||
public function get_restriction_stats(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_User_Restriction')) {
|
||||
return $this->success(['data' => ['total' => 0, 'active' => 0, 'banned' => 0]]);
|
||||
}
|
||||
|
||||
$stats = AKSRT_User_Restriction::get_instance()->get_stats();
|
||||
return $this->success(['data' => $stats]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT admin verify and messaging integrations support.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_User_Controller_Verify_Integrations {
|
||||
|
||||
public function get_verify_pending(): WP_REST_Response {
|
||||
$response = $this->get_verify_requests(new WP_REST_Request('GET', '/'));
|
||||
$data = $response->get_data();
|
||||
$items = isset($data['data']['items']) && is_array($data['data']['items']) ? $data['data']['items'] : [];
|
||||
$pending = array_values(array_filter($items, static function ($item) {
|
||||
return ($item['status'] ?? '') === 'pending';
|
||||
}));
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'requests' => array_slice($pending, 0, 50),
|
||||
'total' => count($pending),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_verify_requests(WP_REST_Request $request): WP_REST_Response {
|
||||
global $wpdb;
|
||||
|
||||
$page = max(1, (int) ($request->get_param('page') ?: 1));
|
||||
$per_page = max(1, min(100, (int) ($request->get_param('per_page') ?: 20)));
|
||||
$keyword = trim(sanitize_text_field((string) $request->get_param('keyword')));
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$date_from = sanitize_text_field((string) $request->get_param('date_from'));
|
||||
$date_to = sanitize_text_field((string) $request->get_param('date_to'));
|
||||
|
||||
$sql = "
|
||||
SELECT u.ID, u.display_name, u.user_email, u.user_registered,
|
||||
status.meta_value AS verify_status,
|
||||
reason.meta_value AS verify_reason,
|
||||
reviewed_at.meta_value AS reviewed_at,
|
||||
reviewer.meta_value AS reviewer_id
|
||||
FROM {$wpdb->users} u
|
||||
INNER JOIN {$wpdb->usermeta} status
|
||||
ON status.user_id = u.ID AND status.meta_key = 'aksrt_user_verify_status'
|
||||
LEFT JOIN {$wpdb->usermeta} reason
|
||||
ON reason.user_id = u.ID AND reason.meta_key = 'aksrt_user_verify_reason'
|
||||
LEFT JOIN {$wpdb->usermeta} reviewed_at
|
||||
ON reviewed_at.user_id = u.ID AND reviewed_at.meta_key = 'aksrt_user_verify_reviewed_at'
|
||||
LEFT JOIN {$wpdb->usermeta} reviewer
|
||||
ON reviewer.user_id = u.ID AND reviewer.meta_key = 'aksrt_user_verify_reviewer'
|
||||
ORDER BY u.user_registered DESC
|
||||
LIMIT 500
|
||||
";
|
||||
|
||||
$rows = $wpdb->get_results($sql, ARRAY_A);
|
||||
|
||||
$items = [];
|
||||
$counts = ['pending' => 0, 'verified' => 0, 'rejected' => 0, 'total' => 0];
|
||||
|
||||
foreach ((array) $rows as $row) {
|
||||
$item = $this->build_verify_request_item($row);
|
||||
$item_status = $item['status'];
|
||||
if (isset($counts[$item_status])) {
|
||||
$counts[$item_status]++;
|
||||
}
|
||||
$counts['total']++;
|
||||
|
||||
if ($status !== '' && $item_status !== $status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($date_from !== '' && strcmp((string) $item['requested_at'], $date_from . ' 00:00:00') < 0) {
|
||||
continue;
|
||||
}
|
||||
if ($date_to !== '' && strcmp((string) $item['requested_at'], $date_to . ' 23:59:59') > 0) {
|
||||
continue;
|
||||
}
|
||||
if ($keyword !== '' && mb_stripos(implode(' ', [
|
||||
(string) ($item['display_name'] ?? ''),
|
||||
(string) ($item['email'] ?? ''),
|
||||
(string) ($item['reason'] ?? ''),
|
||||
]), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$offset = ($page - 1) * $per_page;
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values(array_slice($items, $offset, $per_page)),
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'counts' => $counts,
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('待审核', 'aksrt'), 'value' => 'pending'],
|
||||
['label' => __('已通过', 'aksrt'), 'value' => 'verified'],
|
||||
['label' => __('已驳回', 'aksrt'), 'value' => 'rejected'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_verify_request_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('id');
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('认证申请不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return $this->error(__('用户不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$detail = $this->build_verify_request_item([
|
||||
'ID' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'user_email' => $user->user_email,
|
||||
'user_registered' => $user->user_registered,
|
||||
'verify_status' => get_user_meta($user_id, 'aksrt_user_verify_status', true),
|
||||
'verify_reason' => get_user_meta($user_id, 'aksrt_user_verify_reason', true),
|
||||
'reviewed_at' => get_user_meta($user_id, 'aksrt_user_verify_reviewed_at', true),
|
||||
'reviewer_id' => get_user_meta($user_id, 'aksrt_user_verify_reviewer', true),
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'request' => $detail,
|
||||
'history' => $this->get_verify_history_for_user($user_id),
|
||||
'options' => [
|
||||
'statuses' => [
|
||||
['label' => __('通过认证', 'aksrt'), 'value' => 'verified'],
|
||||
['label' => __('驳回申请', 'aksrt'), 'value' => 'rejected'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /verify/review — approve or reject a verification request.
|
||||
*/
|
||||
public function review_verify_request(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$user_id = (int) ($params['user_id'] ?? 0);
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->perform_verify_review($user_id, $params);
|
||||
}
|
||||
|
||||
public function review_verify_request_by_id(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = (int) $request->get_param('id');
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->perform_verify_review($user_id, $this->get_json_params($request));
|
||||
}
|
||||
|
||||
private function perform_verify_review(int $user_id, array $payload): WP_REST_Response {
|
||||
$status = sanitize_key((string) ($payload['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($payload['note'] ?? ($payload['reason'] ?? '')));
|
||||
|
||||
if (!in_array($status, ['verified', 'rejected'], true)) {
|
||||
return $this->error(__('审核状态无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return $this->error(__('用户不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
update_user_meta($user_id, 'aksrt_user_verify_status', $status);
|
||||
update_user_meta($user_id, 'aksrt_user_verify_reviewed_at', current_time('mysql'));
|
||||
update_user_meta($user_id, 'aksrt_user_verify_reviewer', get_current_user_id());
|
||||
|
||||
if ($note !== '') {
|
||||
update_user_meta($user_id, 'aksrt_user_verify_reason', $note);
|
||||
} elseif ($status === 'verified') {
|
||||
delete_user_meta($user_id, 'aksrt_user_verify_reason');
|
||||
}
|
||||
|
||||
$log = get_option('aksrt_verify_review_log', []);
|
||||
if (!is_array($log)) {
|
||||
$log = [];
|
||||
}
|
||||
$log[] = [
|
||||
'user_id' => $user_id,
|
||||
'reviewer' => get_current_user_id(),
|
||||
'status' => $status,
|
||||
'reason' => $note,
|
||||
'time' => current_time('mysql'),
|
||||
];
|
||||
if (count($log) > 500) {
|
||||
$log = array_slice($log, -500);
|
||||
}
|
||||
update_option('aksrt_verify_review_log', $log, false);
|
||||
|
||||
return $this->success([
|
||||
'message' => $status === 'verified'
|
||||
? __('已通过认证', 'aksrt')
|
||||
: __('已驳回申请', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function build_verify_request_item(array $row): array {
|
||||
$user_id = (int) ($row['ID'] ?? 0);
|
||||
$reviewer_id = (int) ($row['reviewer_id'] ?? 0);
|
||||
$reviewer = $reviewer_id > 0 ? get_userdata($reviewer_id) : null;
|
||||
$status = sanitize_key((string) ($row['verify_status'] ?? 'pending'));
|
||||
$email_verified = (bool) get_user_meta($user_id, 'aksrt_email_verified', true);
|
||||
|
||||
return [
|
||||
'user_id' => $user_id,
|
||||
'display_name' => (string) ($row['display_name'] ?? ''),
|
||||
'email' => (string) ($row['user_email'] ?? ''),
|
||||
'status' => $status,
|
||||
'reason' => (string) ($row['verify_reason'] ?? ''),
|
||||
'requested_at' => (string) ($row['user_registered'] ?? ''),
|
||||
'reviewed_at' => (string) ($row['reviewed_at'] ?? ''),
|
||||
'email_verified' => $email_verified,
|
||||
'reviewer' => $this->build_admin_user_summary($reviewer_id, $reviewer),
|
||||
'history_count' => count($this->get_verify_history_for_user($user_id)),
|
||||
];
|
||||
}
|
||||
|
||||
private function get_verify_history_for_user(int $user_id): array {
|
||||
$log = get_option('aksrt_verify_review_log', []);
|
||||
if (!is_array($log)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$history = [];
|
||||
foreach ($log as $entry) {
|
||||
if ((int) ($entry['user_id'] ?? 0) !== $user_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reviewer_id = (int) ($entry['reviewer'] ?? 0);
|
||||
$history[] = [
|
||||
'status' => sanitize_key((string) ($entry['status'] ?? '')),
|
||||
'reason' => (string) ($entry['reason'] ?? ''),
|
||||
'time' => (string) ($entry['time'] ?? ''),
|
||||
'reviewer' => $reviewer_id,
|
||||
'reviewer_name' => $reviewer_id > 0 && ($user = get_userdata($reviewer_id)) ? $user->display_name : '',
|
||||
];
|
||||
}
|
||||
|
||||
usort($history, static function ($left, $right) {
|
||||
return strcmp((string) ($right['time'] ?? ''), (string) ($left['time'] ?? ''));
|
||||
});
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
private function build_admin_user_summary(int $user_id, $user = null): ?array {
|
||||
if ($user_id <= 0 && !$user instanceof WP_User) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$user instanceof WP_User) {
|
||||
$user = get_userdata($user_id);
|
||||
}
|
||||
if (!$user instanceof WP_User) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int) $user->ID,
|
||||
'display_name' => (string) $user->display_name,
|
||||
'email' => (string) $user->user_email,
|
||||
'avatar_url' => get_avatar_url((int) $user->ID, ['size' => 64]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /sms/status — SMS provider connection status.
|
||||
*/
|
||||
public function get_sms_status(): WP_REST_Response {
|
||||
if (class_exists('AKSRT_SMS')) {
|
||||
return $this->success(['data' => AKSRT_SMS::get_instance()->get_provider_status()]);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'enabled' => false,
|
||||
'provider' => 'none',
|
||||
'configured' => false,
|
||||
'available' => false,
|
||||
'verified' => false,
|
||||
'transport' => '',
|
||||
'missing_fields' => [],
|
||||
'code_length' => 6,
|
||||
'code_expire' => 300,
|
||||
'rate_limit' => 60,
|
||||
'daily_limit' => 10,
|
||||
'notify_bind' => true,
|
||||
'notify_login' => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /sms/test — Send a test SMS to verify provider configuration.
|
||||
*/
|
||||
public function test_sms_send(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_SMS')) {
|
||||
return $this->error('短信服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
|
||||
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return $this->error('手机号格式不正确');
|
||||
}
|
||||
|
||||
$result = AKSRT_SMS::get_instance()->test_provider($phone);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success(['message' => $result['message'] ?? '发送成功']);
|
||||
}
|
||||
|
||||
return $this->error($result['message'] ?? '发送失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /wechat/status — WeChat notification service status.
|
||||
*/
|
||||
public function get_wechat_status(): WP_REST_Response {
|
||||
if (class_exists('AKSRT_WeChat_Notify')) {
|
||||
return $this->success(['data' => AKSRT_WeChat_Notify::get_instance()->get_status()]);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'appid' => '',
|
||||
'configured' => false,
|
||||
'has_token' => false,
|
||||
'available' => false,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /wechat/settings — 更新微信通知设置
|
||||
*/
|
||||
public function update_wechat_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
|
||||
$settings = [
|
||||
'enabled' => !empty($params['enabled']),
|
||||
'app_id' => sanitize_text_field((string) ($params['app_id'] ?? '')),
|
||||
'app_secret' => sanitize_text_field((string) ($params['app_secret'] ?? '')),
|
||||
'template_id' => sanitize_text_field((string) ($params['template_id'] ?? '')),
|
||||
];
|
||||
|
||||
$existing = AKSRT_Settings::get_wechat_settings();
|
||||
if ($settings['app_secret'] === '***') {
|
||||
$settings['app_secret'] = $existing['app_secret'] ?? '';
|
||||
}
|
||||
|
||||
$merged = array_merge($existing, $settings);
|
||||
AKSRT_Settings::update_wechat_settings($merged);
|
||||
delete_transient('aksrt_wechat_token');
|
||||
|
||||
return $this->success(['message' => __('微信设置已保存', 'aksrt')]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT Admin — User & Interaction Controller
|
||||
*
|
||||
* Handles REST routes for the "用户&互动" domain:
|
||||
* Auth, OAuth, OAuth proxy, notifications, credits, user background.
|
||||
*
|
||||
* Captcha, moderation, and user-permissions routes have been moved to
|
||||
* AKSRT_Admin_Permissions_Controller for proper domain separation.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_User_Controller extends AKSRT_Admin_Controller_Base {
|
||||
use AKSRT_Admin_User_Controller_Report_Operations;
|
||||
use AKSRT_Admin_User_Controller_Invite_Operations;
|
||||
use AKSRT_Admin_User_Controller_Verify_Integrations;
|
||||
use AKSRT_Admin_User_Controller_Auth_Phone_Operations;
|
||||
use AKSRT_Admin_User_Controller_Restriction_Operations;
|
||||
use AKSRT_Admin_User_Controller_Follow_Operations;
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Auth settings (login/registration)
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/auth', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_auth_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// OAuth settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/oauth', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_oauth'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// OAuth proxy settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/oauth-proxy', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_oauth_proxy'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// User background upload (logged-in users)
|
||||
register_rest_route(self::API_NAMESPACE, '/user/background', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_upload_user_background'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Comment inline image upload (logged-in users)
|
||||
register_rest_route(self::API_NAMESPACE, '/comment/upload-image', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_upload_comment_image'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Notification settings
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/notifications', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_notification_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Keep message-center aliases on this controller so existing admin pages
|
||||
// remain compatible while notifications runtime lives in Message controller.
|
||||
register_rest_route(self::API_NAMESPACE, '/notifications/overview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_notification_overview'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/notifications/mark-read', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_mark_notification_read'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/notifications/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'rest_delete_notification_item'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/notifications/cleanup', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_run_notification_cleanup'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Mail templates (email content templates)
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/mail-templates', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'handle_mail_templates'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Credits settings (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/credits/settings', [
|
||||
'methods' => ['GET', 'POST'],
|
||||
'callback' => [$this, 'rest_handle_credit_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Credits — current user balance (frontend)
|
||||
register_rest_route(self::API_NAMESPACE, '/credits/me', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_credits_me'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Credits — user list (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/credits/users', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_list_credit_users'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Credits — adjust user balance (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/credits/users/(?P<id>\d+)/adjust', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_adjust_user_credits'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Credits — transaction log (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/credits/log', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_get_credits_log'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Report ticket list (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/report/list', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_report_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/users/reports', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_report_list'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/users/reports/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_report_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/users/reports/(?P<id>\d+)/process', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'process_report'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Report status update (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/report/update-status', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_report_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Report counts (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/report/counts', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_report_counts'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Report submit (logged-in users, front-end)
|
||||
register_rest_route(self::API_NAMESPACE, '/report/submit', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'submit_report'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Follow toggle (logged-in users)
|
||||
register_rest_route(self::API_NAMESPACE, '/follow/toggle', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'follow_toggle'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Follow status check (logged-in users)
|
||||
register_rest_route(self::API_NAMESPACE, '/follow/status', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'follow_status'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Follow counts (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/follow/counts', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'follow_counts'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Invite codes management (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/invite/codes', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_invite_codes'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/invite/codes', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'generate_invite_codes'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/invite/codes/(?P<id>\d+)/disable', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'disable_invite_code'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/invite/codes/bulk', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'bulk_update_invite_codes'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
register_rest_route(self::API_NAMESPACE, '/invite/stats', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_invite_stats'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// ── Modal Auth (弹窗登录/注册) ──
|
||||
|
||||
// Modal login
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/modal-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'modal_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Modal register
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/modal-register', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'modal_register'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Current user info (for AJAX refresh)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/me', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_current_user_info'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// Verify: list pending verification requests
|
||||
register_rest_route(self::API_NAMESPACE, '/verify/pending', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_verify_pending'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/verify/requests', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_verify_requests'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/verify/requests/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_verify_request_detail'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Verify: approve or reject a verification request
|
||||
register_rest_route(self::API_NAMESPACE, '/verify/review', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'review_verify_request'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/verify/requests/(?P<id>\d+)/review', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'review_verify_request_by_id'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// SMS provider status
|
||||
register_rest_route(self::API_NAMESPACE, '/sms/status', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_sms_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// SMS test send (admin only)
|
||||
register_rest_route(self::API_NAMESPACE, '/sms/test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'test_sms_send'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// WeChat notification status
|
||||
register_rest_route(self::API_NAMESPACE, '/wechat/status', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_wechat_status'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// WeChat settings update
|
||||
register_rest_route(self::API_NAMESPACE, '/wechat/settings', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'update_wechat_settings'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// SMS auth: send verification code (public, rate-limited)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/send-sms-code', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_send_sms_code'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: register by phone (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-register', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_register'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: login by phone + password (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: login by phone + SMS code (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-code-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_code_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: bind phone to current user (logged-in)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/bind-phone', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_bind_phone'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// ── User Restrictions / Ban ──
|
||||
|
||||
// List restrictions for a user (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/users/(?P<user_id>\d+)/restrictions', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_user_restrictions'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Add restriction (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/users/(?P<user_id>\d+)/restrict', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'add_user_restriction'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Remove restriction (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/users/restrictions/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [$this, 'remove_user_restriction'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Restriction stats (admin)
|
||||
register_rest_route(self::API_NAMESPACE, '/users/restrictions/stats', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_restriction_stats'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GET/POST /settings/mail-templates — 邮件内容模板
|
||||
*/
|
||||
public function handle_mail_templates(WP_REST_Request $request): WP_REST_Response {
|
||||
$option_key = 'aksrt_mail_templates';
|
||||
$fields = [
|
||||
'welcome_subject', 'welcome_body',
|
||||
'comment_notify_subject', 'comment_notify_body',
|
||||
'comment_reply_subject', 'comment_reply_body',
|
||||
'password_reset_subject', 'password_reset_body',
|
||||
'email_verify_subject', 'email_verify_body',
|
||||
];
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$stored = get_option($option_key, []);
|
||||
if (!is_array($stored)) $stored = [];
|
||||
$result = [];
|
||||
foreach ($fields as $f) {
|
||||
$result[$f] = sanitize_text_field((string) ($stored[$f] ?? ''));
|
||||
}
|
||||
return $this->success(['data' => $result]);
|
||||
}
|
||||
|
||||
// POST
|
||||
$params = $this->get_json_params($request);
|
||||
$clean = [];
|
||||
foreach ($fields as $f) {
|
||||
$clean[$f] = wp_kses_post((string) ($params[$f] ?? ''));
|
||||
}
|
||||
update_option($option_key, $clean);
|
||||
|
||||
return $this->success(['message' => __('邮件模板已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_credit_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$config = AKSRT_Settings_Credits_Runtime::get_credit_settings();
|
||||
return $this->success(['data' => $config]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$current = get_option('aksrt_credit_settings', []);
|
||||
if (!is_array($current)) {
|
||||
$current = [];
|
||||
}
|
||||
$merged = array_replace_recursive($current, $params);
|
||||
update_option('aksrt_credit_settings', $merged);
|
||||
|
||||
return $this->success(['message' => __('积分配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_auth_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$allowed = $provider->get_allowed_fields('users', 'auth');
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$data = AKSRT_Settings::get_all();
|
||||
$invite = class_exists('AKSRT_Settings_Credits_Runtime')
|
||||
? AKSRT_Settings_Credits_Runtime::get_invite_registration_config()
|
||||
: [];
|
||||
|
||||
return $this->success(['data' => [
|
||||
'registration_email_verification_enabled' => AKSRT_Settings::registration_requires_email_verification(),
|
||||
'registration_email_verification_method' => AKSRT_Settings::registration_verification_method(),
|
||||
'invite_registration_enabled' => $invite['enabled'] ?? true,
|
||||
'invite_code_length' => class_exists('AKSRT_Settings') ? AKSRT_Settings::invite_code_length() : 6,
|
||||
'invite_register_field' => $invite['register_field'] ?? 'optional',
|
||||
'invite_reject_invalid' => $invite['reject_invalid'] ?? false,
|
||||
] + array_intersect_key($data, $allowed)]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$sanitized = $this->sanitize_fields($params, $allowed);
|
||||
$settings = array_merge($settings, $sanitized);
|
||||
|
||||
if (array_key_exists('invite_registration_enabled', $params)) {
|
||||
$settings['invite_registration_enabled'] = !empty($params['invite_registration_enabled']);
|
||||
}
|
||||
if (array_key_exists('invite_code_length', $params)) {
|
||||
$settings['invite_code_length'] = max(4, min(12, (int) $params['invite_code_length']));
|
||||
}
|
||||
if (array_key_exists('invite_register_field', $params)) {
|
||||
$field = (string) $params['invite_register_field'];
|
||||
$settings['invit_code_mode'] = $field === 'required' ? 'must' : ($field === 'hidden' ? 'close' : 'open');
|
||||
}
|
||||
if (array_key_exists('invite_reject_invalid', $params)) {
|
||||
$reward = is_array($settings['invite_reward'] ?? null) ? $settings['invite_reward'] : [];
|
||||
$reward['reject_invalid'] = !empty($params['invite_reject_invalid']);
|
||||
$settings['invite_reward'] = $reward;
|
||||
}
|
||||
if (array_key_exists('registration_email_verification_enabled', $params)) {
|
||||
$settings['registration_email_verification_enabled'] = !empty($params['registration_email_verification_enabled']);
|
||||
}
|
||||
if (array_key_exists('registration_email_verification_method', $params)) {
|
||||
$method = (string) $params['registration_email_verification_method'];
|
||||
$settings['registration_email_verification_method'] = in_array($method, ['link', 'code'], true) ? $method : 'link';
|
||||
}
|
||||
|
||||
update_option('aksrt_settings', $settings);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success(['message' => __('注册登录配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_notification_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$allowed = $provider->get_allowed_fields('users', 'notifications');
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$data = AKSRT_Settings::get_all();
|
||||
return $this->success(['data' => array_intersect_key($data, $allowed)]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$settings = array_merge($settings, $this->sanitize_fields($params, $allowed));
|
||||
update_option('aksrt_settings', $settings);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success(['message' => __('消息通知配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_get_notification_overview(WP_REST_Request $request): WP_REST_Response {
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = min(100, max(1, (int) $request->get_param('per_page')));
|
||||
$type = sanitize_key((string) $request->get_param('type'));
|
||||
$is_read = $request->get_param('is_read');
|
||||
|
||||
$items = [];
|
||||
$unread = 0;
|
||||
if (class_exists('AKSRT_Message')) {
|
||||
$args = ['limit' => $per_page, 'offset' => ($page - 1) * $per_page];
|
||||
if ($type !== '') {
|
||||
$args['type'] = $type;
|
||||
}
|
||||
$items = AKSRT_Message::get_instance()->get_notifications(get_current_user_id(), $args);
|
||||
if ($is_read !== null && $is_read !== '') {
|
||||
$want_read = (string) $is_read === '1';
|
||||
$items = array_values(array_filter($items, static function ($item) use ($want_read) {
|
||||
return !empty($item['is_read']) === $want_read;
|
||||
}));
|
||||
}
|
||||
$unread = AKSRT_Message::get_instance()->get_unread_count(get_current_user_id());
|
||||
}
|
||||
|
||||
$total = count($items);
|
||||
$types = [];
|
||||
$today = 0;
|
||||
$system = 0;
|
||||
foreach ($items as $item) {
|
||||
$itemType = sanitize_key((string) ($item['type'] ?? 'system'));
|
||||
$types[$itemType] = ($types[$itemType] ?? 0) + 1;
|
||||
if ($itemType === 'system' || $itemType === 'notice') {
|
||||
$system++;
|
||||
}
|
||||
$created = (string) ($item['created_at'] ?? '');
|
||||
if ($created !== '' && str_starts_with($created, gmdate('Y-m-d'))) {
|
||||
$today++;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success(['data' => [
|
||||
'recent' => $items,
|
||||
'types' => array_map(static fn($key, $count) => ['type' => $key, 'count' => $count], array_keys($types), array_values($types)),
|
||||
'summary' => [
|
||||
'total' => $total,
|
||||
'unread' => $unread,
|
||||
'today' => $today,
|
||||
'system' => $system,
|
||||
],
|
||||
'pagination' => [
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'total_items' => $total,
|
||||
'total_pages' => 1,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
||||
public function rest_mark_notification_read(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Message')) {
|
||||
return $this->success(['message' => __('消息系统未启用', 'aksrt')]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$msg = AKSRT_Message::get_instance();
|
||||
if (!empty($params['mark_all'])) {
|
||||
$msg->mark_all_read(get_current_user_id());
|
||||
} else {
|
||||
$id = sanitize_text_field((string) ($params['id'] ?? ''));
|
||||
if ($id !== '') {
|
||||
$msg->mark_read(get_current_user_id(), $id);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('已更新消息状态', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_delete_notification_item(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->success(['message' => __('当前版本暂未提供后台删除单条消息能力', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_run_notification_cleanup(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->success(['message' => __('消息清理任务已受理', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_oauth(WP_REST_Request $request): WP_REST_Response {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$allowed = $provider->get_allowed_fields('users', 'oauth');
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$data = AKSRT_Settings::get_all();
|
||||
return $this->success(['data' => array_intersect_key($data, $allowed)]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
$settings = array_merge($settings, $this->sanitize_fields($params, $allowed));
|
||||
update_option('aksrt_settings', $settings);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success(['message' => __('第三方登录配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_handle_oauth_proxy(WP_REST_Request $request): WP_REST_Response {
|
||||
$keys = [
|
||||
'oauth_proxy_mode',
|
||||
'oauth_proxy_secret',
|
||||
'oauth_proxy_server_url',
|
||||
'oauth_proxy_server_providers',
|
||||
'oauth_proxy_client_providers',
|
||||
'oauth_proxy_gzh_type',
|
||||
];
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$all = AKSRT_Settings::get_all();
|
||||
$data = [];
|
||||
foreach ($keys as $key) {
|
||||
$data[$key] = $all[$key] ?? null;
|
||||
}
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$settings = AKSRT_Settings::get_all();
|
||||
foreach ($keys as $key) {
|
||||
if (!array_key_exists($key, $params)) {
|
||||
continue;
|
||||
}
|
||||
$settings[$key] = is_array($params[$key])
|
||||
? array_values(array_map('sanitize_key', (array) $params[$key]))
|
||||
: sanitize_text_field((string) $params[$key]);
|
||||
}
|
||||
update_option('aksrt_settings', $settings);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success(['message' => __('OAuth 代理配置已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_upload_user_background(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->error(__('当前版本暂未提供后台 REST 背景图上传,请使用标准媒体上传流程。', 'aksrt'), 501);
|
||||
}
|
||||
|
||||
public function rest_upload_comment_image(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->error(__('当前版本暂未提供后台 REST 评论图片上传,请使用标准媒体上传流程。', 'aksrt'), 501);
|
||||
}
|
||||
|
||||
public function rest_get_credits_me(WP_REST_Request $request): WP_REST_Response {
|
||||
$user_id = get_current_user_id();
|
||||
return $this->success(['data' => [
|
||||
'user_id' => $user_id,
|
||||
'balance' => class_exists('AKSRT_Credits') ? AKSRT_Credits::balance($user_id) : 0,
|
||||
]]);
|
||||
}
|
||||
|
||||
public function rest_list_credit_users(WP_REST_Request $request): WP_REST_Response {
|
||||
$args = [
|
||||
'search' => (string) $request->get_param('search'),
|
||||
'page' => (int) $request->get_param('page'),
|
||||
'per_page' => (int) $request->get_param('per_page'),
|
||||
];
|
||||
$data = class_exists('AKSRT_Credits') ? AKSRT_Credits::list_users($args) : ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 20];
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
|
||||
public function rest_adjust_user_credits(WP_REST_Request $request): WP_REST_Response {
|
||||
if (!class_exists('AKSRT_Credits')) {
|
||||
return $this->error(__('积分系统不可用', 'aksrt'), 500);
|
||||
}
|
||||
|
||||
$user_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$delta = (int) ($params['delta'] ?? 0);
|
||||
$reason = sanitize_text_field((string) ($params['reason'] ?? 'admin'));
|
||||
|
||||
if ($user_id <= 0 || $delta === 0) {
|
||||
return $this->error(__('用户或积分变动值无效', 'aksrt'), 422);
|
||||
}
|
||||
|
||||
if ($delta > 0) {
|
||||
$result = AKSRT_Credits::add($user_id, $delta, $reason, ['type' => 'user', 'id' => $user_id], 'Admin adjustment');
|
||||
} else {
|
||||
$result = AKSRT_Credits::deduct($user_id, abs($delta), $reason, ['type' => 'user', 'id' => $user_id], 'Admin adjustment');
|
||||
}
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message(), 422);
|
||||
}
|
||||
|
||||
return $this->success(['data' => [
|
||||
'user_id' => $user_id,
|
||||
'balance' => (int) $result,
|
||||
], 'message' => __('积分调整已保存', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_get_credits_log(WP_REST_Request $request): WP_REST_Response {
|
||||
$args = [
|
||||
'user_id' => (int) $request->get_param('user_id'),
|
||||
'reason' => (string) $request->get_param('reason'),
|
||||
'page' => (int) $request->get_param('page'),
|
||||
'per_page' => (int) $request->get_param('per_page'),
|
||||
'date_from' => (string) $request->get_param('date_from'),
|
||||
'date_to' => (string) $request->get_param('date_to'),
|
||||
];
|
||||
$data = class_exists('AKSRT_Credits') ? AKSRT_Credits::get_log($args) : ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 20];
|
||||
return $this->success(['data' => $data]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Forum_Controller_Part_1 {
|
||||
|
||||
public function update_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
|
||||
$result = $forum->update_plate($plate_id, $params);
|
||||
if (!$result) {
|
||||
return $this->error(__('更新版块失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('版块已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function delete_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$cascade = sanitize_key((string) ($params['cascade'] ?? 'trash'));
|
||||
$move_to = (int) ($params['move_to'] ?? 0);
|
||||
|
||||
$result = $forum->delete_plate($plate_id, $cascade, $move_to);
|
||||
if (!$result) {
|
||||
return $this->error(__('删除版块失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('版块已删除', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function reorder_plates(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$order = $params['order'] ?? [];
|
||||
if (!is_array($order) || empty($order)) {
|
||||
return $this->error(__('排序参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$forum->reorder_plates($order);
|
||||
|
||||
return $this->success(['message' => __('排序已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function toggle_follow_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$result = $forum->toggle_follow_plate($user_id, $plate_id);
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function get_plate_categories(): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => []]);
|
||||
}
|
||||
|
||||
return $this->success(['data' => $forum->get_plate_categories()]);
|
||||
}
|
||||
|
||||
public function get_sorted_plates(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => []]);
|
||||
}
|
||||
|
||||
$orderby = sanitize_key((string) ($request->get_param('orderby') ?: 'menu_order'));
|
||||
$order = sanitize_key((string) ($request->get_param('order') ?: 'ASC'));
|
||||
$limit = max(1, min(100, (int) ($request->get_param('limit') ?: 50)));
|
||||
|
||||
return $this->success(['data' => $forum->get_sorted_plates($orderby, $order, $limit)]);
|
||||
}
|
||||
|
||||
public function get_topics(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'total_pages' => 1]]);
|
||||
}
|
||||
|
||||
$args = [
|
||||
'page' => max(1, (int) ($request->get_param('page') ?: 1)),
|
||||
'per_page' => max(1, min(100, (int) ($request->get_param('per_page') ?: 20))),
|
||||
'status' => sanitize_key((string) ($request->get_param('status') ?: 'all')),
|
||||
'board_id' => (int) ($request->get_param('board_id') ?: 0),
|
||||
'visibility' => sanitize_key((string) ($request->get_param('visibility') ?: '')),
|
||||
'review_status' => sanitize_key((string) ($request->get_param('review_status') ?: '')),
|
||||
'keyword' => sanitize_text_field((string) ($request->get_param('keyword') ?: '')),
|
||||
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?: 'date')),
|
||||
'viewer_user_id' => get_current_user_id(),
|
||||
'include_hidden' => true,
|
||||
];
|
||||
|
||||
$view = $forum->get_topic_list($args);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $view['items'],
|
||||
'total' => $view['total'],
|
||||
'page' => $view['page'],
|
||||
'total_pages' => $view['max_pages'],
|
||||
'options' => [
|
||||
'boards' => $forum->get_boards(),
|
||||
'visibility' => $forum->get_visibility_options(),
|
||||
'review' => $forum->get_review_options(),
|
||||
'status' => [
|
||||
['label' => __('全部', 'aksrt'), 'value' => 'all'],
|
||||
['label' => __('已发布', 'aksrt'), 'value' => 'publish'],
|
||||
['label' => __('待审核', 'aksrt'), 'value' => 'pending'],
|
||||
['label' => __('草稿/拒绝', 'aksrt'), 'value' => 'draft'],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_topic_detail(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
if ($topic_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => $forum->get_topic_detail($topic_id, get_current_user_id()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function moderate_topic(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$action = sanitize_key((string) ($params['action'] ?? ''));
|
||||
|
||||
if ($topic_id <= 0 || $action === '') {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$result = $forum->moderate_topic($topic_id, $action, $params, get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message(), $result->get_error_code() === 'forbidden' ? 403 : 400);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? __('治理动作已执行', 'aksrt'),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function vote_topic(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$option_ids = $params['option_ids'] ?? [];
|
||||
if (!is_array($option_ids)) {
|
||||
$option_ids = [];
|
||||
}
|
||||
|
||||
$result = $forum->submit_vote($topic_id, get_current_user_id(), $option_ids);
|
||||
if (is_wp_error($result)) {
|
||||
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
|
||||
return $this->error($result->get_error_message(), $status);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? __('投票成功', 'aksrt'),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy admin moderation endpoint kept for compatibility.
|
||||
*/
|
||||
public function update_topic_legacy(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$topic_id = (int) ($params['topic_id'] ?? 0);
|
||||
if ($topic_id <= 0) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$actions = [];
|
||||
if (isset($params['sticky'])) {
|
||||
$actions[] = ['action' => 'sticky', 'value' => !empty($params['sticky'])];
|
||||
}
|
||||
if (isset($params['featured'])) {
|
||||
$actions[] = ['action' => 'featured', 'value' => !empty($params['featured'])];
|
||||
}
|
||||
if (isset($params['locked'])) {
|
||||
$actions[] = ['action' => 'locked', 'value' => !empty($params['locked'])];
|
||||
}
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$result = $forum->moderate_topic($topic_id, $action['action'], ['value' => $action['value']], get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('治理状态已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function get_moderators(): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => ['boards' => []]]);
|
||||
}
|
||||
|
||||
$board_id = isset($_GET['board_id']) ? (int) $_GET['board_id'] : 0;
|
||||
$result = [];
|
||||
foreach ($forum->get_boards() as $board) {
|
||||
$current_board_id = (int) ($board['id'] ?? 0);
|
||||
if ($board_id > 0 && $current_board_id !== $board_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'board_id' => $current_board_id,
|
||||
'board_name' => (string) ($board['name'] ?? ''),
|
||||
'topic_count' => (int) ($board['post_count'] ?? 0),
|
||||
'pending_application_count' => (int) ($board['pending_application_count'] ?? 0),
|
||||
'moderators' => $forum->get_board_moderator_profiles_with_roles($current_board_id),
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success(['data' => ['boards' => $result]]);
|
||||
}
|
||||
|
||||
public function update_moderators(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$board_id = (int) ($params['board_id'] ?? 0);
|
||||
$user_id = (int) ($params['user_id'] ?? 0);
|
||||
$action = sanitize_key((string) ($params['action'] ?? ''));
|
||||
$role = sanitize_key((string) ($params['role'] ?? 'moderator'));
|
||||
|
||||
if ($board_id <= 0 || $user_id <= 0 || !in_array($action, ['add', 'remove'], true)) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$valid_roles = ['super_moderator', 'moderator', 'junior_moderator'];
|
||||
if (!in_array($role, $valid_roles, true)) {
|
||||
$role = 'moderator';
|
||||
}
|
||||
|
||||
$board = get_term($board_id, 'aksrt_forum_board');
|
||||
if (!$board || is_wp_error($board)) {
|
||||
return $this->error(__('版块不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
return $this->error(__('用户不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
if ($action === 'add') {
|
||||
$forum->add_moderator($user_id, $board_id, $role);
|
||||
} else {
|
||||
$forum->remove_moderator($user_id, $board_id);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $action === 'add' ? __('版主已添加', 'aksrt') : __('版主已移除', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function change_moderator_role(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$board_id = (int) ($params['board_id'] ?? 0);
|
||||
$user_id = (int) ($params['user_id'] ?? 0);
|
||||
$role = sanitize_key((string) ($params['role'] ?? ''));
|
||||
|
||||
if ($board_id <= 0 || $user_id <= 0 || !$role) {
|
||||
return $this->error(__('参数无效', 'aksrt'));
|
||||
}
|
||||
|
||||
$valid_roles = ['super_moderator', 'moderator', 'junior_moderator'];
|
||||
if (!in_array($role, $valid_roles, true)) {
|
||||
return $this->error(__('无效的角色', 'aksrt'));
|
||||
}
|
||||
|
||||
$result = $forum->set_moderator_role($user_id, $board_id, $role);
|
||||
if (!$result) {
|
||||
return $this->error(__('设置角色失败,用户可能不是该版块版主', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('角色已更新', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function apply_moderator(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$board_id = (int) ($params['board_id'] ?? 0);
|
||||
$reason = sanitize_textarea_field((string) ($params['reason'] ?? ''));
|
||||
|
||||
$result = $forum->apply_moderator(get_current_user_id(), $board_id, $reason);
|
||||
if (is_wp_error($result)) {
|
||||
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
|
||||
return $this->error($result->get_error_message(), $status);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? __('申请已提交', 'aksrt'),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function get_moderator_applications(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0]]);
|
||||
}
|
||||
|
||||
$items = $forum->get_moderator_application_list([
|
||||
'status' => sanitize_key((string) ($request->get_param('status') ?: '')),
|
||||
'board_id' => (int) ($request->get_param('board_id') ?: 0),
|
||||
'keyword' => sanitize_text_field((string) ($request->get_param('keyword') ?: '')),
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => $items,
|
||||
'total' => count($items),
|
||||
'options' => [
|
||||
'boards' => $forum->get_boards(),
|
||||
'status' => [
|
||||
['label' => __('全部', 'aksrt'), 'value' => ''],
|
||||
['label' => __('待处理', 'aksrt'), 'value' => AKSRT_Forum::APPLICATION_PENDING],
|
||||
['label' => __('已通过', 'aksrt'), 'value' => AKSRT_Forum::APPLICATION_APPROVED],
|
||||
['label' => __('已拒绝', 'aksrt'), 'value' => AKSRT_Forum::APPLICATION_REJECTED],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Forum_Controller_Part_2 {
|
||||
|
||||
|
||||
public function review_moderator_application(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$application_id = (string) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$status = sanitize_key((string) ($params['status'] ?? ''));
|
||||
$note = sanitize_textarea_field((string) ($params['note'] ?? ''));
|
||||
|
||||
$result = $forum->review_moderator_application($application_id, $status, $note, get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message());
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? __('申请状态已更新', 'aksrt'),
|
||||
'data' => $result,
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggle_favorite(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
$result = $forum->toggle_favorite(get_current_user_id(), $topic_id);
|
||||
if (is_wp_error($result)) {
|
||||
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
|
||||
return $this->error($result->get_error_message(), $status);
|
||||
}
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function score_topic(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$amount = (int) ($params['amount'] ?? 0);
|
||||
|
||||
$result = $forum->score_topic(get_current_user_id(), $topic_id, $amount);
|
||||
if (is_wp_error($result)) {
|
||||
$status = $result->get_error_code() === 'not_logged_in' ? 401 : 400;
|
||||
return $this->error($result->get_error_message(), $status);
|
||||
}
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function adopt_answer(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$topic_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$comment_id = (int) ($params['comment_id'] ?? 0);
|
||||
|
||||
$result = $forum->adopt_answer($topic_id, $comment_id, get_current_user_id());
|
||||
if (is_wp_error($result)) {
|
||||
$status = $result->get_error_code() === 'forbidden' ? 403 : 400;
|
||||
return $this->error($result->get_error_message(), $status);
|
||||
}
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function check_logged_in_permission(): bool {
|
||||
return is_user_logged_in();
|
||||
}
|
||||
|
||||
private function get_forum_service(): ?AKSRT_Forum {
|
||||
return class_exists('AKSRT_Forum') ? AKSRT_Forum::get_instance() : null;
|
||||
}
|
||||
|
||||
public function frontend_create_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
if (!$forum->is_forum_admin($user_id)) {
|
||||
return $this->error(__('仅论坛管理员可创建版块', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$data = [
|
||||
'name' => sanitize_text_field((string) ($params['name'] ?? '')),
|
||||
'description' => sanitize_textarea_field((string) ($params['description'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($params['icon'] ?? '')),
|
||||
'cover_url' => esc_url_raw((string) ($params['cover_url'] ?? '')),
|
||||
'parent_id' => (int) ($params['parent_id'] ?? 0),
|
||||
'plate_cat' => (int) ($params['plate_cat'] ?? 0),
|
||||
];
|
||||
|
||||
if ($data['name'] === '') {
|
||||
return $this->error(__('版块名称不能为空', 'aksrt'));
|
||||
}
|
||||
|
||||
$result = $forum->create_plate($data);
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message());
|
||||
}
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function frontend_update_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
|
||||
$post = get_post($plate_id);
|
||||
if (!$post || $post->post_type !== 'aksrt_forum_plate') {
|
||||
return $this->error(__('版块不存在', 'aksrt'));
|
||||
}
|
||||
|
||||
$is_author = (int) $post->post_author === $user_id;
|
||||
if (!$is_author && !$forum->is_forum_admin($user_id)) {
|
||||
return $this->error(__('无权编辑此版块', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$data = [
|
||||
'name' => sanitize_text_field((string) ($params['name'] ?? '')),
|
||||
'description' => sanitize_textarea_field((string) ($params['description'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($params['icon'] ?? '')),
|
||||
'cover_url' => esc_url_raw((string) ($params['cover_url'] ?? '')),
|
||||
];
|
||||
|
||||
$result = $forum->update_plate($plate_id, $data);
|
||||
if (is_wp_error($result)) {
|
||||
return $this->error($result->get_error_message());
|
||||
}
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
public function frontend_delete_plate(WP_REST_Request $request): WP_REST_Response {
|
||||
$forum = $this->get_forum_service();
|
||||
if (!$forum) {
|
||||
return $this->error(__('论坛系统未启用', 'aksrt'));
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
if (!$forum->is_forum_admin($user_id)) {
|
||||
return $this->error(__('仅论坛管理员可删除版块', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
$plate_id = (int) $request->get_param('id');
|
||||
$params = $this->get_json_params($request);
|
||||
$cascade = sanitize_key((string) ($params['cascade'] ?? 'trash'));
|
||||
$move_to = (int) ($params['move_to'] ?? 0);
|
||||
|
||||
$result = $forum->delete_plate($plate_id, $cascade, $move_to);
|
||||
if (!$result) {
|
||||
return $this->error(__('删除版块失败', 'aksrt'));
|
||||
}
|
||||
|
||||
return $this->success(['message' => __('版块已删除', 'aksrt')]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Global_Controller_Part_1 {
|
||||
|
||||
public function rest_reorder_footer_tabbar(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$ordered_ids = $params['ids'] ?? [];
|
||||
|
||||
if (!is_array($ordered_ids)) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
|
||||
}
|
||||
|
||||
if (count($ordered_ids) > 5) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => __('Footer tabbar supports at most 5 items.', 'aksrt')], 400);
|
||||
}
|
||||
|
||||
$tabbar = get_option('aksrt_footer_tabbar', []);
|
||||
$items = $tabbar['items'] ?? [];
|
||||
|
||||
$order_map = array_flip($ordered_ids);
|
||||
usort($items, function ($a, $b) use ($order_map) {
|
||||
$idx_a = $order_map[($a['id'] ?? '')] ?? 999;
|
||||
$idx_b = $order_map[($b['id'] ?? '')] ?? 999;
|
||||
return $idx_a <=> $idx_b;
|
||||
});
|
||||
|
||||
foreach ($items as $index => &$item) {
|
||||
$item['sort_order'] = $index;
|
||||
}
|
||||
|
||||
$tabbar['items'] = $items;
|
||||
update_option('aksrt_footer_tabbar', $tabbar);
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
private function sanitize_tabbar_item(array $item): array {
|
||||
$valid_types = ['home', 'category', 'link', 'user', 'auth', 'search', 'add'];
|
||||
$type = sanitize_text_field($item['type'] ?? 'link');
|
||||
if (!in_array($type, $valid_types, true)) {
|
||||
$type = 'link';
|
||||
}
|
||||
$badge_mode = sanitize_text_field($item['badge_mode'] ?? 'static');
|
||||
if (!in_array($badge_mode, ['static', 'dynamic'], true)) {
|
||||
$badge_mode = 'static';
|
||||
}
|
||||
|
||||
$add_items = [];
|
||||
if (!empty($item['add_items']) && is_array($item['add_items'])) {
|
||||
foreach ($item['add_items'] as $sub_item) {
|
||||
$label = sanitize_text_field($sub_item['label'] ?? '');
|
||||
$icon = sanitize_text_field($sub_item['icon'] ?? 'fas fa-pen');
|
||||
$url = esc_url_raw($sub_item['url'] ?? '');
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
$add_items[] = [
|
||||
'id' => sanitize_text_field($sub_item['id'] ?? uniqid('add_')),
|
||||
'label' => $label,
|
||||
'icon' => $icon,
|
||||
'url' => $url,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => sanitize_text_field($item['id'] ?? uniqid('tab_')),
|
||||
'type' => $type,
|
||||
'icon' => sanitize_text_field($item['icon'] ?? ''),
|
||||
'text' => sanitize_text_field($item['text'] ?? ''),
|
||||
'link' => esc_url_raw($item['link'] ?? ''),
|
||||
'badge' => sanitize_text_field($item['badge'] ?? ''),
|
||||
'badge_mode' => $badge_mode,
|
||||
'category_id' => absint($item['category_id'] ?? 0),
|
||||
'search_default_type' => (function() use ($item) {
|
||||
$v = $item['search_default_type'] ?? 'post';
|
||||
return in_array($v, ['post', 'category', 'tag'], true) ? $v : 'post';
|
||||
})(),
|
||||
'new_window' => !empty($item['new_window']),
|
||||
'add_items' => $add_items,
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'sort_order' => (int) ($item['sort_order'] ?? 0),
|
||||
'ontop' => !empty($item['ontop']),
|
||||
];
|
||||
}
|
||||
|
||||
public function rest_handle_floating_buttons(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$settings = class_exists('AKSRT_Settings')
|
||||
? AKSRT_Settings::get_floating_buttons_settings()
|
||||
: [
|
||||
'enabled' => true,
|
||||
'style' => 'round',
|
||||
'position' => 'bottom',
|
||||
'scroll_hide' => false,
|
||||
'scroll_down_hide' => true,
|
||||
'filter_blur' => [],
|
||||
'items' => [],
|
||||
];
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$items = isset($params['items']) ? array_map([$this, 'sanitize_floating_button_item'], (array) $params['items']) : [];
|
||||
|
||||
$filter_blur = [];
|
||||
if (!empty($params['filter_blur']) && is_array($params['filter_blur'])) {
|
||||
foreach ($params['filter_blur'] as $device) {
|
||||
$device = sanitize_text_field((string) $device);
|
||||
if (in_array($device, ['pc', 'mobile'], true)) {
|
||||
$filter_blur[] = $device;
|
||||
}
|
||||
}
|
||||
$filter_blur = array_values(array_unique($filter_blur));
|
||||
}
|
||||
|
||||
$settings = [
|
||||
'enabled' => !empty($params['enabled']),
|
||||
'style' => in_array($params['style'] ?? '', ['round', 'square'], true) ? $params['style'] : 'round',
|
||||
'position' => in_array($params['position'] ?? '', ['bottom', 'center'], true) ? $params['position'] : 'bottom',
|
||||
'scroll_hide' => !empty($params['scroll_hide']),
|
||||
'scroll_down_hide' => !empty($params['scroll_down_hide']),
|
||||
'max' => max(1, min(10, (int) ($params['max'] ?? 5))),
|
||||
'size' => max(32, min(64, (int) ($params['size'] ?? 44))),
|
||||
'spacing' => max(4, min(24, (int) ($params['spacing'] ?? 10))),
|
||||
'shadow' => !empty($params['shadow']),
|
||||
'animation' => !empty($params['animation']),
|
||||
'opacity' => max(30, min(100, (int) ($params['opacity'] ?? 100))),
|
||||
'active_color' => sanitize_hex_color($params['active_color'] ?? '') ?: '',
|
||||
'bg_color' => sanitize_hex_color($params['bg_color'] ?? '') ?: '',
|
||||
'pc_show' => !empty($params['pc_show']),
|
||||
'm_show' => !empty($params['m_show']),
|
||||
'filter_blur' => $filter_blur,
|
||||
'items' => $items,
|
||||
];
|
||||
|
||||
update_option('aksrt_floating_buttons', $settings);
|
||||
|
||||
if (class_exists('AKSRT_Settings')) {
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Floating button settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_reorder_floating_buttons(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$ordered_ids = $params['ids'] ?? [];
|
||||
|
||||
if (!is_array($ordered_ids)) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => __('参数错误', 'aksrt')], 400);
|
||||
}
|
||||
|
||||
$settings = get_option('aksrt_floating_buttons', []);
|
||||
$items = $settings['items'] ?? [];
|
||||
|
||||
$order_map = array_flip($ordered_ids);
|
||||
usort($items, function ($a, $b) use ($order_map) {
|
||||
$idx_a = $order_map[($a['id'] ?? '')] ?? 999;
|
||||
$idx_b = $order_map[($b['id'] ?? '')] ?? 999;
|
||||
return $idx_a <=> $idx_b;
|
||||
});
|
||||
|
||||
foreach ($items as $index => &$item) {
|
||||
$item['sort_order'] = $index;
|
||||
}
|
||||
unset($item);
|
||||
|
||||
$settings['items'] = $items;
|
||||
update_option('aksrt_floating_buttons', $settings);
|
||||
|
||||
if (class_exists('AKSRT_Settings')) {
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
}
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Order saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
private function sanitize_floating_button_item(array $item): array {
|
||||
$valid_types = ['back_top', 'theme_mode', 'qrcode', 'service_qq', 'service_wechat', 'pay_vip', 'custom', 'add', 'shop_cart'];
|
||||
$type = sanitize_text_field($item['type'] ?? 'custom');
|
||||
if (!in_array($type, $valid_types, true)) {
|
||||
$type = 'custom';
|
||||
}
|
||||
|
||||
$hover_width = (int) ($item['hover_width'] ?? 200);
|
||||
if ($hover_width < 120) {
|
||||
$hover_width = 120;
|
||||
}
|
||||
if ($hover_width > 480) {
|
||||
$hover_width = 480;
|
||||
}
|
||||
|
||||
$qq = preg_replace('/\D+/', '', (string) ($item['qq'] ?? ''));
|
||||
$action_mode = sanitize_text_field($item['action_mode'] ?? 'link');
|
||||
if (!in_array($action_mode, ['link', 'qrcode'], true)) {
|
||||
$action_mode = 'link';
|
||||
}
|
||||
$qr_size = (int) ($item['qr_size'] ?? 120);
|
||||
if ($qr_size < 80) {
|
||||
$qr_size = 80;
|
||||
}
|
||||
if ($qr_size > 240) {
|
||||
$qr_size = 240;
|
||||
}
|
||||
|
||||
$add_items = [];
|
||||
if (!empty($item['add_items']) && is_array($item['add_items'])) {
|
||||
foreach ($item['add_items'] as $add_item) {
|
||||
if (!is_array($add_item)) {
|
||||
continue;
|
||||
}
|
||||
$label = sanitize_text_field($add_item['label'] ?? '');
|
||||
$url = esc_url_raw($add_item['url'] ?? '');
|
||||
$icon_item = sanitize_text_field($add_item['icon'] ?? '');
|
||||
if ($label === '' && $url === '') {
|
||||
continue;
|
||||
}
|
||||
$add_items[] = [
|
||||
'id' => sanitize_text_field($add_item['id'] ?? uniqid('add_')),
|
||||
'label' => $label,
|
||||
'url' => $url,
|
||||
'icon' => $icon_item,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$icon = sanitize_text_field($item['icon'] ?? '');
|
||||
$icon_dark = sanitize_text_field($item['icon_dark'] ?? '');
|
||||
$icon_light = sanitize_text_field($item['icon_light'] ?? '');
|
||||
if ($type === 'theme_mode') {
|
||||
$icon = 'fas fa-sun';
|
||||
$icon_dark = '';
|
||||
$icon_light = '';
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => sanitize_text_field($item['id'] ?? uniqid('float_')),
|
||||
'type' => $type,
|
||||
'icon' => $icon,
|
||||
'icon_dark' => $icon_dark,
|
||||
'icon_light' => $icon_light,
|
||||
'text' => sanitize_text_field($item['text'] ?? ''),
|
||||
'desc' => sanitize_text_field($item['desc'] ?? ''),
|
||||
'action_mode' => $action_mode,
|
||||
'qq' => $qq,
|
||||
'qrcode_img' => esc_url_raw($item['qrcode_img'] ?? ''),
|
||||
'wechat_img' => esc_url_raw($item['wechat_img'] ?? ''),
|
||||
'link' => esc_url_raw($item['link'] ?? ''),
|
||||
'add_items' => $add_items,
|
||||
'hover' => wp_kses_post($item['hover'] ?? ''),
|
||||
'qr_size' => $qr_size,
|
||||
'hover_width' => $hover_width,
|
||||
'pc_s' => !empty($item['pc_s']),
|
||||
'm_s' => !empty($item['m_s']),
|
||||
'color' => sanitize_text_field($item['color'] ?? ''),
|
||||
'bg' => sanitize_text_field($item['bg'] ?? ''),
|
||||
'enabled' => !empty($item['enabled']),
|
||||
'sort_order' => (int) ($item['sort_order'] ?? 0),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Global_Controller_Part_2 {
|
||||
|
||||
|
||||
public function rest_handle_homepage_settings(WP_REST_Request $request): WP_REST_Response {
|
||||
$option_key = 'aksrt_settings';
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$data = AKSRT_Settings::get_homepage_fixed_settings();
|
||||
$data['homepage_modules'] = AKSRT_Settings::get_homepage_modules();
|
||||
return rest_ensure_response([
|
||||
'code' => 'success',
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
if (empty($params)) {
|
||||
$params = $request->get_params();
|
||||
}
|
||||
|
||||
$allowed_sorts = AKSRT_Settings::homepage_allowed_sorts();
|
||||
|
||||
$settings = [
|
||||
'homepage_card_style' => in_array($params['homepage_card_style'] ?? '', ['default', 'style2', 'style3'], true)
|
||||
? $params['homepage_card_style']
|
||||
: 'default',
|
||||
'homepage_multi_thumb_enabled' => !isset($params['homepage_multi_thumb_enabled']) || !empty($params['homepage_multi_thumb_enabled']),
|
||||
'homepage_multi_thumb_min_images' => max(2, min(4, (int) ($params['homepage_multi_thumb_min_images'] ?? 3))),
|
||||
'homepage_sidebar_mini_enabled' => !isset($params['homepage_sidebar_mini_enabled']) || !empty($params['homepage_sidebar_mini_enabled']),
|
||||
'homepage_default_sort' => in_array($params['homepage_default_sort'] ?? '', $allowed_sorts, true)
|
||||
? $params['homepage_default_sort']
|
||||
: 'modified',
|
||||
'homepage_default_view' => in_array($params['homepage_default_view'] ?? '', ['list', 'card'], true)
|
||||
? $params['homepage_default_view']
|
||||
: 'list',
|
||||
'homepage_sort_ajax_enabled' => !isset($params['homepage_sort_ajax_enabled']) || !empty($params['homepage_sort_ajax_enabled']),
|
||||
'homepage_sticky_tabs_enabled' => !isset($params['homepage_sticky_tabs_enabled']) || !empty($params['homepage_sticky_tabs_enabled']),
|
||||
'homepage_content_model' => in_array($params['homepage_content_model'] ?? '', ['post', 'mixed'], true)
|
||||
? $params['homepage_content_model']
|
||||
: 'mixed',
|
||||
'home_posts_list_enabled' => !isset($params['home_posts_list_enabled']) || !empty($params['home_posts_list_enabled']),
|
||||
'home_list_title' => sanitize_text_field((string) ($params['home_list_title'] ?? __('Latest Posts', 'aksrt'))),
|
||||
'home_list_sort_enabled' => !isset($params['home_list_sort_enabled']) || !empty($params['home_list_sort_enabled']),
|
||||
'home_list_sort_dropdown' => !empty($params['home_list_sort_dropdown']),
|
||||
'homepage_tab_posts_per_page' => max(1, min(20, (int) ($params['homepage_tab_posts_per_page'] ?? 6))),
|
||||
'homepage_tab_limit' => max(1, min(12, (int) ($params['homepage_tab_limit'] ?? 4))),
|
||||
];
|
||||
|
||||
if (isset($params['home_list_sort_options']) && is_array($params['home_list_sort_options'])) {
|
||||
$seen = [];
|
||||
$options = [];
|
||||
foreach ($params['home_list_sort_options'] as $key) {
|
||||
$key = sanitize_key((string) $key);
|
||||
if (in_array($key, $allowed_sorts, true) && !isset($seen[$key])) {
|
||||
$options[] = $key;
|
||||
$seen[$key] = true;
|
||||
}
|
||||
}
|
||||
$settings['home_list_sort_options'] = !empty($options) ? $options : ['modified', 'date', 'views', 'likes', 'comments'];
|
||||
}
|
||||
|
||||
if (isset($params['home_exclude_post_ids'])) {
|
||||
$settings['home_exclude_post_ids'] = array_values(array_filter(
|
||||
array_map('absint', (array) $params['home_exclude_post_ids']),
|
||||
static fn($value) => $value > 0
|
||||
));
|
||||
}
|
||||
|
||||
if (isset($params['home_exclude_category_ids'])) {
|
||||
$settings['home_exclude_category_ids'] = array_values(array_filter(
|
||||
array_map('absint', (array) $params['home_exclude_category_ids']),
|
||||
static fn($value) => $value > 0
|
||||
));
|
||||
}
|
||||
|
||||
if (isset($params['homepage_tabs']) && is_array($params['homepage_tabs'])) {
|
||||
$tabs = [];
|
||||
foreach ($params['homepage_tabs'] as $tab) {
|
||||
if (!is_array($tab)) {
|
||||
continue;
|
||||
}
|
||||
$term_id = absint($tab['term_id'] ?? 0);
|
||||
if ($term_id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$term = get_term($term_id, 'category');
|
||||
if (!$term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
$orderby = sanitize_key($tab['orderby'] ?? 'date');
|
||||
if (!in_array($orderby, $allowed_sorts, true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$tabs[] = [
|
||||
'term_id' => $term_id,
|
||||
'title' => sanitize_text_field($tab['title'] ?? ''),
|
||||
'orderby' => $orderby,
|
||||
'enabled' => !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
$settings['homepage_tabs'] = $tabs;
|
||||
}
|
||||
|
||||
if (isset($params['homepage_modules']) && is_array($params['homepage_modules'])) {
|
||||
$modules = [];
|
||||
$allowed_types = [
|
||||
'hero', 'banner', 'category', 'tabs', 'ranking', 'recent',
|
||||
'tag_cloud', 'friend_links', 'html', 'hotspot',
|
||||
'category_recommend_cards', 'text_title', 'button_group',
|
||||
'spacing', 'faq', 'data_grid', 'highlight_cards',
|
||||
'partner_marquee', 'product_showcase',
|
||||
];
|
||||
foreach ($params['homepage_modules'] as $module) {
|
||||
if (!is_array($module) || !in_array($module['type'] ?? '', $allowed_types, true)) {
|
||||
continue;
|
||||
}
|
||||
$modules[] = [
|
||||
'id' => sanitize_text_field($module['id'] ?? uniqid('mod_')),
|
||||
'type' => sanitize_text_field($module['type']),
|
||||
'config' => is_array($module['config'] ?? null) ? $module['config'] : [],
|
||||
'enabled' => !empty($module['enabled']),
|
||||
'title' => sanitize_text_field($module['title'] ?? ''),
|
||||
];
|
||||
}
|
||||
$settings['homepage_modules'] = $modules;
|
||||
}
|
||||
|
||||
$current = get_option($option_key, []);
|
||||
if (!is_array($current)) {
|
||||
$current = [];
|
||||
}
|
||||
|
||||
foreach ($settings as $key => $value) {
|
||||
$current[$key] = $value;
|
||||
}
|
||||
|
||||
update_option($option_key, $current);
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
update_option('aksrt_homepage_schema_version', gmdate('Y-m-d'));
|
||||
delete_transient('aksrt_homepage_cached_images');
|
||||
delete_transient('aksrt_homepage_modules_cache');
|
||||
do_action('aksrt_homepage_settings_changed');
|
||||
|
||||
return rest_ensure_response([
|
||||
'code' => 'success',
|
||||
'message' => __('Homepage settings saved.', 'aksrt'),
|
||||
'data' => $settings,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_handle_category_recommend(WP_REST_Request $request): WP_REST_Response {
|
||||
if ($request->get_method() === 'GET') {
|
||||
$settings = AKSRT_Settings::get_category_recommend_settings();
|
||||
return rest_ensure_response(['code' => 'success', 'data' => $settings]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$category_ids = array_map('absint', (array) ($params['category_recommend_category_ids'] ?? []));
|
||||
$category_ids = array_filter($category_ids, fn($id) => $id > 0);
|
||||
$category_ids = array_values(array_unique($category_ids));
|
||||
|
||||
$settings = [
|
||||
'enable_category_recommend' => !empty($params['enable_category_recommend']),
|
||||
'category_recommend_title' => sanitize_text_field($params['category_recommend_title'] ?? ''),
|
||||
'category_recommend_category_ids' => $category_ids,
|
||||
'category_recommend_posts_per_category' => max(1, min(10, (int) ($params['category_recommend_posts_per_category'] ?? 5))),
|
||||
'category_recommend_columns' => max(1, min(4, (int) ($params['category_recommend_columns'] ?? 2))),
|
||||
'category_recommend_show_description' => !empty($params['category_recommend_show_description']),
|
||||
'category_recommend_show_dates' => !empty($params['category_recommend_show_dates']),
|
||||
'category_recommend_show_more_link' => !empty($params['category_recommend_show_more_link']),
|
||||
];
|
||||
update_option('aksrt_category_recommend', $settings);
|
||||
AKSRT_Settings::flush_category_recommend_cache();
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('Category recommendation settings saved.', 'aksrt')]);
|
||||
}
|
||||
|
||||
public function rest_get_categories(WP_REST_Request $request): WP_REST_Response {
|
||||
$categories = get_categories([
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'hide_empty' => false,
|
||||
]);
|
||||
|
||||
$data = array_map(function ($cat) {
|
||||
return [
|
||||
'term_id' => (int) $cat->term_id,
|
||||
'name' => $cat->name,
|
||||
'slug' => $cat->slug,
|
||||
'count' => (int) $cat->count,
|
||||
'description' => $cat->description,
|
||||
'cover_id' => (int) get_term_meta($cat->term_id, 'aksrt_category_cover_id', true),
|
||||
'cover_url' => AKSRT_Settings::get_category_cover_url((int) $cat->term_id),
|
||||
'recommend_desc' => (string) get_term_meta($cat->term_id, 'aksrt_category_recommend_desc', true),
|
||||
];
|
||||
}, $categories);
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => array_values($data)]);
|
||||
}
|
||||
|
||||
public function rest_search_users(WP_REST_Request $request): WP_REST_Response {
|
||||
$search = (string) $request->get_param('search');
|
||||
$number = max(1, min(50, (int) $request->get_param('number')));
|
||||
|
||||
$args = [
|
||||
'number' => $number,
|
||||
'orderby' => 'display_name',
|
||||
'order' => 'ASC',
|
||||
'fields' => ['ID', 'display_name', 'user_login'],
|
||||
];
|
||||
if ($search !== '') {
|
||||
$args['search'] = '*' . $search . '*';
|
||||
$args['search_columns'] = ['display_name', 'user_login', 'user_email'];
|
||||
}
|
||||
|
||||
$users = get_users($args);
|
||||
$data = array_map(function ($user) {
|
||||
return [
|
||||
'id' => (int) $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'user_login' => $user->user_login,
|
||||
];
|
||||
}, $users);
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'data' => array_values($data)]);
|
||||
}
|
||||
|
||||
public function rest_update_category_meta(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$term = get_term($id, 'category');
|
||||
if (!$term || is_wp_error($term)) {
|
||||
return new WP_REST_Response(['code' => 'error', 'message' => __('Category does not exist.', 'aksrt')], 404);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$cover_id = absint($params['cover_id'] ?? 0);
|
||||
$recommend_desc = sanitize_textarea_field($params['recommend_desc'] ?? '');
|
||||
|
||||
if ($cover_id > 0) {
|
||||
update_term_meta($id, 'aksrt_category_cover_id', $cover_id);
|
||||
} else {
|
||||
delete_term_meta($id, 'aksrt_category_cover_id');
|
||||
}
|
||||
|
||||
if ($recommend_desc !== '') {
|
||||
update_term_meta($id, 'aksrt_category_recommend_desc', $recommend_desc);
|
||||
} else {
|
||||
delete_term_meta($id, 'aksrt_category_recommend_desc');
|
||||
}
|
||||
|
||||
AKSRT_Settings::flush_category_recommend_cache();
|
||||
|
||||
return rest_ensure_response(['code' => 'success', 'message' => __('分类元数据已保存', 'aksrt')]);
|
||||
}
|
||||
}
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Import_Export_Controller_Part_1 {
|
||||
|
||||
public function rest_migration_status(WP_REST_Request $request): WP_REST_Response {
|
||||
$schema_version = get_option('aksrt_db_schema_version', '0.0.0');
|
||||
$migration = class_exists('AKSRT_Settings_Migration') ? AKSRT_Settings_Migration::get_instance() : null;
|
||||
$migration_log = $migration ? $migration->get_log() : get_option('aksrt_settings_migration_log', []);
|
||||
|
||||
return $this->success([
|
||||
'schema_version' => $schema_version,
|
||||
'migration_version' => $migration ? $migration->get_version() : get_option('aksrt_settings_migration_version', '0.0.0'),
|
||||
'migration_log' => is_array($migration_log) ? $migration_log : [],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the schema definition for a domain (for frontend introspection).
|
||||
*/
|
||||
public function rest_get_schema(WP_REST_Request $request): WP_REST_Response {
|
||||
$domain = $request->get_param('domain');
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
|
||||
$schema = $schema_provider->get_schema($domain);
|
||||
if (!$schema) {
|
||||
return $this->error('未知的设置域: ' . esc_html($domain), 404);
|
||||
}
|
||||
|
||||
return $this->success(['schema' => $schema]);
|
||||
}
|
||||
|
||||
public function rest_get_schema_section(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->get_section_settings($request, (string) $request->get_param('domain'));
|
||||
}
|
||||
|
||||
public function rest_save_schema_section(WP_REST_Request $request): WP_REST_Response {
|
||||
return $this->save_section_settings($request, (string) $request->get_param('domain'));
|
||||
}
|
||||
|
||||
public function list_setting_backups(WP_REST_Request $request): WP_REST_Response {
|
||||
$backups = get_option('aksrt_settings_backups', []);
|
||||
if (!is_array($backups)) {
|
||||
$backups = [];
|
||||
}
|
||||
|
||||
usort($backups, static function ($a, $b) {
|
||||
return strcmp((string) ($b['created_at'] ?? ''), (string) ($a['created_at'] ?? ''));
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'items' => array_values($backups),
|
||||
'total' => count($backups),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function create_setting_backup(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$type = sanitize_key((string) ($params['type'] ?? 'manual'));
|
||||
$label = sanitize_text_field((string) ($params['label'] ?? ''));
|
||||
|
||||
$allowed_types = ['manual', 'scheduled', 'reset_all', 'reset_domain', 'update_theme'];
|
||||
if (!in_array($type, $allowed_types, true)) {
|
||||
$type = 'manual';
|
||||
}
|
||||
|
||||
$backup = $this->build_settings_backup($type, $label);
|
||||
$this->persist_setting_backup($backup);
|
||||
|
||||
return $this->success([
|
||||
'message' => __('设置快照已创建', 'aksrt'),
|
||||
'data' => $backup,
|
||||
]);
|
||||
}
|
||||
|
||||
public function restore_setting_backup(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$backups = get_option('aksrt_settings_backups', []);
|
||||
if (!is_array($backups)) {
|
||||
$backups = [];
|
||||
}
|
||||
|
||||
$target = null;
|
||||
foreach ($backups as $backup) {
|
||||
if ((int) ($backup['id'] ?? 0) === $id) {
|
||||
$target = $backup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$target || !is_array($target['payload'] ?? null)) {
|
||||
return $this->error(__('备份快照不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
$payload = $target['payload'];
|
||||
$domains = $payload['domains'] ?? [];
|
||||
$standalone = $payload['standalone'] ?? [];
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
|
||||
foreach ($domains as $domain => $domainPayload) {
|
||||
$data = $domainPayload['data'] ?? $domainPayload;
|
||||
if (is_array($data)) {
|
||||
$provider->write_settings((string) $domain, $data);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($standalone as $key => $value) {
|
||||
if (is_string($key) && preg_match('/^aksrt_/', $key)) {
|
||||
update_option($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
AKSRT_Settings::flush_settings_cache();
|
||||
|
||||
return $this->success([
|
||||
'message' => __('设置已从快照恢复', 'aksrt'),
|
||||
'data' => [
|
||||
'id' => $id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function delete_setting_backup(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$backups = get_option('aksrt_settings_backups', []);
|
||||
if (!is_array($backups)) {
|
||||
$backups = [];
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
$found = false;
|
||||
foreach ($backups as $backup) {
|
||||
if ((int) ($backup['id'] ?? 0) === $id) {
|
||||
$found = true;
|
||||
continue;
|
||||
}
|
||||
$filtered[] = $backup;
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
return $this->error(__('备份快照不存在', 'aksrt'), 404);
|
||||
}
|
||||
|
||||
update_option('aksrt_settings_backups', array_values($filtered));
|
||||
|
||||
return $this->success([
|
||||
'message' => __('备份快照已删除', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Widget Template Handlers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* GET /widgets/export — Export current widget configuration.
|
||||
*/
|
||||
public function export_widgets(WP_REST_Request $request): WP_REST_Response {
|
||||
$sidebars_widgets = get_option('sidebars_widgets', []);
|
||||
$widget_options = $this->collect_widget_options();
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'version' => AKSRT_VERSION ?? '3.4.0',
|
||||
'exported' => gmdate('Y-m-d H:i:s'),
|
||||
'sidebars_widgets' => $sidebars_widgets,
|
||||
'widget_options' => $widget_options,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /widgets/import — Import widget configuration.
|
||||
*/
|
||||
public function import_widgets(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $request->get_json_params() ?? [];
|
||||
$sidebars = $params['sidebars_widgets'] ?? null;
|
||||
$options = $params['widget_options'] ?? null;
|
||||
|
||||
if (!$sidebars || !is_array($sidebars)) {
|
||||
return $this->error('缺少 sidebars_widgets 数据');
|
||||
}
|
||||
|
||||
// Validate sidebar IDs exist
|
||||
$valid_sidebars = array_keys(get_registered_sidebars());
|
||||
$valid_sidebars[] = 'wp_inactive_widgets';
|
||||
|
||||
$imported = 0;
|
||||
foreach ($sidebars as $sidebar_id => $widget_ids) {
|
||||
if (!in_array($sidebar_id, $valid_sidebars, true) && $sidebar_id !== 'array_version') {
|
||||
continue;
|
||||
}
|
||||
$imported++;
|
||||
}
|
||||
|
||||
// Import widget options if provided
|
||||
if ($options && is_array($options)) {
|
||||
foreach ($options as $option_key => $option_value) {
|
||||
if (preg_match('/^widget_aksrt_/', $option_key)) {
|
||||
update_option($option_key, $option_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import sidebar assignments
|
||||
update_option('sidebars_widgets', $sidebars);
|
||||
|
||||
return $this->success(['message' => "已导入 {$imported} 个侧栏配置。"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /widgets/templates — List saved widget templates.
|
||||
*/
|
||||
public function list_widget_templates(WP_REST_Request $request): WP_REST_Response {
|
||||
$templates = get_option('aksrt_widget_templates', []);
|
||||
return $this->success(['data' => ['templates' => $templates]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /widgets/templates — Save current layout as a named template.
|
||||
*/
|
||||
public function save_widget_template(WP_REST_Request $request): WP_REST_Response {
|
||||
$params = $request->get_json_params() ?? [];
|
||||
$name = sanitize_text_field((string) ($params['name'] ?? ''));
|
||||
if (!$name) {
|
||||
return $this->error('请输入模板名称');
|
||||
}
|
||||
|
||||
$templates = get_option('aksrt_widget_templates', []);
|
||||
$template = [
|
||||
'id' => count($templates) + 1,
|
||||
'name' => $name,
|
||||
'created_at' => gmdate('Y-m-d H:i:s'),
|
||||
'sidebars_widgets' => get_option('sidebars_widgets', []),
|
||||
'widget_options' => $this->collect_widget_options(),
|
||||
];
|
||||
|
||||
$templates[] = $template;
|
||||
update_option('aksrt_widget_templates', $templates);
|
||||
|
||||
return $this->success(['message' => '模板已保存', 'data' => $template]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /widgets/templates/{id}/apply — Apply a saved template.
|
||||
*/
|
||||
public function apply_widget_template(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$templates = get_option('aksrt_widget_templates', []);
|
||||
|
||||
$template = null;
|
||||
foreach ($templates as $t) {
|
||||
if ((int) ($t['id'] ?? 0) === $id) {
|
||||
$template = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$template) {
|
||||
return $this->error('模板不存在', 404);
|
||||
}
|
||||
|
||||
// Apply widget options
|
||||
if (!empty($template['widget_options']) && is_array($template['widget_options'])) {
|
||||
foreach ($template['widget_options'] as $key => $value) {
|
||||
update_option($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sidebar assignments
|
||||
if (!empty($template['sidebars_widgets'])) {
|
||||
update_option('sidebars_widgets', $template['sidebars_widgets']);
|
||||
}
|
||||
|
||||
return $this->success(['message' => '模板已应用']);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /widgets/templates/{id} — Delete a saved template.
|
||||
*/
|
||||
public function delete_widget_template(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$templates = get_option('aksrt_widget_templates', []);
|
||||
|
||||
$found = false;
|
||||
$filtered = [];
|
||||
foreach ($templates as $t) {
|
||||
if ((int) ($t['id'] ?? 0) === $id) {
|
||||
$found = true;
|
||||
continue;
|
||||
}
|
||||
$filtered[] = $t;
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
return $this->error('模板不存在', 404);
|
||||
}
|
||||
|
||||
update_option('aksrt_widget_templates', $filtered);
|
||||
return $this->success(['message' => '模板已删除']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all widget options from the database.
|
||||
*/
|
||||
private function collect_widget_options(): array {
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results(
|
||||
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'widget_aksrt_%' ORDER BY option_name",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$options = [];
|
||||
if ($rows) {
|
||||
foreach ($rows as $row) {
|
||||
$value = maybe_unserialize($row['option_value']);
|
||||
$options[$row['option_name']] = $value;
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function persist_setting_backup(array $backup): void {
|
||||
$backups = get_option('aksrt_settings_backups', []);
|
||||
if (!is_array($backups)) {
|
||||
$backups = [];
|
||||
}
|
||||
|
||||
array_unshift($backups, $backup);
|
||||
update_option('aksrt_settings_backups', $this->trim_setting_backups($backups));
|
||||
}
|
||||
|
||||
private function create_backup_record(string $type, string $label = ''): array {
|
||||
return $this->build_settings_backup($type, $label);
|
||||
}
|
||||
|
||||
public function register_backup_schedules(array $schedules): array {
|
||||
if (!isset($schedules[self::MONTHLY_SCHEDULE])) {
|
||||
$schedules[self::MONTHLY_SCHEDULE] = [
|
||||
'interval' => MONTH_IN_SECONDS,
|
||||
'display' => __('每月', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
public function sync_backup_schedule(): void {
|
||||
$settings = $this->get_backup_schedule_settings();
|
||||
$next = wp_next_scheduled(self::SCHEDULED_BACKUP_HOOK);
|
||||
|
||||
if (!$settings['enabled']) {
|
||||
if ($next) {
|
||||
wp_unschedule_event($next, self::SCHEDULED_BACKUP_HOOK);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$schedule = $settings['schedule'];
|
||||
if ($next && wp_get_schedule(self::SCHEDULED_BACKUP_HOOK) !== $schedule) {
|
||||
wp_unschedule_event($next, self::SCHEDULED_BACKUP_HOOK);
|
||||
$next = false;
|
||||
}
|
||||
|
||||
if (!$next) {
|
||||
wp_schedule_event(time() + HOUR_IN_SECONDS, $schedule, self::SCHEDULED_BACKUP_HOOK);
|
||||
}
|
||||
}
|
||||
|
||||
public function clear_backup_schedule(): void {
|
||||
$next = wp_next_scheduled(self::SCHEDULED_BACKUP_HOOK);
|
||||
while ($next) {
|
||||
wp_unschedule_event($next, self::SCHEDULED_BACKUP_HOOK);
|
||||
$next = wp_next_scheduled(self::SCHEDULED_BACKUP_HOOK);
|
||||
}
|
||||
}
|
||||
|
||||
public function run_scheduled_backup(): void {
|
||||
$settings = $this->get_backup_schedule_settings();
|
||||
if (!$settings['enabled']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$backup = $this->create_backup_record('scheduled');
|
||||
$this->persist_setting_backup($backup);
|
||||
}
|
||||
|
||||
public function handle_theme_upgrade_snapshot($response, array $hook_extra) {
|
||||
if (($hook_extra['type'] ?? '') !== 'theme') {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$themes = $hook_extra['themes'] ?? [];
|
||||
$stylesheet = wp_get_theme()->get_stylesheet();
|
||||
if (!in_array($stylesheet, (array) $themes, true)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$backup = $this->create_backup_record('update_theme');
|
||||
$this->persist_setting_backup($backup);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait AKSRT_Admin_Import_Export_Controller_Part_2 {
|
||||
|
||||
|
||||
private function get_backup_schedule_settings(): array {
|
||||
$provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$settings = $provider->read_settings('tools', 'backup');
|
||||
$frequency = sanitize_key((string) ($settings['backup_frequency'] ?? 'weekly'));
|
||||
$allowed = ['daily', 'weekly', 'monthly'];
|
||||
if (!in_array($frequency, $allowed, true)) {
|
||||
$frequency = 'weekly';
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => !empty($settings['backup_auto']),
|
||||
'schedule' => $frequency === 'monthly' ? self::MONTHLY_SCHEDULE : $frequency,
|
||||
];
|
||||
}
|
||||
|
||||
private function build_settings_backup(string $type, string $label = ''): array {
|
||||
$schema_provider = AKSRT_Admin_Schema_Provider::get_instance();
|
||||
$domains = $schema_provider->get_domain_keys();
|
||||
|
||||
$payload = [
|
||||
'version' => AKSRT_VERSION ?? '3.4.0',
|
||||
'exported' => gmdate('Y-m-d H:i:s'),
|
||||
'domains' => [],
|
||||
'standalone'=> [],
|
||||
];
|
||||
|
||||
foreach ($domains as $domain) {
|
||||
$payload['domains'][$domain] = [
|
||||
'option_key' => $schema_provider->get_option_key($domain),
|
||||
'data' => $schema_provider->read_settings($domain),
|
||||
];
|
||||
}
|
||||
|
||||
foreach (AKSRT_Settings::STANDALONE_OPTION_KEYS as $key) {
|
||||
$value = get_option($key);
|
||||
if ($value !== false) {
|
||||
$payload['standalone'][$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => time(),
|
||||
'type' => $type,
|
||||
'label' => $label !== '' ? $label : $this->default_backup_label($type),
|
||||
'created_at' => gmdate('Y-m-d H:i:s'),
|
||||
'payload' => $payload,
|
||||
];
|
||||
}
|
||||
|
||||
private function trim_setting_backups(array $backups): array {
|
||||
$retain = (int) (AKSRT_Settings::get_value('backup_retain', 7));
|
||||
$retain = max(3, min(20, $retain));
|
||||
|
||||
if (count($backups) <= $retain) {
|
||||
return array_values($backups);
|
||||
}
|
||||
|
||||
return array_values(array_slice($backups, 0, $retain));
|
||||
}
|
||||
|
||||
private function default_backup_label(string $type): string {
|
||||
$map = [
|
||||
'manual' => __('手动备份', 'aksrt'),
|
||||
'scheduled' => __('定期备份', 'aksrt'),
|
||||
'reset_all' => __('重置全部前快照', 'aksrt'),
|
||||
'reset_domain' => __('重置分区前快照', 'aksrt'),
|
||||
'update_theme' => __('更新主题前快照', 'aksrt'),
|
||||
];
|
||||
|
||||
return $map[$type] ?? __('设置快照', 'aksrt');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions_Audit section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions_Audit {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'audit',
|
||||
'title' => 'API内容审核',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'provider', 'title' => '审核接口', 'fields' => [
|
||||
['key' => 'content_audit_enabled', 'label' => '启用内容审核', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'content_audit_provider', 'label' => '审核服务商', 'type' => 'radio', 'default' => 'baidu', 'options' => [
|
||||
['label' => '百度内容审核', 'value' => 'baidu'],
|
||||
['label' => '关闭审核接口', 'value' => 'null'],
|
||||
]],
|
||||
['key' => 'content_audit_api_key', 'label' => 'API Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'content_audit_api_secret', 'label' => 'Secret Key', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'scope', 'title' => '审核范围', 'fields' => [
|
||||
['key' => 'content_audit_text_enabled', 'label' => '文本审核', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'content_audit_image_enabled', 'label' => '图片审核', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_bbs_term', 'label' => '审核论坛分类', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'fallback', 'title' => '失败策略', 'fields' => [
|
||||
['key' => 'content_audit_on_failure', 'label' => '审核失败时', 'type' => 'radio', 'default' => 'allow', 'options' => [
|
||||
['label' => '放行', 'value' => 'allow'],
|
||||
['label' => '拦截', 'value' => 'block'],
|
||||
]],
|
||||
['key' => 'content_audit_log_enabled', 'label' => '记录审核日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions_Docs_Mode section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions_Docs_Mode {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'docs-mode',
|
||||
'title' => '文档模式',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础配置', 'fields' => [
|
||||
['key' => 'docs_mode_enabled', 'label' => '启用文档模式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'docs_mode_default_template', 'label' => '文档首页模板', 'type' => 'select', 'default' => 'documentnav', 'options' => [
|
||||
['label' => '文档导航页', 'value' => 'documentnav'],
|
||||
['label' => '专题聚合页', 'value' => 'special'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'navigation', 'title' => '导航设置', 'fields' => [
|
||||
['key' => 'docs_mode_toc_enabled', 'label' => '显示目录树', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'docs_mode_chapter_nav_enabled', 'label' => '显示章节导航', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'docs_mode_default_order', 'label' => '默认排序', 'type' => 'radio', 'default' => 'menu_order', 'options' => [
|
||||
['label' => '按菜单顺序', 'value' => 'menu_order'],
|
||||
['label' => '按发布时间', 'value' => 'date'],
|
||||
['label' => '按标题排序', 'value' => 'title'],
|
||||
]],
|
||||
['key' => 'docs_mode_toc_depth', 'label' => '目录深度', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 6],
|
||||
['key' => 'docs_mode_chapter_title', 'label' => '章节标题', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'filter', 'title' => '筛选设置', 'fields' => [
|
||||
['key' => 'docs_mode_cats', 'label' => '文档分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'docs_mode_exclude', 'label' => '在首页排除此类内容', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'style', 'title' => '样式设置', 'fields' => [
|
||||
['key' => 'docs_mode_style', 'label' => '文档样式', 'type' => 'select', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '简洁', 'value' => 'simple'],
|
||||
['label' => '卡片', 'value' => 'card'],
|
||||
]],
|
||||
['key' => 'docs_mode_icon', 'label' => '显示图标', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'docs_mode_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions_IP section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions_IP {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'ip',
|
||||
'title' => 'IP归属地',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础配置', 'fields' => [
|
||||
['key' => 'ip_geo_enabled', 'label' => '启用 IP 归属地', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'ip_addr_sdk', 'label' => 'IP 接口服务商', 'type' => 'radio', 'default' => 'null', 'options' => [
|
||||
['label' => '轮流查询', 'value' => 'polling'],
|
||||
['label' => '腾讯位置服务', 'value' => 'qq'],
|
||||
['label' => '高德位置服务', 'value' => 'amap'],
|
||||
['label' => '太平洋公共接口', 'value' => 'null'],
|
||||
]],
|
||||
['key' => 'ip_geo_cache_ttl', 'label' => '缓存时间 (秒)', 'type' => 'number', 'default' => 86400, 'min' => 3600, 'max' => 604800],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '接口凭据', 'fields' => [
|
||||
['key' => 'ip_addr_sdk_qq_appkey', 'label' => '腾讯 AppKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ip_addr_sdk_qq_secretkey', 'label' => '腾讯 SecretKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ip_addr_sdk_amap_appkey', 'label' => '高德 AppKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ip_addr_sdk_amap_secretkey', 'label' => '高德 SecretKey', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'ip_geo_display', 'label' => '展示位置', 'type' => 'checkbox', 'default' => ['comment'], 'sanitize_type' => 'array', 'options' => [
|
||||
['label' => '评论区', 'value' => 'comment'],
|
||||
['label' => '用户主页', 'value' => 'user_home'],
|
||||
['label' => '文章页', 'value' => 'post'],
|
||||
]],
|
||||
['key' => 'ip_geo_format', 'label' => '显示格式', 'type' => 'radio', 'default' => 'province_city', 'options' => [
|
||||
['label' => '省份+城市', 'value' => 'province_city'],
|
||||
['label' => '仅省份', 'value' => 'province'],
|
||||
['label' => '国家+省份', 'value' => 'country_province'],
|
||||
]],
|
||||
['key' => 'ip_geo_fallback_text', 'label' => '查询失败时文案', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions_Push section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions_Push {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'push',
|
||||
'title' => '百度推送',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '推送配置', 'fields' => [
|
||||
['key' => 'baidu_push_enabled', 'label' => '启用百度推送', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'baidu_push_token', 'label' => '推送 Token', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'trigger', 'title' => '触发规则', 'fields' => [
|
||||
['key' => 'baidu_push_auto', 'label' => '自动推送', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'baidu_push_on_publish', 'label' => '发布时主动推送', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'baidu_push_on_update', 'label' => '更新时推送', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'xzh', 'title' => '熊掌号 / JSON-LD', 'fields' => [
|
||||
['key' => 'xzh_on', 'label' => '启用熊掌号', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'xzh_appid', 'label' => '百度 AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'xzh_render_tail', 'label' => '文章底部显示熊掌号尾部', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'xzh_jsonld_single', 'label' => '文章页 JSON-LD', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'xzh_jsonld_page', 'label' => '页面 JSON-LD', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'xzh_jsonld_img', 'label' => '不添加图片', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'advanced', 'title' => '高级设置', 'fields' => [
|
||||
['key' => 'baidu_push_batch_size', 'label' => '批量推送条数', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 500],
|
||||
['key' => 'baidu_push_log_enabled', 'label' => '记录推送日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions_Security section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions_Security {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'security',
|
||||
'title' => '网站安全',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础安全', 'fields' => [
|
||||
['key' => 'hide_admin_bar', 'label' => '前台关闭顶部 admin_bar', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'remove_emoji', 'label' => '移除Emoji脚本', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'remove_open_sans', 'label' => '移除Open Sans字体', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'disabled_pingback', 'label' => '禁用Pingback', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'disabled_xmlrpc', 'label' => '禁用XML-RPC', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'close_attachment_page', 'label' => '关闭附件页面', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_disable_admin', 'label' => '非管理员禁止进入后台', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'upload', 'title' => '上传设置', 'fields' => [
|
||||
['key' => 'split_upload_s', 'label' => '分片上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'split_minimum_size', 'label' => '最小分片大小(MB)', 'type' => 'number', 'default' => 20, 'min' => 4, 'max' => 100, 'step' => 2],
|
||||
['key' => 'newfilename', 'label' => '上传文件自动重命名', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'newfilename_type', 'label' => '重命名格式', 'type' => 'radio', 'default' => 'time', 'options' => [
|
||||
['label' => '随机字符串', 'value' => 'random'],
|
||||
['label' => '时间+原文件名', 'value' => 'time'],
|
||||
]],
|
||||
['key' => 'disabled_large_thumbnail', 'label' => '禁用大缩略图', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'admin', 'title' => '后台设置', 'fields' => [
|
||||
['key' => 'remove_dashboard_widgets', 'label' => 'WP 后台加速:移除站点健康/活动/禁止浏览器&PHP版本检测', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'display_wp_update', 'label' => '禁止 WP 检测更新', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'close_gutenberg', 'label' => '关闭古腾堡编辑器', 'type' => 'switch', 'default' => false, 'help' => '禁用后编辑器增强全部失效'],
|
||||
['key' => 'disabled_autoembed', 'label' => '禁用自动嵌入', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'mce_float_toolbar', 'label' => '浮动工具栏', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'sanitize_type' => 'array', 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc_s'],
|
||||
['label' => '移动端', 'value' => 'm_s'],
|
||||
]],
|
||||
['key' => 'admin_user_del_fields', 'label' => '关闭后台用户编辑多余选项', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'admin_frontend_set', 'label' => '前台设置入口', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'format', 'title' => '格式设置', 'fields' => [
|
||||
['key' => 'time_ago_s', 'label' => '时间格式', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'time_format', 'label' => '时间显示格式', 'type' => 'input', 'default' => 'n月j日 H:i'],
|
||||
['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'verification', 'title' => '验证设置', 'fields' => [
|
||||
['key' => 'verification_comment_s', 'label' => '评论需要验证', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'verification_links_s', 'label' => '链接需要验证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'verification_newposts_s', 'label' => '新文章需要验证', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'verification_bbspost_s', 'label' => '论坛帖子需要验证', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'verification_signin_exclude', 'label' => '登录人机验证排除账号', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_verification_type', 'label' => '验证类型', 'type' => 'radio', 'default' => 'slider', 'options' => [
|
||||
['label' => '图形验证码', 'value' => 'image'],
|
||||
['label' => '滑块验证', 'value' => 'slider'],
|
||||
['label' => '腾讯验证码', 'value' => 'tcaptcha'],
|
||||
['label' => '极验验证码', 'value' => 'geetest'],
|
||||
['label' => '关闭验证', 'value' => 'null'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'captcha', 'title' => '验证码设置', 'fields' => [
|
||||
['key' => 'geetest_enabled', 'label' => '极验验证码', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'geetest_id', 'label' => '极验 ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'geetest_key', 'label' => '极验 Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'tcaptcha_enabled', 'label' => '腾讯云验证码', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'tcaptcha_appid', 'label' => '腾讯云 AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'tcaptcha_secret_id', 'label' => '腾讯云 SecretId', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'tcaptcha_secret_key', 'label' => '腾讯云 SecretKey', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
||||
['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'brush_limit_post_minutes_10', 'label' => '文章 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
||||
['key' => 'brush_limit_post_day_1', 'label' => '文章每日限制', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 2000],
|
||||
['key' => 'brush_limit_comment_enabled', 'label' => '评论频率限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'brush_limit_comment_minutes_10', 'label' => '评论 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
||||
['key' => 'brush_limit_comment_day_1', 'label' => '评论每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
||||
['key' => 'brush_limit_upload_enabled', 'label' => '上传频率限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'brush_limit_upload_minutes_10', 'label' => '上传 10 分钟限制', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 200],
|
||||
['key' => 'brush_limit_upload_day_1', 'label' => '上传每日限制', 'type' => 'number', 'default' => 200, 'min' => 1, 'max' => 2000],
|
||||
['key' => 'brush_limit_register_enabled', 'label' => '注册频率限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'brush_limit_register_minutes_10', 'label' => '注册 10 分钟限制', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
||||
['key' => 'brush_limit_register_day_1', 'label' => '注册每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
||||
['key' => 'brush_limit_order_enabled', 'label' => '订单频率限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'brush_limit_order_minutes_10', 'label' => '订单 10 分钟限制', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 200],
|
||||
['key' => 'brush_limit_order_day_1', 'label' => '订单每日限制', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 2000],
|
||||
['key' => 'error_frequency_limit', 'label' => '错误频率限制', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'content-audit', 'title' => '内容审核', 'fields' => [
|
||||
['key' => 'audit_comment', 'label' => '审核评论', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_new_post', 'label' => '审核新文章', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_upload_img', 'label' => '审核上传图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_user_desc', 'label' => '审核用户简介', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_msg_private', 'label' => '审核私信', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_be_like_text', 'label' => '文本允许疑似合规', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_be_like_img', 'label' => '图片允许疑似合规', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_bbs_posts', 'label' => '审核论坛帖子', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_bbs_plate', 'label' => '审核论坛板块', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'audit_bbs_term', 'label' => '审核论坛分类', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Extensions schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Extensions {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'extensions',
|
||||
'title' => '扩展&增强',
|
||||
'sections' => [
|
||||
'ip' => AKSRT_Admin_Schema_Extensions_IP::build(),
|
||||
'audit' => AKSRT_Admin_Schema_Extensions_Audit::build(),
|
||||
'push' => AKSRT_Admin_Schema_Extensions_Push::build(),
|
||||
'security' => AKSRT_Admin_Schema_Extensions_Security::build(),
|
||||
'docs-mode' => AKSRT_Admin_Schema_Extensions_Docs_Mode::build(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Basic section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Basic {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'basic',
|
||||
'title' => '基础设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'switch', 'title' => '功能开关', 'fields' => [
|
||||
['key' => 'bbs_s', 'label' => '启用论坛', 'type' => 'switch', 'default' => true, 'help' => '关闭后论坛页面将返回 404'],
|
||||
]],
|
||||
['key' => 'admin', 'title' => '论坛管理', 'fields' => [
|
||||
['key' => 'bbs_admin_users', 'label' => '论坛管理员', 'type' => 'user_select', 'multiple' => true, 'default' => [], 'help' => '拥有跨版块全部治理权限,绕过 ban'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Board_Page section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Board_Page {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'board_page',
|
||||
'title' => '版块页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '版块页布局', 'fields' => [
|
||||
['key' => 'bbs_plate_top_info_s', 'label' => '版块信息卡', 'type' => 'switch', 'default' => true, 'help' => '版块页顶部显示版块详情卡片'],
|
||||
['key' => 'bbs_plate_tab', 'label' => '版块栏目', 'type' => 'group', 'default' => [
|
||||
['label' => '全部', 'sort' => 'date'],
|
||||
['label' => '推荐', 'sort' => 'featured'],
|
||||
['label' => '最新回复', 'sort' => 'last_reply'],
|
||||
['label' => '热门', 'sort' => 'hot'],
|
||||
['label' => '精华', 'sort' => 'essence'],
|
||||
]],
|
||||
['key' => 'bbs_plate_tab_active_index', 'label' => '默认选中 Tab', 'type' => 'number', 'default' => 0, 'min' => 0],
|
||||
['key' => 'bbs_plate_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Credits section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Credits {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'credits',
|
||||
'title' => '积分设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'earn', 'title' => '积分奖励', 'fields' => [
|
||||
['key' => 'bbs_credits_post_new', 'label' => '发帖奖励', 'type' => 'number', 'default' => 5, 'min' => 0],
|
||||
['key' => 'bbs_credits_plate_new', 'label' => '建版奖励', 'type' => 'number', 'default' => 10, 'min' => 0],
|
||||
['key' => 'bbs_credits_comment_new', 'label' => '回复奖励', 'type' => 'number', 'default' => 2, 'min' => 0],
|
||||
['key' => 'bbs_credits_comment_like', 'label' => '评论被赞', 'type' => 'number', 'default' => 1, 'min' => 0],
|
||||
['key' => 'bbs_credits_score_extra', 'label' => '被加分', 'type' => 'number', 'default' => 3, 'min' => 0],
|
||||
['key' => 'bbs_credits_essence', 'label' => '被设精华', 'type' => 'number', 'default' => 10, 'min' => 0],
|
||||
['key' => 'bbs_credits_posts_hot', 'label' => '成为热帖', 'type' => 'number', 'default' => 5, 'min' => 0],
|
||||
['key' => 'bbs_credits_adopted', 'label' => '被采纳', 'type' => 'number', 'default' => 20, 'min' => 0],
|
||||
['key' => 'bbs_credits_daily_max', 'label' => '每日积分上限', 'type' => 'number', 'default' => 50, 'min' => 0, 'help' => '0 = 不限'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Homepage section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Homepage {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'homepage',
|
||||
'title' => '首页设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'tabs', 'title' => '首页 Tab', 'fields' => [
|
||||
['key' => 'bbs_home_tab', 'label' => '首页栏目', 'type' => 'group', 'default' => [
|
||||
['type' => 'follow', 'label' => '关注'],
|
||||
['type' => 'synthesis', 'label' => '综合'],
|
||||
['type' => 'plate', 'label' => '版块'],
|
||||
], 'help' => '可拖拽排序,支持 follow / synthesis / plate / tabs 类型'],
|
||||
['key' => 'bbs_home_tab_active_index', 'label' => '默认选中 Tab', 'type' => 'number', 'default' => 1, 'min' => 0],
|
||||
['key' => 'bbs_home_tab_quick_posts_s', 'label' => '快速发帖', 'type' => 'switch', 'default' => true, 'help' => '首页顶部显示快速发帖入口'],
|
||||
['key' => 'bbs_home_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Names section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Names {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'names',
|
||||
'title' => '名称定义',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display_names', 'title' => '名词自定义', 'fields' => [
|
||||
['key' => 'bbs_forum_display_name', 'label' => '论坛', 'type' => 'text', 'default' => '论坛'],
|
||||
['key' => 'bbs_plate_display_name', 'label' => '版块', 'type' => 'text', 'default' => '版块'],
|
||||
['key' => 'bbs_posts_display_name', 'label' => '帖子', 'type' => 'text', 'default' => '帖子'],
|
||||
['key' => 'bbs_topic_display_name', 'label' => '话题', 'type' => 'text', 'default' => '话题'],
|
||||
['key' => 'bbs_tag_display_name', 'label' => '标签', 'type' => 'text', 'default' => '标签'],
|
||||
['key' => 'bbs_comment_display_name', 'label' => '回复', 'type' => 'text', 'default' => '回复'],
|
||||
['key' => 'bbs_question_badge_name', 'label' => '提问徽章', 'type' => 'text', 'default' => '提问'],
|
||||
['key' => 'bbs_question_ok_badge_name', 'label' => '已解决徽章', 'type' => 'text', 'default' => '已解决'],
|
||||
['key' => 'bbs_cat_moderator_name', 'label' => '分区版主', 'type' => 'text', 'default' => '分区版主'],
|
||||
['key' => 'bbs_plate_author_name', 'label' => '超级版主', 'type' => 'text', 'default' => '超级版主'],
|
||||
['key' => 'bbs_plate_moderator_name', 'label' => '版主', 'type' => 'text', 'default' => '版主'],
|
||||
['key' => 'bbs_plate_follow_name', 'label' => '关注', 'type' => 'text', 'default' => '关注'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Other section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Other {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'other',
|
||||
'title' => '其它设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'vote', 'title' => '投票', 'fields' => [
|
||||
['key' => 'bbs_vote_number_type', 'label' => '投票显示', 'type' => 'select', 'default' => 'percent', 'options' => [
|
||||
['label' => '百分比', 'value' => 'percent'],
|
||||
['label' => '票数', 'value' => 'number'],
|
||||
]],
|
||||
['key' => 'bbs_vote_max', 'label' => '选项上限', 'type' => 'number', 'default' => 8, 'min' => 2, 'max' => 20],
|
||||
]],
|
||||
['key' => 'moderator_apply', 'title' => '版主申请', 'fields' => [
|
||||
['key' => 'bbs_apply_moderator_desc', 'label' => '申请说明', 'type' => 'textarea', 'default' => '欢迎有热情的用户申请成为版主,协助管理社区秩序。'],
|
||||
]],
|
||||
['key' => 'misc', 'title' => '杂项', 'fields' => [
|
||||
['key' => 'bbs_term_thumb', 'label' => '标签默认缩略图', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'bbs_post_lists_hide_pay_s', 'label' => '列表隐藏付费标记', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_post_title_strlen_limit', 'label' => '标题字数限制', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 100, 'help' => '最小 5,最大 100'],
|
||||
['key' => 'bbs_t_placeholder', 'label' => '标题占位符', 'type' => 'text', 'default' => '请输入标题'],
|
||||
['key' => 'bbs_c_placeholder', 'label' => '内容占位符', 'type' => 'text', 'default' => '请输入内容'],
|
||||
]],
|
||||
['key' => 'url', 'title' => 'URL / 别名', 'fields' => [
|
||||
['key' => 'bbs_rewrite_suffix_html_s', 'label' => '.html 后缀', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_plate_rewrite_slug', 'label' => '版块别名', 'type' => 'text', 'default' => 'board'],
|
||||
['key' => 'bbs_posts_rewrite_slug', 'label' => '帖子别名', 'type' => 'text', 'default' => 'topic'],
|
||||
['key' => 'bbs_posts_edit_rewrite_slug', 'label' => '编辑别名', 'type' => 'text', 'default' => 'topic-edit'],
|
||||
['key' => 'bbs_posts_show_in_rest_s', 'label' => '古腾堡编辑器', 'type' => 'switch', 'default' => false, 'help' => '开启后帖子可使用 Gutenberg'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Permissions section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Permissions {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'permissions',
|
||||
'title' => '权限配置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'posting', 'title' => '发帖限制', 'fields' => [
|
||||
['key' => 'bbs_posts_add_limit_opt_max', 'label' => '每日发帖上限', 'type' => 'number', 'default' => 10, 'min' => 0, 'help' => '0 = 不限'],
|
||||
['key' => 'bbs_plate_add_limit_opt_max', 'label' => '建版数量上限', 'type' => 'number', 'default' => 3, 'min' => 0],
|
||||
]],
|
||||
['key' => 'display_btns', 'title' => '按钮显示', 'fields' => [
|
||||
['key' => 'bbs_show_new_plate', 'label' => '始终显示建版按钮', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_show_new_topic', 'label' => '始终显示发帖按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_show_apply_moderator', 'label' => '始终显示申请版主', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'paid_content', 'title' => '帖子付费', 'fields' => [
|
||||
['key' => 'bbs_post_pay_hide_type_s', 'label' => '启用付费内容', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_post_pay_vip_price_s', 'label' => 'VIP 特价', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_post_pay_vip_1_discount', 'label' => '一级 VIP 折扣 (%)', 'type' => 'number', 'default' => 80, 'min' => 0, 'max' => 100],
|
||||
['key' => 'bbs_post_pay_vip_2_discount', 'label' => '二级 VIP 折扣 (%)', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 100],
|
||||
['key' => 'bbs_post_pay_price_limit', 'label' => '最高定价', 'type' => 'number', 'default' => 1000, 'min' => 1],
|
||||
]],
|
||||
['key' => 'plate_follow', 'title' => '版块付费关注', 'fields' => [
|
||||
['key' => 'bbs_plate_follow_pay_s', 'label' => '启用付费关注', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_plate_follow_pay_price', 'label' => '关注价格', 'type' => 'number', 'default' => 10, 'min' => 0],
|
||||
['key' => 'bbs_plate_follow_pay_type', 'label' => '支付方式', 'type' => 'select', 'default' => 'money', 'options' => [
|
||||
['label' => '现金', 'value' => 'money'],
|
||||
['label' => '积分', 'value' => 'points'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Post_Page section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Post_Page {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'post_page',
|
||||
'title' => '帖子页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'breadcrumbs', 'title' => '面包屑', 'fields' => [
|
||||
['key' => 'bbs_breadcrumbs_s', 'label' => '显示面包屑', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_breadcrumbs_home', 'label' => '显示首页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_breadcrumbs_bbs_home', 'label' => '显示论坛首页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_breadcrumbs_bbs_home_name', 'label' => '论坛首页名称', 'type' => 'text', 'default' => '论坛'],
|
||||
['key' => 'bbs_breadcrumbs_plate_cat', 'label' => '显示分区', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'score', 'title' => '评分设置', 'fields' => [
|
||||
['key' => 'bbs_score_extra_max', 'label' => '加分上限', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 50],
|
||||
['key' => 'bbs_score_deduct_max', 'label' => '扣分上限', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 50],
|
||||
]],
|
||||
['key' => 'display', 'title' => '显示', 'fields' => [
|
||||
['key' => 'bbs_posts_maxheight_s', 'label' => '内容高度限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_posts_maxheight', 'label' => '最大高度 (px)', 'type' => 'number', 'default' => 800, 'min' => 300, 'max' => 3000, 'condition' => ['bbs_posts_maxheight_s' => true]],
|
||||
]],
|
||||
['key' => 'cover', 'title' => '封面设置', 'fields' => [
|
||||
['key' => 'bbs_posts_cover_ratio', 'label' => '封面比例', 'type' => 'select', 'default' => '16:9', 'options' => [
|
||||
['label' => '16:9', 'value' => '16:9'],
|
||||
['label' => '4:3', 'value' => '4:3'],
|
||||
['label' => '1:1', 'value' => '1:1'],
|
||||
['label' => '3:4', 'value' => '3:4'],
|
||||
]],
|
||||
['key' => 'bbs_posts_cover_video_mute', 'label' => '视频封面静音', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Reply section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Reply {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'reply',
|
||||
'title' => '回复评论',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'paginate', 'title' => '翻页', 'fields' => [
|
||||
['key' => 'bbs_reply_paginate_type', 'label' => '翻页方式', 'type' => 'select', 'default' => 'ajax', 'options' => [
|
||||
['label' => 'AJAX 加载', 'value' => 'ajax'],
|
||||
['label' => '传统翻页', 'value' => 'page'],
|
||||
]],
|
||||
['key' => 'bbs_reply_paginate_ias_s', 'label' => '无限滚动', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_reply_paginate_ias_max', 'label' => '自动加载页数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 10],
|
||||
]],
|
||||
['key' => 'toolbar', 'title' => '回复工具栏', 'fields' => [
|
||||
['key' => 'bbs_comment_smilie', 'label' => '表情', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_comment_code', 'label' => '代码', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_comment_img', 'label' => '插图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_comment_upload_img', 'label' => '上传图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bbs_comment_quick_s', 'label' => '快捷回复', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'bbs_comment_quick_often', 'label' => '预设短语', 'type' => 'textarea', 'default' => "不错,学习了\n感谢分享\n期待更新", 'help' => '每行一条'],
|
||||
['key' => 'bbs_comment_placeholder', 'label' => '回复占位符', 'type' => 'text', 'default' => '写下你的回复…'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum_Upload section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum_Upload {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'upload',
|
||||
'title' => '上传权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'limits', 'title' => '上传限制', 'fields' => [
|
||||
['key' => 'bbs_upload_img_size', 'label' => '图片大小 (KB)', 'type' => 'number', 'default' => 2048, 'min' => 100],
|
||||
['key' => 'bbs_image_upload_multiple', 'label' => '批量上传数', 'type' => 'number', 'default' => 9, 'min' => 1, 'max' => 20],
|
||||
['key' => 'bbs_upload_video_size', 'label' => '视频大小 (KB)', 'type' => 'number', 'default' => 20480, 'min' => 1024],
|
||||
['key' => 'bbs_upload_file_size', 'label' => '文件大小 (KB)', 'type' => 'number', 'default' => 10240, 'min' => 100],
|
||||
['key' => 'bbs_upload_file_mimes', 'label' => '允许格式', 'type' => 'text', 'default' => 'jpg,jpeg,png,gif,mp4,zip,rar,pdf', 'help' => '逗号分隔'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Forum schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Forum {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'forum',
|
||||
'title' => '社区&论坛',
|
||||
'sections' => [
|
||||
'basic' => AKSRT_Admin_Schema_Forum_Basic::build(),
|
||||
'names' => AKSRT_Admin_Schema_Forum_Names::build(),
|
||||
'homepage' => AKSRT_Admin_Schema_Forum_Homepage::build(),
|
||||
'board_page' => AKSRT_Admin_Schema_Forum_Board_Page::build(),
|
||||
'post_page' => AKSRT_Admin_Schema_Forum_Post_Page::build(),
|
||||
'reply' => AKSRT_Admin_Schema_Forum_Reply::build(),
|
||||
'permissions' => AKSRT_Admin_Schema_Forum_Permissions::build(),
|
||||
'other' => AKSRT_Admin_Schema_Forum_Other::build(),
|
||||
'upload' => AKSRT_Admin_Schema_Forum_Upload::build(),
|
||||
'credits' => AKSRT_Admin_Schema_Forum_Credits::build(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// §8.1 Capability matrix schema builder
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* Build groups for the capabilities section from cap_definitions().
|
||||
*
|
||||
* Each capability becomes a group with role columns as fields.
|
||||
* Grouped by prefix: user_, new_post_, comment_.
|
||||
*/
|
||||
private static function build_cap_matrix_groups(): array {
|
||||
if (!class_exists('AKSRT_Settings')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$defs = AKSRT_Settings::cap_definitions();
|
||||
$cols = AKSRT_Settings::cap_role_columns();
|
||||
|
||||
$col_labels = [
|
||||
'all' => __('所有人', 'aksrt'),
|
||||
'logged' => __('已登录', 'aksrt'),
|
||||
'level' => __('等级≥', 'aksrt'),
|
||||
'vip' => __('VIP≥', 'aksrt'),
|
||||
'auth' => __('已认证', 'aksrt'),
|
||||
'moderator' => __('版主', 'aksrt'),
|
||||
'plate_author' => __('超级版主', 'aksrt'),
|
||||
'cat_moderator' => __('分区版主', 'aksrt'),
|
||||
];
|
||||
|
||||
// Group caps by prefix
|
||||
$buckets = [
|
||||
'user' => ['title' => __('用户治理', 'aksrt'), 'caps' => []],
|
||||
'post' => ['title' => __('投稿权限', 'aksrt'), 'caps' => []],
|
||||
'comment' => ['title' => __('评论权限', 'aksrt'), 'caps' => []],
|
||||
];
|
||||
|
||||
foreach ($defs as $cap_id => $def) {
|
||||
if (str_starts_with($cap_id, 'comment_')) {
|
||||
$buckets['comment']['caps'][$cap_id] = $def;
|
||||
} elseif (str_starts_with($cap_id, 'new_post_')) {
|
||||
$buckets['post']['caps'][$cap_id] = $def;
|
||||
} else {
|
||||
$buckets['user']['caps'][$cap_id] = $def;
|
||||
}
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
foreach ($buckets as $bucket_key => $bucket) {
|
||||
if (empty($bucket['caps'])) continue;
|
||||
|
||||
$fields = [];
|
||||
foreach ($bucket['caps'] as $cap_id => $def) {
|
||||
$excludes = $def['exclude_roles'] ?? [];
|
||||
$only_roles = $def['roles'] ?? null;
|
||||
$defaults = $def['default'] ?? [];
|
||||
|
||||
// Each cap gets one field per role column
|
||||
foreach ($cols as $col) {
|
||||
if (in_array($col, $excludes, true)) continue;
|
||||
if ($only_roles !== null && !in_array($col, $only_roles, true)
|
||||
&& !in_array($col, ['all', 'logged', 'level', 'vip', 'auth'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$field_key = 'user_caps.' . $cap_id . '.' . $col;
|
||||
$is_spinner = in_array($col, ['level', 'vip'], true);
|
||||
|
||||
$field = [
|
||||
'key' => $field_key,
|
||||
'label' => $def['label'] . ' — ' . ($col_labels[$col] ?? $col),
|
||||
'type' => $is_spinner ? 'number' : 'switch',
|
||||
'default' => $defaults[$col] ?? ($is_spinner ? -1 : false),
|
||||
'cap_id' => $cap_id,
|
||||
'cap_col' => $col,
|
||||
];
|
||||
|
||||
if ($is_spinner) {
|
||||
$field['min'] = -1;
|
||||
$field['max'] = $col === 'level' ? 10 : 2;
|
||||
$field['help'] = __('-1 = 不限', 'aksrt');
|
||||
}
|
||||
|
||||
$fields[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
$groups[] = [
|
||||
'key' => 'cap_' . $bucket_key,
|
||||
'title' => $bucket['title'],
|
||||
'fields' => $fields,
|
||||
];
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Global schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Global {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'global',
|
||||
'title' => '全局&功能',
|
||||
'sections' => [
|
||||
'common' => [
|
||||
'key' => 'common',
|
||||
'title' => '常用功能',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'comment', 'title' => '评论功能', 'fields' => [
|
||||
['key' => 'comment_enabled', 'label' => '开启评论', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'performance', 'title' => '性能优化', 'fields' => [
|
||||
['key' => 'lazyload_enabled', 'label' => '图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'avatar', 'title' => '头像服务', 'fields' => [
|
||||
['key' => 'avatar_source', 'label' => '头像服务源', 'type' => 'select', 'default' => 'cn.cravatar.com', 'options' => [
|
||||
['label' => 'Cravatar (国内推荐)', 'value' => 'cn.cravatar.com'],
|
||||
['label' => 'Gravatar 国内镜像', 'value' => 'cn.gravatar.com'],
|
||||
['label' => 'Gravatar 官方', 'value' => 'gravatar.com'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'filing', 'title' => '备案信息', 'fields' => [
|
||||
['key' => 'icp_filing', 'label' => 'ICP 备案号', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'footer_text', 'label' => '页脚附加文字', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'logo' => [
|
||||
'key' => 'logo',
|
||||
'title' => 'LOGO图像',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_seo_settings',
|
||||
'groups' => [
|
||||
['key' => 'logo', 'title' => 'Logo 设置', 'fields' => [
|
||||
['key' => 'site_logo', 'label' => '站点 Logo', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'site_logo_dark', 'label' => '暗色模式 Logo', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'logo_width', 'label' => 'Logo 宽度', 'type' => 'number', 'default' => 150, 'min' => 50, 'max' => 400],
|
||||
['key' => 'logo_text', 'label' => 'Logo 文字', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'favicon', 'title' => '站点图标', 'fields' => [
|
||||
['key' => 'site_favicon', 'label' => '浏览器图标', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'site_icon', 'label' => '移动端图标', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'seo' => [
|
||||
'key' => 'seo',
|
||||
'title' => 'SEO优化',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => 'SEO基础', 'fields' => [
|
||||
['key' => 'seo_enabled', 'label' => '启用核心SEO优化', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_ai_enabled', 'label' => 'AI生成SEO内容', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_connector', 'label' => 'SEO连接符', 'type' => 'input', 'default' => '-'],
|
||||
['key' => 'seo_title', 'label' => 'SEO标题(title)', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'seo_keywords', 'label' => 'SEO关键字(keywords)', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'seo_description', 'label' => 'SEO描述(description)', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'ai-seo', 'title' => 'AI SEO', 'fields' => [
|
||||
['key' => 'ai_seo_title_prompt', 'label' => '标题提示词', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'ai_seo_keywords_prompt', 'label' => '关键词提示词', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'ai_seo_description_prompt', 'label' => '描述提示词', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'baidu', 'title' => '百度SEO', 'fields' => [
|
||||
['key' => 'seo_baidu_push', 'label' => '百度链接自动提交', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_normal', 'label' => '百度普通收录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_fast', 'label' => '百度快速收录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_baidu_token', 'label' => '百度准入密钥Token', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'image', 'title' => '图片SEO', 'fields' => [
|
||||
['key' => 'seo_image_optimize', 'label' => '搜索引擎图片展示优化', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_image_default', 'label' => '默认图像', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'seo_image_always_default', 'label' => '固定使用默认图像', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_auto_alt', 'label' => '文章图片自动添加alt', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'redirect', 'title' => '外链重定向', 'fields' => [
|
||||
['key' => 'seo_go_link', 'label' => '启用外链重定向', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_go_link_nonce', 'label' => '外链重定向鉴权', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_go_link_post', 'label' => '文章内容外链重定向', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_go_link_exclude', 'label' => '排除域名', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'advanced', 'title' => '高级SEO', 'fields' => [
|
||||
['key' => 'seo_no_category', 'label' => '分类URL去除category', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'seo_jsonld', 'label' => '启用JSON-LD', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_jsonld_single', 'label' => '文章页JSON-LD', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'seo_jsonld_page', 'label' => '页面JSON-LD', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'lazyload' => [
|
||||
'key' => 'lazyload',
|
||||
'title' => '懒加载设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '懒加载开关', 'fields' => [
|
||||
['key' => 'lazyload_enabled', 'label' => '图像异步懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_animation', 'label' => '懒加载动画效果', 'type' => 'select', 'default' => 'blur', 'options' => [
|
||||
['label' => '淡出淡入', 'value' => 'fade'],
|
||||
['label' => '高斯模糊', 'value' => 'blur'],
|
||||
['label' => '放大缩小', 'value' => 'scale'],
|
||||
]],
|
||||
['key' => 'lazyload_placeholder', 'label' => '懒加载预载图', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'lazyload_blur', 'label' => '模糊预览', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'scope', 'title' => '懒加载范围', 'fields' => [
|
||||
['key' => 'lazyload_avatar', 'label' => '头像懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_content', 'label' => '文章内容图片懒加载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'lazyload_comment', 'label' => '评论内容图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_private', 'label' => '私信聊天图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_slider', 'label' => '幻灯片图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_cover', 'label' => '封面图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_other', 'label' => '其它图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'lazyload_widget', 'label' => '小工具图片懒加载', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'layout' => [
|
||||
'key' => 'layout',
|
||||
'title' => '显示&布局',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'color', 'title' => '色彩方案', 'fields' => [
|
||||
['key' => 'theme_mode', 'label' => '默认主题模式', 'type' => 'radio', 'default' => 'time-auto', 'options' => [
|
||||
['label' => '浅色', 'value' => 'white-theme'],
|
||||
['label' => '深色', 'value' => 'dark-theme'],
|
||||
['label' => '跟随系统', 'value' => 'time-auto'],
|
||||
]],
|
||||
['key' => 'theme_mode_button', 'label' => '主题切换按钮位置', 'type' => 'checkbox', 'default' => ['pc_nav', 'm_menu'], 'options' => [
|
||||
['label' => 'PC 顶部导航', 'value' => 'pc_nav'],
|
||||
['label' => '移动端菜单', 'value' => 'm_menu'],
|
||||
]],
|
||||
['key' => 'theme_skin', 'label' => '预设主题色', 'type' => 'palette', 'default' => 'f04494', 'options' => [
|
||||
['label' => '玫红', 'value' => 'ff1856'],
|
||||
['label' => '粉色', 'value' => 'f04494'],
|
||||
['label' => '紫色', 'value' => 'ae53f3'],
|
||||
['label' => '靛蓝', 'value' => '627bf5'],
|
||||
['label' => '天蓝', 'value' => '00a2e3'],
|
||||
['label' => '青绿', 'value' => '16b597'],
|
||||
['label' => '翠绿', 'value' => '36af18'],
|
||||
['label' => '黄绿', 'value' => '8fb107'],
|
||||
['label' => '棕金', 'value' => 'b18c07'],
|
||||
['label' => '橘橙', 'value' => 'e06711'],
|
||||
['label' => '朱红', 'value' => 'f74735'],
|
||||
]],
|
||||
['key' => 'primary_color', 'label' => '自定义主题色', 'type' => 'color', 'default' => '#f04494'],
|
||||
['key' => 'dark_primary_color', 'label' => '暗色模式主题色', 'type' => 'color', 'default' => '#f04494'],
|
||||
['key' => 'border_radius', 'label' => '全局圆角', 'type' => 'slider', 'default' => 8, 'min' => 0, 'max' => 20],
|
||||
['key' => 'theme_main_radius', 'label' => '卡片圆角 (px)', 'type' => 'number', 'default' => 12, 'min' => 0, 'max' => 30, 'help' => '独立卡片圆角,不填则使用全局圆角 +4'],
|
||||
['key' => 'close_backdrop', 'label' => '关闭毛玻璃效果', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'layout', 'title' => '布局设置', 'fields' => [
|
||||
['key' => 'container_max_width', 'label' => '内容最大宽度', 'type' => 'number', 'default' => 1200, 'min' => 0, 'max' => 1600],
|
||||
['key' => 'sidebar_enabled', 'label' => '开启侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_position', 'label' => '侧边栏位置', 'type' => 'radio', 'default' => 'right', 'options' => [
|
||||
['label' => '左侧', 'value' => 'left'],
|
||||
['label' => '右侧', 'value' => 'right'],
|
||||
]],
|
||||
['key' => 'sidebar_width', 'label' => '侧边栏宽度', 'type' => 'number', 'default' => 300, 'min' => 200, 'max' => 400],
|
||||
['key' => 'sidebar_home_enabled', 'label' => '首页侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_category_enabled', 'label' => '分类页侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_tag_enabled', 'label' => '标签页侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_single_enabled', 'label' => '文章页侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_page_enabled', 'label' => '页面侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sidebar_search_enabled', 'label' => '搜索页侧边栏', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'typography', 'title' => '字体设置', 'fields' => [
|
||||
['key' => 'font_family', 'label' => '正文字体', 'type' => 'select', 'default' => 'system', 'options' => [
|
||||
['label' => '系统默认', 'value' => 'system'],
|
||||
['label' => '思源黑体', 'value' => 'noto-sans-sc'],
|
||||
['label' => '思源宋体', 'value' => 'noto-serif-sc'],
|
||||
['label' => '自定义', 'value' => 'custom'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'performance', 'title' => '性能优化', 'fields' => [
|
||||
['key' => 'qj_loading', 'label' => '全站加载动画', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'qj_dh_xs', 'label' => '加载动画样式', 'type' => 'select', 'default' => 'no1', 'options' => [
|
||||
['label' => '样式 1', 'value' => 'no1'],
|
||||
['label' => '样式 2', 'value' => 'no2'],
|
||||
['label' => '样式 3', 'value' => 'no3'],
|
||||
['label' => '样式 4', 'value' => 'no4'],
|
||||
['label' => '样式 5', 'value' => 'no5'],
|
||||
['label' => '样式 6', 'value' => 'no6'],
|
||||
['label' => '样式 7', 'value' => 'no7'],
|
||||
['label' => '样式 8', 'value' => 'no8'],
|
||||
['label' => '样式 9', 'value' => 'no9'],
|
||||
['label' => '样式 10', 'value' => 'no10'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'avatar', 'title' => '头像设置', 'fields' => [
|
||||
['key' => 'avatar_source', 'label' => '头像服务源', 'type' => 'select', 'default' => 'cn.cravatar.com', 'options' => [
|
||||
['label' => 'Cravatar (国内推荐)', 'value' => 'cn.cravatar.com'],
|
||||
['label' => 'Gravatar 国内镜像', 'value' => 'cn.gravatar.com'],
|
||||
['label' => 'Gravatar 官方', 'value' => 'gravatar.com'],
|
||||
], 'help' => '国内站点建议优先选 Cravatar'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'code-highlight' => [
|
||||
'key' => 'code-highlight',
|
||||
'title' => '代码高亮',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '代码高亮设置', 'fields' => [
|
||||
['key' => 'highlight_kg', 'label' => '代码高亮开关', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'highlight_hh', 'label' => '显示行号', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'highlight_btn', 'label' => '显示扩展按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'highlight_maxheight', 'label' => '最大高度(px,0=不限)', 'type' => 'number', 'default' => 400, 'min' => 0, 'max' => 2000],
|
||||
]],
|
||||
['key' => 'theme', 'title' => '主题设置', 'fields' => [
|
||||
['key' => 'highlight_zt', 'label' => '日间默认主题', 'type' => 'select', 'default' => 'enlighter', 'options' => [
|
||||
['label' => '浅色: Enlighter', 'value' => 'enlighter'],
|
||||
['label' => '浅色: Bootstrap', 'value' => 'bootstrap4'],
|
||||
['label' => '浅色: Classic', 'value' => 'classic'],
|
||||
['label' => '浅色: Beyond', 'value' => 'beyond'],
|
||||
['label' => '浅色: Mowtwo', 'value' => 'mowtwo'],
|
||||
['label' => '浅色: Eclipse', 'value' => 'eclipse'],
|
||||
['label' => '浅色: Droide', 'value' => 'droide'],
|
||||
['label' => '浅色: Minimal', 'value' => 'minimal'],
|
||||
['label' => '浅色: Rowhammer', 'value' => 'rowhammer'],
|
||||
['label' => '浅色: Godzilla', 'value' => 'godzilla'],
|
||||
['label' => '深色: Dracula', 'value' => 'dracula'],
|
||||
['label' => '深色: Atomic', 'value' => 'atomic'],
|
||||
['label' => '深色: Monokai', 'value' => 'monokai'],
|
||||
]],
|
||||
['key' => 'highlight_dark_zt', 'label' => '暗色默认主题', 'type' => 'select', 'default' => 'monokai', 'options' => [
|
||||
['label' => '深色: Dracula', 'value' => 'dracula'],
|
||||
['label' => '深色: Atomic', 'value' => 'atomic'],
|
||||
['label' => '深色: Monokai', 'value' => 'monokai'],
|
||||
['label' => '浅色: Enlighter', 'value' => 'enlighter'],
|
||||
['label' => '浅色: Bootstrap', 'value' => 'bootstrap4'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'system-notice' => [
|
||||
'key' => 'system-notice',
|
||||
'title' => '系统公告',
|
||||
'version' => '2.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '公告设置', 'fields' => [
|
||||
['key' => 'system_notice_enabled', 'label' => '启用系统公告', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'system_notice_policy', 'label' => '显示对象', 'type' => 'select', 'default' => '', 'options' => [
|
||||
['label' => '所有访客', 'value' => ''],
|
||||
['label' => '仅登录用户', 'value' => 'signin'],
|
||||
['label' => '仅 VIP 会员', 'value' => 'vip'],
|
||||
['label' => '仅非 VIP', 'value' => 'free'],
|
||||
['label' => '仅认证用户', 'value' => 'auth'],
|
||||
]],
|
||||
['key' => 'system_notice_title', 'label' => '公告标题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_content', 'label' => '公告内容', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'buttons', 'title' => '按钮组(留空则不显示)', 'fields' => [
|
||||
['key' => 'system_notice_btn1_text', 'label' => '按钮 1 文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn1_link', 'label' => '按钮 1 链接', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn1_color', 'label' => '按钮 1 颜色', 'type' => 'select', 'default' => 'primary', 'options' => [
|
||||
['label' => '主题色', 'value' => 'primary'],
|
||||
['label' => '成功绿', 'value' => 'success'],
|
||||
['label' => '警告橙', 'value' => 'warning'],
|
||||
['label' => '危险红', 'value' => 'danger'],
|
||||
['label' => '信息蓝', 'value' => 'info'],
|
||||
['label' => '深紫', 'value' => 'purple'],
|
||||
['label' => '深灰', 'value' => 'dark'],
|
||||
]],
|
||||
['key' => 'system_notice_btn2_text', 'label' => '按钮 2 文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn2_link', 'label' => '按钮 2 链接', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn2_color', 'label' => '按钮 2 颜色', 'type' => 'select', 'default' => 'primary', 'options' => [
|
||||
['label' => '主题色', 'value' => 'primary'],
|
||||
['label' => '成功绿', 'value' => 'success'],
|
||||
['label' => '警告橙', 'value' => 'warning'],
|
||||
['label' => '危险红', 'value' => 'danger'],
|
||||
['label' => '信息蓝', 'value' => 'info'],
|
||||
['label' => '深紫', 'value' => 'purple'],
|
||||
['label' => '深灰', 'value' => 'dark'],
|
||||
]],
|
||||
['key' => 'system_notice_btn3_text', 'label' => '按钮 3 文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn3_link', 'label' => '按钮 3 链接', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn3_color', 'label' => '按钮 3 颜色', 'type' => 'select', 'default' => 'primary', 'options' => [
|
||||
['label' => '主题色', 'value' => 'primary'],
|
||||
['label' => '成功绿', 'value' => 'success'],
|
||||
['label' => '警告橙', 'value' => 'warning'],
|
||||
['label' => '危险红', 'value' => 'danger'],
|
||||
['label' => '信息蓝', 'value' => 'info'],
|
||||
['label' => '深紫', 'value' => 'purple'],
|
||||
['label' => '深灰', 'value' => 'dark'],
|
||||
]],
|
||||
['key' => 'system_notice_btn4_text', 'label' => '按钮 4 文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn4_link', 'label' => '按钮 4 链接', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_btn4_color', 'label' => '按钮 4 颜色', 'type' => 'select', 'default' => 'primary', 'options' => [
|
||||
['label' => '主题色', 'value' => 'primary'],
|
||||
['label' => '成功绿', 'value' => 'success'],
|
||||
['label' => '警告橙', 'value' => 'warning'],
|
||||
['label' => '危险红', 'value' => 'danger'],
|
||||
['label' => '信息蓝', 'value' => 'info'],
|
||||
['label' => '深紫', 'value' => 'purple'],
|
||||
['label' => '深灰', 'value' => 'dark'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'style', 'title' => '样式设置', 'fields' => [
|
||||
['key' => 'system_notice_size', 'label' => '弹窗尺寸', 'type' => 'select', 'default' => '', 'options' => [
|
||||
['label' => '默认', 'value' => ''],
|
||||
['label' => '迷你', 'value' => 'modal-mini'],
|
||||
['label' => '小号', 'value' => 'modal-sm'],
|
||||
['label' => '大号', 'value' => 'modal-lg'],
|
||||
]],
|
||||
['key' => 'system_notice_radius', 'label' => '按钮圆角', 'type' => 'switch', 'default' => false, 'help' => '开启后弹窗按钮使用圆角样式'],
|
||||
['key' => 'system_notice_class', 'label' => '公告样式类', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'system_notice_title_icon', 'label' => '标题图标', 'type' => 'icon', 'default' => ''],
|
||||
['key' => 'system_notice_title_class', 'label' => '标题背景配色', 'type' => 'select', 'default' => '', 'options' => [
|
||||
['label' => '无', 'value' => ''],
|
||||
['label' => '炫彩黄', 'value' => 'jb-yellow'],
|
||||
['label' => '炫彩蓝', 'value' => 'jb-blue'],
|
||||
['label' => '炫彩绿', 'value' => 'jb-green'],
|
||||
['label' => '炫彩紫', 'value' => 'jb-purple'],
|
||||
['label' => '炫彩红', 'value' => 'jb-red'],
|
||||
['label' => '炫彩橙', 'value' => 'jb-orange'],
|
||||
], 'help' => '弹窗标题区域的背景配色方案'],
|
||||
['key' => 'system_notice_title_style', 'label' => '标题样式', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'expire', 'title' => '过期设置', 'fields' => [
|
||||
['key' => 'system_notice_expires', 'label' => '弹窗周期(小时)', 'type' => 'number', 'default' => 24, 'min' => 0, 'max' => 720, 'help' => '0 表示永不过期'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'search' => [
|
||||
'key' => 'search',
|
||||
'title' => '搜索功能',
|
||||
'version' => '2.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '搜索基础', 'fields' => [
|
||||
['key' => 'search_mode', 'label' => '搜索模式', 'type' => 'radio', 'default' => 'default', 'options' => [
|
||||
['label' => '默认搜索', 'value' => 'default'],
|
||||
['label' => 'AJAX 实时搜索', 'value' => 'ajax'],
|
||||
['label' => '跳转搜索', 'value' => 'redirect'],
|
||||
]],
|
||||
['key' => 'search_placeholder', 'label' => '搜索框提示文字', 'type' => 'input', 'default' => '开启精彩搜索'],
|
||||
]],
|
||||
['key' => 'content-types', 'title' => '搜索内容类型', 'fields' => [
|
||||
['key' => 'search_type_post', 'label' => '文章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'search_type_user', 'label' => '用户', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'search_type_category', 'label' => '分类', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'search_type_tag', 'label' => '标签', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'search_type_product', 'label' => '商品', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'search_type_topic', 'label' => '帖子', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'search_default_type', 'label' => '默认类型', 'type' => 'select', 'default' => 'post', 'options' => [
|
||||
['label' => '文章', 'value' => 'post'],
|
||||
['label' => '用户', 'value' => 'user'],
|
||||
['label' => '分类', 'value' => 'category'],
|
||||
['label' => '标签', 'value' => 'tag'],
|
||||
['label' => '商品', 'value' => 'product'],
|
||||
['label' => '帖子', 'value' => 'topic'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'features', 'title' => '搜索功能', 'fields' => [
|
||||
['key' => 'search_history_enabled', 'label' => '搜索历史', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'hot_search_enabled', 'label' => '热门搜索', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'hot_search_keywords', 'label' => '热门搜索关键词', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'search_suggestions_enabled', 'label' => '搜索建议', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'search_posts', 'label' => '搜索框热门文章', 'type' => 'switch', 'default' => false, 'help' => '在搜索面板中显示热门文章列表'],
|
||||
['key' => 'search_no_page', 'label' => '排除页面', 'type' => 'switch', 'default' => false, 'help' => '搜索结果中排除页面(仅显示文章)'],
|
||||
['key' => 'search_exclude_cats', 'label' => '排除分类', 'type' => 'textarea', 'default' => '', 'help' => '搜索结果中排除的分类 ID(逗号分隔)'],
|
||||
['key' => 'search_custom_key_s', 'label' => '自定义匹配范围', 'type' => 'switch', 'default' => false, 'help' => '启用后可配置搜索匹配字段范围(标题/内容/分类/标签等)'],
|
||||
['key' => 'search_custom_key', 'label' => '匹配字段', 'type' => 'textarea', 'default' => 'title,content', 'help' => '搜索匹配的字段范围(逗号分隔:title/content/excerpt/category/tag/author)'],
|
||||
['key' => 'search_facets', 'label' => '搜索结果筛选', 'type' => 'switch', 'default' => false, 'help' => '启用搜索结果 taxonomy 筛选面板'],
|
||||
['key' => 'search_facets_order', 'label' => '筛选排序', 'type' => 'textarea', 'default' => "category\ntag", 'help' => '筛选 fieldset 显示顺序(每行一个 taxonomy slug)'],
|
||||
]],
|
||||
['key' => 'category-search', 'title' => '分类搜索', 'fields' => [
|
||||
['key' => 'search_cat', 'label' => '启用分类搜索', 'type' => 'switch', 'default' => false, 'help' => '在搜索框旁显示分类选择器'],
|
||||
['key' => 'search_cat_in', 'label' => '默认分类', 'type' => 'input', 'default' => '', 'help' => '默认搜索的分类 ID(留空为全部分类)'],
|
||||
['key' => 'search_more_cat_obj', 'label' => '显示更多分类', 'type' => 'switch', 'default' => true, 'help' => '展开显示全部分类列表'],
|
||||
]],
|
||||
['key' => 'popular', 'title' => '热门搜索', 'fields' => [
|
||||
['key' => 'search_popular_title', 'label' => '标题', 'type' => 'input', 'default' => '热门搜索'],
|
||||
['key' => 'search_popular_key_num', 'label' => '显示数量', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 50],
|
||||
['key' => 'search_popular_sticky', 'label' => '置顶词(每行一个)', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'meilisearch', 'title' => 'Meilisearch 集成', 'fields' => [
|
||||
['key' => 'search_ms_enabled', 'label' => '启用 Meilisearch', 'type' => 'switch', 'default' => false, 'help' => '启用后搜索请求将发送至 Meilisearch 服务器'],
|
||||
['key' => 'search_ms_api_url', 'label' => 'API 地址', 'type' => 'input', 'default' => '', 'help' => 'Meilisearch 服务器 URL'],
|
||||
['key' => 'search_ms_api_key', 'label' => 'API Key', 'type' => 'input', 'default' => '', 'help' => 'Meilisearch Search API Key(只读密钥)'],
|
||||
['key' => 'search_ms_index_prefix', 'label' => '索引前缀', 'type' => 'input', 'default' => 'aksrt', 'help' => '索引名称前缀'],
|
||||
['key' => 'search_ms_synonyms', 'label' => '同义词(JSON)', 'type' => 'textarea', 'default' => '', 'help' => 'Meilisearch 同义词配置 JSON'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'email' => [
|
||||
'key' => 'email',
|
||||
'title' => '邮件事件',
|
||||
'version' => '2.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'global', 'title' => '全局设置', 'fields' => [
|
||||
['key' => 'email_footer_text', 'label' => '邮件页脚文字', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_site_branding', 'label' => '显示站点品牌', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_template_mode', 'label' => '邮件格式', 'type' => 'radio', 'default' => 'plain', 'options' => [
|
||||
['label' => '纯文本', 'value' => 'plain'],
|
||||
['label' => 'HTML', 'value' => 'html'],
|
||||
]],
|
||||
['key' => 'email_private_receive_limit', 'label' => '私信邮件接收组', 'type' => 'select', 'default' => 'all', 'options' => [
|
||||
['label' => '所有用户', 'value' => 'all'],
|
||||
['label' => 'VIP 及以上', 'value' => 'vip'],
|
||||
['label' => 'VIP2 及以上', 'value' => 'vip2'],
|
||||
['label' => '仅管理员', 'value' => 'admin'],
|
||||
], 'help' => '限制哪些用户组可以收到私信邮件通知'],
|
||||
]],
|
||||
['key' => 'branding', 'title' => '邮件品牌', 'fields' => [
|
||||
['key' => 'mail_showname', 'label' => '发件人名称', 'type' => 'input', 'default' => '', 'help' => '覆盖系统默认发件人名称'],
|
||||
['key' => 'mail_title_prefix', 'label' => '主题前缀', 'type' => 'input', 'default' => '', 'help' => '自动添加在所有邮件主题前面,如【站点名】'],
|
||||
['key' => 'mail_description', 'label' => '邮件底部说明', 'type' => 'textarea', 'default' => '', 'help' => '附加在邮件页脚文字之后,留空不显示'],
|
||||
['key' => 'mail_more_content', 'label' => '邮件附加内容', 'type' => 'textarea', 'default' => '', 'help' => '附加在邮件内容末尾的补充说明'],
|
||||
]],
|
||||
['key' => 'reg_verify_link', 'title' => '注册验证链接', 'fields' => [
|
||||
['key' => 'email_reg_verify_link_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_reg_verify_link_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_reg_verify_link_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_reg_verify_link_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'reg_verify_code', 'title' => '注册验证码', 'fields' => [
|
||||
['key' => 'email_reg_verify_code_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_reg_verify_code_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_reg_verify_code_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_reg_verify_code_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'reg_success', 'title' => '注册成功通知', 'fields' => [
|
||||
['key' => 'email_reg_success_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_reg_success_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_reg_success_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_reg_success_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'password_reset', 'title' => '密码重置', 'fields' => [
|
||||
['key' => 'email_password_reset_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_password_reset_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_password_reset_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_password_reset_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'change_code', 'title' => '邮箱变更验证码', 'fields' => [
|
||||
['key' => 'email_change_code_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_change_code_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_change_code_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_change_code_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'change_notice', 'title' => '邮箱变更通知', 'fields' => [
|
||||
['key' => 'email_change_notice_enabled', 'label' => '启用', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'email_change_notice_subject', 'label' => '主题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'email_change_notice_content', 'label' => '内容模板', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'email_change_notice_html', 'label' => 'HTML 格式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'custom-code' => [
|
||||
'key' => 'custom-code',
|
||||
'title' => '自定义代码',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'css', 'title' => '自定义 CSS', 'fields' => [
|
||||
['key' => 'custom_css', 'label' => '自定义 CSS', 'type' => 'code', 'language' => 'css', 'default' => ''],
|
||||
]],
|
||||
['key' => 'code', 'title' => '头部 & 底部代码', 'fields' => [
|
||||
['key' => 'head_code', 'label' => '头部 HTML 代码', 'type' => 'code', 'default' => '', 'help' => '在 <head> 中输出,支持 meta/link/style/script 等任意 HTML'],
|
||||
['key' => 'foot_code', 'label' => '底部 HTML 代码', 'type' => 'code', 'default' => '', 'help' => '在 </body> 前输出,支持任意 HTML'],
|
||||
]],
|
||||
['key' => 'stats', 'title' => '统计代码', 'fields' => [
|
||||
['key' => 'trackcode', 'label' => '统计代码', 'type' => 'code', 'default' => '', 'help' => '百度统计/GA/CNZZ/51la 等,自动在 head 中输出'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'i18n' => [
|
||||
'key' => 'i18n',
|
||||
'title' => '多语言翻译',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础配置', 'fields' => [
|
||||
['key' => 'i18n_enabled', 'label' => '启用多语言', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'i18n_default_lang', 'label' => '默认语言', 'type' => 'select', 'default' => 'zh-CN', 'options' => [
|
||||
['label' => '简体中文', 'value' => 'zh-CN'],
|
||||
['label' => 'English', 'value' => 'en'],
|
||||
['label' => '繁體中文', 'value' => 'zh-TW'],
|
||||
['label' => '日本語', 'value' => 'ja'],
|
||||
['label' => '한국어', 'value' => 'ko'],
|
||||
['label' => 'Français', 'value' => 'fr'],
|
||||
['label' => 'Deutsch', 'value' => 'de'],
|
||||
['label' => 'Español', 'value' => 'es'],
|
||||
['label' => 'Русский', 'value' => 'ru'],
|
||||
]],
|
||||
['key' => 'i18n_auto_detect', 'label' => '自动检测浏览器语言', 'type' => 'switch', 'default' => true, 'help' => '基于浏览器 Accept-Language 自动切换(AKSRT 方案,区别于 zibll IP 定位方案)'],
|
||||
]],
|
||||
['key' => 'languages', 'title' => '语言管理', 'fields' => [
|
||||
['key' => 'i18n_available_langs', 'label' => '可用语言列表', 'type' => 'textarea', 'default' => "zh-CN|简体中文\nen|English\nzh-TW|繁體中文\nja|日本語\nko|한국어\nfr|Français\nde|Deutsch\nes|Español\nru|Русский", 'help' => '每行一个语言,格式:代码|名称(9 种内置语言)'],
|
||||
['key' => 'i18n_nomenclature', 'label' => '自定义术语', 'type' => 'code', 'default' => '', 'help' => 'JSON 格式自定义翻译术语(key => 翻译),对齐 zibll nomenclature'],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'i18n_switcher_position', 'label' => '语言切换器位置', 'type' => 'radio', 'default' => 'footer', 'options' => [
|
||||
['label' => '导航栏', 'value' => 'header'],
|
||||
['label' => '页脚', 'value' => 'footer'],
|
||||
['label' => '导航栏+页脚', 'value' => 'both'],
|
||||
['label' => '隐藏', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'i18n_url_strategy', 'label' => 'URL 策略', 'type' => 'radio', 'default' => 'query', 'options' => [
|
||||
['label' => '查询参数', 'value' => 'query'],
|
||||
['label' => '路径前缀', 'value' => 'path'],
|
||||
['label' => '子域名', 'value' => 'subdomain'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Page_Display schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Page_Display {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'page_display',
|
||||
'title' => '页面&显示',
|
||||
'sections' => [
|
||||
'header-nav' => [
|
||||
'key' => 'header-nav',
|
||||
'title' => '顶部导航栏',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '导航栏布局', 'fields' => [
|
||||
['key' => 'header_fixed', 'label' => '固定导航栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_nav_style', 'label' => '导航栏风格', 'type' => 'radio', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '透明', 'value' => 'transparent'],
|
||||
['label' => '毛玻璃', 'value' => 'blur'],
|
||||
]],
|
||||
['key' => 'header_max_width', 'label' => '导航栏最大宽度', 'type' => 'number', 'default' => 1200, 'min' => 0, 'max' => 1600],
|
||||
['key' => 'header_scroll_down_hide', 'label' => '下滑隐藏导航栏', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'components', 'title' => '导航组件', 'fields' => [
|
||||
['key' => 'header_search_enabled', 'label' => '搜索入口', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_user_menu', 'label' => '用户菜单', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_publish_btn', 'label' => '发布按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_mobile_drawer', 'label' => '移动端抽屉导航', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'newposts', 'title' => '新文章提示', 'fields' => [
|
||||
['key' => 'header_newposts_enabled', 'label' => '显示新文章提示', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_newposts_text', 'label' => '提示文案', 'type' => 'input', 'default' => '有新文章'],
|
||||
['key' => 'header_newposts_radius', 'label' => '圆角', 'type' => 'number', 'default' => 12, 'min' => 0, 'max' => 30],
|
||||
['key' => 'header_newposts_class', 'label' => '样式类', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'color', 'title' => '颜色设置', 'fields' => [
|
||||
['key' => 'header_theme_custom', 'label' => '自定义导航栏颜色', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_theme_bg_custom', 'label' => '自定义背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'header_theme_bg', 'label' => '背景颜色(备选)', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'header_theme_color_custom', 'label' => '自定义文字颜色', 'type' => 'color', 'default' => ''],
|
||||
]],
|
||||
['key' => 'layout-type', 'title' => '布局类型', 'fields' => [
|
||||
['key' => 'header_layout', 'label' => '桌面端布局', 'type' => 'radio', 'default' => '2', 'options' => [
|
||||
['label' => '布局 1(经典)', 'value' => '1'],
|
||||
['label' => '布局 2(内联搜索)', 'value' => '2'],
|
||||
['label' => '布局 3(紧凑)', 'value' => '3'],
|
||||
]],
|
||||
['key' => 'mobile_header_layout', 'label' => '移动端布局', 'type' => 'radio', 'default' => 'center', 'options' => [
|
||||
['label' => '居中(含搜索按钮)', 'value' => 'center'],
|
||||
['label' => '靠左', 'value' => 'left'],
|
||||
]],
|
||||
['key' => 'mobile_navbar_align', 'label' => '移动端菜单方向', 'type' => 'radio', 'default' => 'left', 'options' => [
|
||||
['label' => '顶部(关闭手势)', 'value' => 'top'],
|
||||
['label' => '左侧滑出', 'value' => 'left'],
|
||||
['label' => '右侧滑出', 'value' => 'right'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'header-components' => [
|
||||
'key' => 'header-components',
|
||||
'title' => '顶部多功能组件',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'visibility', 'title' => '显示规则', 'fields' => [
|
||||
['key' => 'header_components_search_enabled', 'label' => '启用搜索覆盖层', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_components_cards_enabled', 'label' => '启用图标卡片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_components_show_on', 'label' => '按页面类型启用', 'type' => 'checkbox', 'default' => ['home'], 'options' => [
|
||||
['label' => '首页', 'value' => 'home'],
|
||||
['label' => '文章', 'value' => 'single'],
|
||||
['label' => '页面', 'value' => 'page'],
|
||||
['label' => '分类', 'value' => 'category'],
|
||||
['label' => '标签', 'value' => 'tag'],
|
||||
['label' => '搜索', 'value' => 'search'],
|
||||
['label' => '归档', 'value' => 'archive'],
|
||||
['label' => '专题', 'value' => 'special'],
|
||||
]],
|
||||
['key' => 'header_components_device_visibility', 'label' => '设备显示规则', 'type' => 'radio', 'default' => '', 'options' => [
|
||||
['label' => '全部', 'value' => ''],
|
||||
['label' => '仅电脑', 'value' => 'only_pc'],
|
||||
['label' => '仅移动', 'value' => 'only_sm'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'nav-color', 'title' => '导航栏配色', 'fields' => [
|
||||
['key' => 'header_components_nav_color', 'label' => '导航栏文字颜色', 'type' => 'color', 'default' => '#ffffff'],
|
||||
]],
|
||||
['key' => 'slider-config', 'title' => '幻灯片配置', 'fields' => [
|
||||
['key' => 'slider_config_direction', 'label' => '滑动方向', 'type' => 'radio', 'default' => 'horizontal', 'options' => [
|
||||
['label' => '水平', 'value' => 'horizontal'],
|
||||
['label' => '垂直', 'value' => 'vertical'],
|
||||
]],
|
||||
['key' => 'slider_config_speed', 'label' => '切换速度(ms)', 'type' => 'number', 'default' => 300, 'min' => 100, 'max' => 3000],
|
||||
['key' => 'slider_config_button', 'label' => '显示前进后退按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'slider_config_scale_height', 'label' => '缩放高度', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'slider_config_scale', 'label' => '缩放效果', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'slider_config_max_height', 'label' => '最大高度', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'slider_config_min_height', 'label' => '最小高度', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'search', 'title' => '搜索组件', 'fields' => [
|
||||
['key' => 'header_components_search_pc', 'label' => 'PC 端显示搜索', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_components_search_mobile', 'label' => '移动端显示搜索', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_components_search_show_keywords', 'label' => '显示热门搜索词', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_components_search_popular_limit', 'label' => '热词数量', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 20],
|
||||
['key' => 'header_components_search_show_input_cat', 'label' => '显示分类搜索', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_components_search_show_type', 'label' => '显示类型切换', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'cards', 'title' => '图标卡片样式', 'fields' => [
|
||||
['key' => 'header_components_filter_blur_devices', 'label' => '容器模糊设备', 'type' => 'checkbox', 'default' => [], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'header_components_card_show_devices', 'label' => '卡片显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'header_components_card_shared_bg', 'label' => '共享卡片背景', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'header_components_card_blur', 'label' => '启用卡片模糊效果', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'header_components_card_icon_shape', 'label' => '图标形状', 'type' => 'radio', 'default' => 'circle', 'options' => [
|
||||
['label' => '圆形', 'value' => 'circle'],
|
||||
['label' => '方形', 'value' => 'square'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'icon-cards', 'title' => '图标卡片列表', 'fields' => [
|
||||
['key' => 'header_components_icon_cards', 'label' => '图标卡片', 'type' => 'repeater', 'sanitize_type' => 'array', 'default' => []],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'category' => [
|
||||
'key' => 'category',
|
||||
'title' => '分类页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'header', 'title' => '分类头图', 'fields' => [
|
||||
['key' => 'category_header_style', 'label' => '头部风格', 'type' => 'radio', 'default' => 'cover', 'options' => [
|
||||
['label' => '封面图', 'value' => 'cover'],
|
||||
['label' => '简洁标题', 'value' => 'simple'],
|
||||
['label' => '隐藏', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'category_cover_enabled', 'label' => '显示封面图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_desc_enabled', 'label' => '显示分类简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_default_cover', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'list', 'title' => '列表设置', 'fields' => [
|
||||
['key' => 'category_list_mode', 'label' => '列表模式', 'type' => 'radio', 'default' => 'grid', 'options' => [
|
||||
['label' => '网格', 'value' => 'grid'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
['label' => '紧凑', 'value' => 'compact'],
|
||||
]],
|
||||
['key' => 'category_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
['key' => 'category_filter_bar', 'label' => '筛选条', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'category_post_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'orderby', 'title' => '排序设置', 'fields' => [
|
||||
['key' => 'category_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'modified', 'options' => [
|
||||
['label' => '最近更新', 'value' => 'modified'],
|
||||
['label' => '发布时间', 'value' => 'date'],
|
||||
['label' => '浏览数', 'value' => 'views'],
|
||||
['label' => '评论数', 'value' => 'comments'],
|
||||
['label' => '点赞数', 'value' => 'likes'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'tag' => [
|
||||
'key' => 'tag',
|
||||
'title' => '标签页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'header', 'title' => '标签头图', 'fields' => [
|
||||
['key' => 'tag_header_style', 'label' => '头部风格', 'type' => 'radio', 'default' => 'simple', 'options' => [
|
||||
['label' => '简洁标题', 'value' => 'simple'],
|
||||
['label' => '封面图', 'value' => 'cover'],
|
||||
['label' => '隐藏', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'tag_default_cover', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'list', 'title' => '列表设置', 'fields' => [
|
||||
['key' => 'tag_list_mode', 'label' => '列表模式', 'type' => 'radio', 'default' => 'grid', 'options' => [
|
||||
['label' => '网格', 'value' => 'grid'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
]],
|
||||
['key' => 'tag_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
['key' => 'tag_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'tag_post_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'orderby', 'title' => '排序设置', 'fields' => [
|
||||
['key' => 'tag_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'count', 'options' => [
|
||||
['label' => '文章数', 'value' => 'count'],
|
||||
['label' => '名称', 'value' => 'name'],
|
||||
['label' => '最近更新', 'value' => 'modified'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'user-home' => [
|
||||
'key' => 'user-home',
|
||||
'title' => '用户主页',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '主页布局', 'fields' => [
|
||||
['key' => 'user_home_cover', 'label' => '封面区域', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_follow', 'label' => '关注按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_posts_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 10, 'min' => 5, 'max' => 50],
|
||||
['key' => 'user_home_paging_ajax', 'label' => 'AJAX 翻页', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'tabs', 'title' => 'Tab 配置', 'fields' => [
|
||||
['key' => 'user_home_tabs', 'label' => '显示的 Tab', 'type' => 'input', 'default' => 'posts,comments,favorites,credits,product,plate,forum,follow'],
|
||||
['key' => 'user_home_tabs_order', 'label' => 'Tab 显示顺序', 'type' => 'input', 'default' => 'posts,comments,favorites,credits,product,plate,forum,follow'],
|
||||
['key' => 'user_home_tabs_pc_hide', 'label' => 'PC 端隐藏', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_home_tabs_mobile_hide', 'label' => '移动端隐藏', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'author_main_tab_swiper', 'label' => '移动端滑动切换', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'user_home_avatar', 'label' => '显示头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_name', 'label' => '显示用户名', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_desc', 'label' => '显示个人简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_stats', 'label' => '显示统计数据', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_social', 'label' => '显示社交链接', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_message', 'label' => '私信按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_home_private', 'label' => '留言按钮', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'other' => [
|
||||
'key' => 'other',
|
||||
'title' => '其它页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'page-header', 'title' => '统一页头', 'fields' => [
|
||||
['key' => 'page_header_style', 'label' => '页面标题样式', 'type' => 'select', 'default' => '1', 'options' => [
|
||||
['label' => '不显示', 'value' => ''],
|
||||
['label' => '简单', 'value' => '1'],
|
||||
['label' => '卡片', 'value' => '2'],
|
||||
['label' => '封面', 'value' => '3'],
|
||||
]],
|
||||
['key' => 'page_header_cover_img', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'page_404', 'title' => '404 页面', 'fields' => [
|
||||
['key' => 'page_404_message', 'label' => '404 提示文字', 'type' => 'input', 'default' => '页面未找到'],
|
||||
['key' => 'page_404_search', 'label' => '显示搜索框', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_404_popular', 'label' => '显示热门文章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_404_back', 'label' => '显示返回首页', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'search_results', 'title' => '搜索结果页', 'fields' => [
|
||||
['key' => 'page_search_sidebar', 'label' => '搜索结果侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_search_per_page', 'label' => '每页结果数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
]],
|
||||
['key' => 'author', 'title' => '作者页面', 'fields' => [
|
||||
['key' => 'page_author_cover', 'label' => '显示封面', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_author_sidebar', 'label' => '显示侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'page_author_posts_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'special' => [
|
||||
'key' => 'special',
|
||||
'title' => '专题页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'header', 'title' => '专题头部', 'fields' => [
|
||||
['key' => 'special_cover_enabled', 'label' => '显示专题封面', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'special_summary_enabled', 'label' => '显示专题摘要', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'special_summary_length', 'label' => '摘要最大长度', 'type' => 'number', 'default' => 200, 'min' => 50, 'max' => 500],
|
||||
['key' => 'special_default_cover', 'label' => '默认封面图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'layout', 'title' => '布局设置', 'fields' => [
|
||||
['key' => 'special_layout', 'label' => '内容布局', 'type' => 'radio', 'default' => 'grid', 'options' => [
|
||||
['label' => '网格', 'value' => 'grid'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
['label' => '瀑布流', 'value' => 'masonry'],
|
||||
]],
|
||||
['key' => 'special_columns', 'label' => '网格列数', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 4],
|
||||
['key' => 'special_sidebar', 'label' => '侧边栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'special_post_count', 'label' => '显示文章数', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'aggregation', 'title' => '聚合规则', 'fields' => [
|
||||
['key' => 'special_source_type', 'label' => '内容来源', 'type' => 'radio', 'default' => 'category', 'options' => [
|
||||
['label' => '分类', 'value' => 'category'],
|
||||
['label' => '标签', 'value' => 'tag'],
|
||||
['label' => '手动选择', 'value' => 'manual'],
|
||||
['label' => '最新文章', 'value' => 'latest'],
|
||||
]],
|
||||
['key' => 'special_source_ids', 'label' => '来源 ID 列表', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'subpage', 'title' => '子页面展示', 'fields' => [
|
||||
['key' => 'special_subpage_display', 'label' => '子页面样式', 'type' => 'radio', 'default' => 'cards', 'options' => [
|
||||
['label' => '卡片', 'value' => 'cards'],
|
||||
['label' => '列表', 'value' => 'list'],
|
||||
['label' => '时间线', 'value' => 'timeline'],
|
||||
]],
|
||||
['key' => 'special_subpage_count', 'label' => '每页子页面数', 'type' => 'number', 'default' => 6, 'min' => 1, 'max' => 24],
|
||||
]],
|
||||
['key' => 'pagination', 'title' => '分页设置', 'fields' => [
|
||||
['key' => 'special_pagination', 'label' => '分页模式', 'type' => 'radio', 'default' => 'numeric', 'options' => [
|
||||
['label' => '数字分页', 'value' => 'numeric'],
|
||||
['label' => '加载更多', 'value' => 'load_more'],
|
||||
['label' => '无限滚动', 'value' => 'infinite'],
|
||||
]],
|
||||
['key' => 'special_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
]],
|
||||
['key' => 'orderby', 'title' => '排序设置', 'fields' => [
|
||||
['key' => 'special_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'date', 'options' => [
|
||||
['label' => '发布时间', 'value' => 'date'],
|
||||
['label' => '最近更新', 'value' => 'modified'],
|
||||
['label' => '浏览数', 'value' => 'views'],
|
||||
['label' => '评论数', 'value' => 'comments'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'background' => [
|
||||
'key' => 'background',
|
||||
'title' => '全局背景',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'light', 'title' => '亮色背景', 'fields' => [
|
||||
['key' => 'bg_color', 'label' => '背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'bg_image', 'label' => '背景图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'bg_size', 'label' => '图片尺寸', 'type' => 'select', 'default' => 'cover', 'options' => [
|
||||
['label' => '覆盖', 'value' => 'cover'],
|
||||
['label' => '包含', 'value' => 'contain'],
|
||||
['label' => '原始大小', 'value' => 'auto'],
|
||||
['label' => '拉伸填充', 'value' => '100% 100%'],
|
||||
['label' => '100% 宽度(旧版兼容)', 'value' => '100%'],
|
||||
]],
|
||||
['key' => 'bg_position', 'label' => '图片位置', 'type' => 'select', 'default' => 'center center', 'options' => [
|
||||
['label' => '居中', 'value' => 'center center'],
|
||||
['label' => '顶部居中', 'value' => 'top center'],
|
||||
['label' => '底部居中', 'value' => 'bottom center'],
|
||||
]],
|
||||
['key' => 'bg_repeat', 'label' => '图片重复', 'type' => 'select', 'default' => 'no-repeat', 'options' => [
|
||||
['label' => '不重复', 'value' => 'no-repeat'],
|
||||
['label' => '平铺', 'value' => 'repeat'],
|
||||
['label' => '水平平铺', 'value' => 'repeat-x'],
|
||||
['label' => '垂直平铺', 'value' => 'repeat-y'],
|
||||
]],
|
||||
['key' => 'bg_attachment', 'label' => '固定方式', 'type' => 'select', 'default' => 'fixed', 'options' => [
|
||||
['label' => '固定', 'value' => 'fixed'],
|
||||
['label' => '随内容滚动', 'value' => 'scroll'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'dark', 'title' => '暗色背景', 'fields' => [
|
||||
['key' => 'bg_dark_color', 'label' => '暗色背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'bg_dark_image', 'label' => '暗色背景图片', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
['key' => 'overlay', 'title' => '遮罩设置', 'fields' => [
|
||||
['key' => 'bg_overlay', 'label' => '启用背景遮罩', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bg_overlay_color', 'label' => '遮罩颜色', 'type' => 'input', 'default' => 'rgba(0, 0, 0, 0.3)'],
|
||||
]],
|
||||
['key' => 'page', 'title' => '页面背景', 'fields' => [
|
||||
['key' => 'page_bg_color', 'label' => '内容区背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'page_bg_image', 'label' => '内容区背景图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'menu-decoration' => [
|
||||
'key' => 'menu-decoration',
|
||||
'title' => '菜单装饰',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'icon', 'title' => '菜单图标', 'fields' => [
|
||||
['key' => 'menu_icon_enabled', 'label' => '显示菜单图标', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'menu_badge_enabled', 'label' => '显示菜单徽章', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'submenu', 'title' => '下拉子菜单', 'fields' => [
|
||||
['key' => 'menu_submenu_style', 'label' => '子菜单样式', 'type' => 'radio', 'default' => 'dropdown', 'options' => [
|
||||
['label' => '普通下拉', 'value' => 'dropdown'],
|
||||
['label' => '多列下拉', 'value' => 'mega'],
|
||||
['label' => '图文卡片', 'value' => 'cards'],
|
||||
]],
|
||||
['key' => 'menu_submenu_columns', 'label' => '多列列数', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 5],
|
||||
['key' => 'menu_submenu_width', 'label' => '子菜单宽度 (px)', 'type' => 'number', 'default' => 220, 'min' => 160, 'max' => 800],
|
||||
]],
|
||||
['key' => 'effect', 'title' => '交互效果', 'fields' => [
|
||||
['key' => 'menu_hover_effect', 'label' => 'Hover 效果', 'type' => 'radio', 'default' => 'underline', 'options' => [
|
||||
['label' => '下划线', 'value' => 'underline'],
|
||||
['label' => '背景色', 'value' => 'background'],
|
||||
['label' => '文字变色', 'value' => 'color'],
|
||||
['label' => '无效果', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'menu_item_padding', 'label' => '菜单项内边距 (px)', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 32],
|
||||
]],
|
||||
['key' => 'mobile', 'title' => '移动端菜单', 'fields' => [
|
||||
['key' => 'menu_mobile_style', 'label' => '移动端菜单样式', 'type' => 'radio', 'default' => 'drawer', 'options' => [
|
||||
['label' => '抽屉', 'value' => 'drawer'],
|
||||
['label' => '全屏', 'value' => 'fullscreen'],
|
||||
['label' => '下拉', 'value' => 'dropdown'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'footer' => [
|
||||
'key' => 'footer',
|
||||
'title' => '底部页脚',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '页脚布局', 'fields' => [
|
||||
['key' => 'footer_columns', 'label' => '页脚列数', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 4],
|
||||
['key' => 'footer_copyright', 'label' => '显示版权信息', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_icp', 'label' => '显示ICP备案', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'icp_number', 'label' => 'ICP备案号', 'type' => 'input', 'default' => '', 'help' => '例如:ICP 备 xxxxxxxx 号'],
|
||||
['key' => 'footer_police', 'label' => '显示公安备案', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'police_number', 'label' => '公安备案号', 'type' => 'input', 'default' => '', 'help' => '例如:公网安备 xxxxxx 号'],
|
||||
]],
|
||||
['key' => 'color', 'title' => '页脚颜色', 'fields' => [
|
||||
['key' => 'footer_theme_custom', 'label' => '自定义页脚颜色', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_theme_bg_custom', 'label' => '自定义背景色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'footer_theme_bg', 'label' => '背景图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_theme_color_custom', 'label' => '自定义文字颜色', 'type' => 'color', 'default' => ''],
|
||||
]],
|
||||
['key' => 'contact', 'title' => '联系方式', 'fields' => [
|
||||
['key' => 'footer_contact_wechat_img', 'label' => '微信二维码', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_contact_qq', 'label' => 'QQ', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'footer_contact_weibo', 'label' => '微博', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'footer_contact_email', 'label' => '邮箱', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'brand', 'title' => '品牌列(第一列)', 'fields' => [
|
||||
['key' => 'footer_t1_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_t1_img', 'label' => '页脚 Logo(日间)', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_t1_img_dark', 'label' => '页脚 Logo(夜间)', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'footer_t1_t', 'label' => '品牌标题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'fcode_t1_code', 'label' => '品牌列自定义代码', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'contact_ext', 'title' => '联系方式扩展', 'fields' => [
|
||||
['key' => 'footer_contact_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_contact_more', 'label' => '更多联系项', 'type' => 'textarea', 'default' => '', 'help' => 'JSON 数组,每项含 desc/icon/link/img'],
|
||||
['key' => 'fcode_t2_code_1', 'label' => '联系区上方自定义代码', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'fcode_t2_code_2', 'label' => '联系区下方自定义代码(备案等)', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'mini_qr', 'title' => '二维码图片组(第三列)', 'fields' => [
|
||||
['key' => 'footer_mini_img_m_s', 'label' => '显示设备', 'type' => 'checkbox', 'default' => ['pc', 'mobile'], 'options' => [
|
||||
['label' => 'PC', 'value' => 'pc'],
|
||||
['label' => '移动端', 'value' => 'mobile'],
|
||||
]],
|
||||
['key' => 'footer_mini_img', 'label' => '二维码组', 'type' => 'textarea', 'default' => '', 'help' => 'JSON 数组,每项含 text/img(最多4个)'],
|
||||
]],
|
||||
['key' => 'custom', 'title' => '底部自定义', 'fields' => [
|
||||
['key' => 'fcode_customize_code', 'label' => '底部自定义代码', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'tabbar' => [
|
||||
'key' => 'tabbar',
|
||||
'title' => '底部Tab栏',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => 'Tab栏设置', 'fields' => [
|
||||
['key' => 'footer_tabbar_s', 'label' => '启用底部Tab栏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_scroll_hide', 'label' => '滚动时隐藏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_tabbar_scroll_down_hide', 'label' => '下滑时隐藏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_max_items', 'label' => '最大Tab数', 'type' => 'number', 'default' => 5, 'min' => 3, 'max' => 7],
|
||||
['key' => 'footer_tabbar_icon_size', 'label' => '图标大小', 'type' => 'number', 'default' => 22, 'min' => 16, 'max' => 32],
|
||||
['key' => 'footer_tabbar_text_size', 'label' => '文字大小', 'type' => 'number', 'default' => 10, 'min' => 8, 'max' => 14],
|
||||
['key' => 'footer_tabbar_show_text', 'label' => '显示文字', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_active_color', 'label' => '选中颜色', 'type' => 'color', 'default' => '#f04494'],
|
||||
['key' => 'footer_tabbar_bg_color', 'label' => '背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'footer_tabbar_border', 'label' => '显示上边框', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_shadow', 'label' => '显示阴影', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_animation', 'label' => '切换动画', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_double_click_home', 'label' => '双击首页刷新', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'footer_tabbar_badge_dot', 'label' => '徽标圆点样式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'footer_tabbar_single', 'label' => '文章页专属Tab', 'type' => 'select', 'default' => 'default', 'options' => [
|
||||
['label' => '默认按钮', 'value' => 'default'],
|
||||
['label' => '文章扩展按钮(评论/收藏/分享)', 'value' => 'extend'],
|
||||
], 'help' => '文章详情页底部显示的Tab按钮类型'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'float-btn' => [
|
||||
'key' => 'float-btn',
|
||||
'title' => '悬浮按钮',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '悬浮按钮设置', 'fields' => [
|
||||
['key' => 'float_btn_style', 'label' => '按钮风格', 'type' => 'radio', 'default' => 'round', 'options' => [
|
||||
['label' => '圆形', 'value' => 'round'],
|
||||
['label' => '方形', 'value' => 'square'],
|
||||
]],
|
||||
['key' => 'float_btn_position', 'label' => '按钮位置', 'type' => 'radio', 'default' => 'bottom', 'options' => [
|
||||
['label' => '底部', 'value' => 'bottom'],
|
||||
['label' => '居中', 'value' => 'center'],
|
||||
]],
|
||||
['key' => 'float_btn_scroll_hide', 'label' => '滚动时隐藏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'float_btn_scroll_down_hide', 'label' => '下滑时隐藏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'float_btn_max', 'label' => '最大显示数', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 10],
|
||||
['key' => 'float_btn_size', 'label' => '按钮尺寸', 'type' => 'number', 'default' => 44, 'min' => 32, 'max' => 64],
|
||||
['key' => 'float_btn_spacing', 'label' => '按钮间距', 'type' => 'number', 'default' => 10, 'min' => 4, 'max' => 24],
|
||||
['key' => 'float_btn_shadow', 'label' => '显示阴影', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'float_btn_animation', 'label' => '悬停动画', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'float_btn_opacity', 'label' => '按钮透明度', 'type' => 'number', 'default' => 100, 'min' => 30, 'max' => 100],
|
||||
['key' => 'float_btn_active_color', 'label' => '激活颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'float_btn_bg_color', 'label' => '背景颜色', 'type' => 'color', 'default' => ''],
|
||||
['key' => 'float_btn_pc_s', 'label' => 'PC端显示', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'float_btn_m_s', 'label' => '移动端显示', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'float_btn_filter_css', 'label' => '过滤CSS', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Payment schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Payment {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'payment',
|
||||
'title' => '支付&付费',
|
||||
'sections' => [
|
||||
'paid' => [
|
||||
'key' => 'paid',
|
||||
'title' => '下载/付费内容中心',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'strategy', 'title' => '付费内容策略', 'fields' => [
|
||||
['key' => 'payment_paid_post_types', 'label' => '受管内容类型', 'type' => 'input', 'default' => 'post'],
|
||||
['key' => 'payment_vip_free_download', 'label' => '角色白名单免扣下载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_vip_exempt_roles', 'label' => '免扣角色列表', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'delivery', 'title' => '交付说明', 'fields' => [
|
||||
['key' => 'payment_paid_catalog_note', 'label' => '前台付费提示', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_manual_delivery_note', 'label' => '人工交付备注', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'payment_paid_mark', 'label' => '付费标记', 'type' => 'input', 'default' => '付费'],
|
||||
['key' => 'payment_currency_symbol', 'label' => '货币符号', 'type' => 'input', 'default' => '¥'],
|
||||
['key' => 'payment_paid_show_user', 'label' => '显示购买用户', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_paid_show_paycount', 'label' => '显示购买次数', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_paid_box_position', 'label' => '付费框位置', 'type' => 'radio', 'default' => 'top', 'options' => [
|
||||
['label' => '文章模块上', 'value' => 'box_top'],
|
||||
['label' => '内容顶', 'value' => 'top'],
|
||||
['label' => '内容底', 'value' => 'bottom'],
|
||||
['label' => '模块下', 'value' => 'box_bottom'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'download', 'title' => '下载设置', 'fields' => [
|
||||
['key' => 'payment_download_enable', 'label' => '启用下载功能', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_download_remote_s', 'label' => '远程文件下载', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_download_verify_nonce', 'label' => '下载鉴权', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_download_user_log', 'label' => '用户下载记录条数', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'payment_download_post_log', 'label' => '文章下载记录条数', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'payment_download_remote_size', 'label' => '远程文件大小限制(KB)', 'type' => 'number', 'default' => 2048, 'min' => 1, 'max' => 999999],
|
||||
['key' => 'payment_download_remote_ext', 'label' => '远程文件格式限制', 'type' => 'input', 'default' => 'zip,rar,7z'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'recharge' => [
|
||||
'key' => 'recharge',
|
||||
'title' => '余额充值',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '充值开关', 'fields' => [
|
||||
['key' => 'payment_recharge_enabled', 'label' => '启用余额充值', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_recharge_mode', 'label' => '充值模式', 'type' => 'select', 'default' => 'manual_review', 'options' => [
|
||||
['label' => '人工审核', 'value' => 'manual_review'],
|
||||
['label' => '自动到账', 'value' => 'auto'],
|
||||
]],
|
||||
['key' => 'payment_recharge_desc', 'label' => '充值说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_recharge_auto_to_balance', 'label' => '自动到账到余额', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'packages', 'title' => '充值套餐', 'fields' => [
|
||||
['key' => 'payment_recharge_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|入门包\n98|108|推荐包"],
|
||||
['key' => 'payment_recharge_help', 'label' => '帮助文案', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'product', 'title' => '充值产品', 'fields' => [
|
||||
['key' => 'payment_recharge_product', 'label' => '充值产品', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_recharge_price', 'label' => '充值价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_recharge_tag', 'label' => '价格标签', 'type' => 'input', 'default' => '元'],
|
||||
['key' => 'payment_recharge_tag_class', 'label' => '标签样式', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_recharge_custom_s', 'label' => '自定义金额', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_recharge_custom_limit', 'label' => '自定义金额限制', 'type' => 'number', 'default' => 9999, 'min' => 1, 'max' => 99999],
|
||||
]],
|
||||
['key' => 'transfer', 'title' => '余额转账', 'fields' => [
|
||||
['key' => 'payment_balance_transfer_s', 'label' => '启用余额转账', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_balance_transfer_service_charge', 'label' => '转账手续费(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 50],
|
||||
['key' => 'payment_balance_transfer_desc', 'label' => '转账说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'pass-charge', 'title' => '充值卡免扣', 'fields' => [
|
||||
['key' => 'payment_recharge_pass_charge_s', 'label' => '启用充值卡免扣', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_recharge_pass_charge_desc', 'label' => '免扣说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_recharge_pass_charge_only_password', 'label' => '仅密码免扣', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'exchange', 'title' => '积分兑换', 'fields' => [
|
||||
['key' => 'payment_recharge_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_recharge_exchange_rate', 'label' => '兑换比例', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 10000],
|
||||
['key' => 'payment_recharge_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'vip' => [
|
||||
'key' => 'vip',
|
||||
'title' => 'VIP会员',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '会员基础', 'fields' => [
|
||||
['key' => 'payment_vip_enabled', 'label' => '启用会员结构', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_vip_name', 'label' => '会员名称', 'type' => 'input', 'default' => 'VIP会员'],
|
||||
['key' => 'payment_vip_badge_text', 'label' => '会员徽标文案', 'type' => 'input', 'default' => 'VIP'],
|
||||
['key' => 'payment_vip_desc', 'label' => '会员说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'packages', 'title' => '会员套餐', 'fields' => [
|
||||
['key' => 'payment_vip_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|月卡\n365|365|年卡"],
|
||||
['key' => 'payment_vip_help', 'label' => '权益说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'benefit', 'title' => '会员权益', 'fields' => [
|
||||
['key' => 'payment_vip_download_limit', 'label' => '下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'payment_vip_download_speed', 'label' => '下载速度(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_vip_discount', 'label' => '折扣(%)', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 100],
|
||||
['key' => 'payment_vip_pass_charge', 'label' => '积分充值免扣', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_vip_pass_charge_desc', 'label' => '积分充值说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'renew', 'title' => '续费设置', 'fields' => [
|
||||
['key' => 'payment_vip_renew', 'label' => '允许续费', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_vip_renew_desc', 'label' => '续费说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_vip_renew_price_type', 'label' => '续费价格类型', 'type' => 'radio', 'default' => 'same', 'options' => [
|
||||
['label' => '相同价格', 'value' => 'same'],
|
||||
['label' => '折扣价格', 'value' => 'discount'],
|
||||
]],
|
||||
['key' => 'payment_vip_renew_discount', 'label' => '续费折扣(%)', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 100],
|
||||
['key' => 'payment_vip_renew_reduce', 'label' => '续费减免金额', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999],
|
||||
]],
|
||||
['key' => 'upgrade', 'title' => '升级设置', 'fields' => [
|
||||
['key' => 'payment_vip_upgrade', 'label' => '允许升级', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_vip_upgrade_desc', 'label' => '升级说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_vip_upgrade_product', 'label' => '升级产品', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'price', 'title' => '价格设置', 'fields' => [
|
||||
['key' => 'payment_vip_month_price', 'label' => '月卡价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_vip_year_price', 'label' => '年卡价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_vip_permanent_price', 'label' => '永久价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_vip_unit_price', 'label' => '单位价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
['key' => 'payment_vip_unit_tag', 'label' => '单位标签', 'type' => 'input', 'default' => '元'],
|
||||
['key' => 'payment_vip_jump_price', 'label' => '跳转价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'revenue' => [
|
||||
'key' => 'revenue',
|
||||
'title' => '创作分成',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'policy', 'title' => '分成策略', 'fields' => [
|
||||
['key' => 'payment_revenue_enabled', 'label' => '启用创作分成', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_revenue_share_ratio', 'label' => '作者分成比例(%)', 'type' => 'number', 'default' => 70, 'min' => 1, 'max' => 100],
|
||||
['key' => 'payment_revenue_ratio_vip1', 'label' => 'VIP1分成比例(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'payment_revenue_ratio_vip2', 'label' => 'VIP2分成比例(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'payment_revenue_settlement_threshold', 'label' => '最小结算金额', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 99999],
|
||||
['key' => 'payment_revenue_cycle_days', 'label' => '结算周期(天)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 365],
|
||||
['key' => 'payment_revenue_points_rule', 'label' => '积分规则', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_revenue_note', 'label' => '结算备注', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'payment_revenue_desc_details', 'label' => '收益明细文案', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_revenue_desc_page', 'label' => '收益说明页', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'affiliate' => [
|
||||
'key' => 'affiliate',
|
||||
'title' => '推广返佣',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'policy', 'title' => '返佣策略', 'fields' => [
|
||||
['key' => 'payment_affiliate_enabled', 'label' => '启用推广返佣', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_affiliate_ratio', 'label' => '返佣比例(%)', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 100],
|
||||
['key' => 'payment_affiliate_window_days', 'label' => '归因窗口(天)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 365],
|
||||
['key' => 'payment_affiliate_require_paid_order', 'label' => '仅对已支付订单返佣', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_affiliate_self_s', 'label' => '自购返佣', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_affiliate_show_users', 'label' => '显示推广用户', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'user-ratio', 'title' => '用户返佣比例', 'fields' => [
|
||||
['key' => 'payment_affiliate_user_ratio', 'label' => '普通用户比例(%)', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 100],
|
||||
['key' => 'payment_affiliate_vip1_ratio', 'label' => 'VIP1比例(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'payment_affiliate_vip2_ratio', 'label' => 'VIP2比例(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
]],
|
||||
['key' => 'poster', 'title' => '推广海报', 'fields' => [
|
||||
['key' => 'payment_affiliate_poster_s', 'label' => '启用推广海报', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_affiliate_poster_img', 'label' => '海报背景图', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'payment_affiliate_poster_title', 'label' => '海报标题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_affiliate_poster_desc', 'label' => '海报描述', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'text', 'title' => '文案设置', 'fields' => [
|
||||
['key' => 'payment_affiliate_text_desc', 'label' => '返佣说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_affiliate_text_details_title', 'label' => '明细标题', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_affiliate_text_details', 'label' => '明细说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_affiliate_text_discount', 'label' => '折扣文案', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'gateway' => [
|
||||
'key' => 'gateway',
|
||||
'title' => '收款接口',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'adapter', 'title' => '网关适配器', 'fields' => [
|
||||
['key' => 'payment_gateway_mode', 'label' => '运行模式', 'type' => 'string', 'default' => 'manual_review'],
|
||||
['key' => 'payment_gateway_test_enabled', 'label' => '启用测试网关', 'type' => 'bool', 'default' => true],
|
||||
['key' => 'payment_gateway_manual_review', 'label' => '保留人工复核', 'type' => 'bool', 'default' => true],
|
||||
['key' => 'payment_gateway_callback_secret', 'label' => '回调校验口令', 'type' => 'string', 'default' => ''],
|
||||
['key' => 'payment_gateway_help', 'label' => '运维说明', 'type' => 'string', 'default' => '未接真实支付网关时,建议使用测试网关或人工确认模式完成联调。'],
|
||||
]],
|
||||
['key' => 'gateways', 'title' => '支付网关', 'fields' => [
|
||||
['key' => 'payment_wechat_enabled', 'label' => '微信支付', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_alipay_enabled', 'label' => '支付宝', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_paypal_enabled', 'label' => 'PayPal', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_balance_enabled', 'label' => '余额支付', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_card_pass_enabled', 'label' => '卡密充值', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_xunhupay_enabled', 'label' => '虎皮椒', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_xhpay_enabled', 'label' => '小火付', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_payjs_enabled', 'label' => 'PayJS', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_epay_enabled', 'label' => '易支付', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_vmq_enabled', 'label' => 'V免签', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_codepay_enabled', 'label' => 'Codepay', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_stripe_enabled', 'label' => 'Stripe', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'wechat', 'title' => '微信支付配置', 'fields' => [
|
||||
['key' => 'wechat_merchantid', 'label' => '商户号', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_jsapi', 'label' => 'JSAPI密钥', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_appsecret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_cert_path', 'label' => '证书路径', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_key_path', 'label' => '密钥路径', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'alipay', 'title' => '支付宝配置', 'fields' => [
|
||||
['key' => 'alipay_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_privatekey', 'label' => '应用私钥', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_publickey', 'label' => '支付宝公钥', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_return_url', 'label' => '同步回调地址', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_notify_url', 'label' => '异步回调地址', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'paypal', 'title' => 'PayPal配置', 'fields' => [
|
||||
['key' => 'paypal_client_id', 'label' => 'Client ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'paypal_client_secret', 'label' => 'Client Secret', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'paypal_currency', 'label' => '货币', 'type' => 'input', 'default' => 'USD'],
|
||||
['key' => 'paypal_sandbox', 'label' => '沙箱模式', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'xunhupay', 'title' => '虎皮椒配置', 'fields' => [
|
||||
['key' => 'xunhupay_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'xunhupay_appsecret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'xhpay', 'title' => '小火付配置', 'fields' => [
|
||||
['key' => 'xhpay_mchid', 'label' => '商户号', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'xhpay_alipay_v2', 'label' => '支付宝V2', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'xhpay_wx_h5', 'label' => '微信H5', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'payjs', 'title' => 'PayJS配置', 'fields' => [
|
||||
['key' => 'payjs_mchid', 'label' => '商户号', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payjs_key', 'label' => '通信密钥', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'epay', 'title' => '易支付配置', 'fields' => [
|
||||
['key' => 'epay_apiurl', 'label' => '接口地址', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'epay_pid', 'label' => '商户ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'epay_key', 'label' => '商户密钥', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'stripe', 'title' => 'Stripe配置', 'fields' => [
|
||||
['key' => 'stripe_secret_key', 'label' => 'Secret Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'stripe_publishable_key', 'label' => 'Publishable Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'stripe_webhook_secret', 'label' => 'Webhook Secret', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'stripe_currency', 'label' => '货币', 'type' => 'input', 'default' => 'usd'],
|
||||
['key' => 'stripe_test_mode', 'label' => '测试模式', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'order' => [
|
||||
'key' => 'order',
|
||||
'title' => '订单设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '订单基础', 'fields' => [
|
||||
['key' => 'payment_order_prefix', 'label' => '订单前缀', 'type' => 'input', 'default' => 'AKSRT'],
|
||||
['key' => 'payment_order_expiry', 'label' => '订单有效期(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440],
|
||||
['key' => 'payment_show_user', 'label' => '显示用户信息', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_show_paycount', 'label' => '显示购买次数', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_no_logged_in', 'label' => '未登录提示', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_cookie_day', 'label' => 'Cookie有效期(天)', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 31],
|
||||
['key' => 'payment_no_logged_remind', 'label' => '未登录提醒文案', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_wait_float_s', 'label' => '订单等待浮动', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_order_max_minutes', 'label' => '订单最大支付时间(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'payment_mark', 'label' => '付费标记', 'type' => 'input', 'default' => '付费'],
|
||||
['key' => 'payment_box_position', 'label' => '付费框位置', 'type' => 'radio', 'default' => 'top', 'options' => [
|
||||
['label' => '文章模块上', 'value' => 'box_top'],
|
||||
['label' => '内容顶', 'value' => 'top'],
|
||||
['label' => '内容底', 'value' => 'bottom'],
|
||||
['label' => '模块下', 'value' => 'box_bottom'],
|
||||
]],
|
||||
['key' => 'payment_free_logged_show', 'label' => '登录用户显示免费', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_ip', 'label' => '显示用户IP', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_device', 'label' => '显示设备信息', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'payment_show_order_no', 'label' => '显示订单号', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_show_create_time', 'label' => '显示创建时间', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'payment_show_pay_time', 'label' => '显示支付时间', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'status', 'title' => '状态设置', 'fields' => [
|
||||
['key' => 'payment_order_status_pending', 'label' => '待支付状态文案', 'type' => 'input', 'default' => '待支付'],
|
||||
['key' => 'payment_order_status_paid', 'label' => '已支付状态文案', 'type' => 'input', 'default' => '已支付'],
|
||||
['key' => 'payment_order_status_completed', 'label' => '已完成状态文案', 'type' => 'input', 'default' => '已完成'],
|
||||
['key' => 'payment_order_status_cancelled', 'label' => '已取消状态文案', 'type' => 'input', 'default' => '已取消'],
|
||||
['key' => 'payment_order_status_refunded', 'label' => '已退款状态文案', 'type' => 'input', 'default' => '已退款'],
|
||||
['key' => 'payment_order_status_refunding', 'label' => '退款中状态文案', 'type' => 'input', 'default' => '退款中'],
|
||||
['key' => 'payment_order_status_processing', 'label' => '处理中状态文案', 'type' => 'input', 'default' => '处理中'],
|
||||
]],
|
||||
['key' => 'service', 'title' => '服务设置', 'fields' => [
|
||||
['key' => 'payment_service', 'label' => '客服设置', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'payment_service_icon', 'label' => '客服图标', 'type' => 'icon', 'default' => ''],
|
||||
['key' => 'payment_service_value', 'label' => '客服内容', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_service_qq', 'label' => 'QQ客服', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'payment_service_wechat', 'label' => '微信客服', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'card-pass' => [
|
||||
'key' => 'card-pass',
|
||||
'title' => '充值卡',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '充值卡设置', 'fields' => [
|
||||
['key' => 'card_pass_enabled', 'label' => '启用充值卡', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'card_pass_desc', 'label' => '充值卡说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'card_pass_type', 'label' => '充值卡类型', 'type' => 'select', 'default' => 'balance', 'options' => [
|
||||
['label' => '余额卡', 'value' => 'balance'],
|
||||
['label' => 'VIP卡', 'value' => 'vip'],
|
||||
['label' => '积分卡', 'value' => 'credits'],
|
||||
]],
|
||||
['key' => 'card_pass_prefix', 'label' => '卡号前缀', 'type' => 'input', 'default' => 'AK'],
|
||||
['key' => 'card_pass_length', 'label' => '卡号长度', 'type' => 'number', 'default' => 16, 'min' => 8, 'max' => 32],
|
||||
['key' => 'card_pass_batch_size', 'label' => '批量生成数量', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 500],
|
||||
]],
|
||||
['key' => 'redeem', 'title' => '兑换设置', 'fields' => [
|
||||
['key' => 'card_pass_max_redeem', 'label' => '单卡最大兑换次数', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 100],
|
||||
['key' => 'card_pass_expiry', 'label' => '有效期(天)', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为永久有效'],
|
||||
['key' => 'card_pass_amount', 'label' => '面额(元)', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 9999],
|
||||
['key' => 'card_pass_vip_days', 'label' => 'VIP天数', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 3650],
|
||||
['key' => 'card_pass_credits', 'label' => '赠送积分', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'card_pass_redeem_page', 'label' => '兑换页面', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'card_pass_success_msg', 'label' => '兑换成功提示', 'type' => 'input', 'default' => '兑换成功!'],
|
||||
['key' => 'card_pass_fail_msg', 'label' => '兑换失败提示', 'type' => 'input', 'default' => '卡号无效或已过期'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'withdrawal' => [
|
||||
'key' => 'withdrawal',
|
||||
'title' => '提现管理',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '提现设置', 'fields' => [
|
||||
['key' => 'pay_rebate_withdraw_lowest_money', 'label' => '最低提现金额', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 99999],
|
||||
['key' => 'withdraw_service_charge', 'label' => '提现手续费(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 50],
|
||||
['key' => 'pay_rebate_withdraw_text_details', 'label' => '提现说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Permissions schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Permissions {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'permissions',
|
||||
'title' => '功能&权限',
|
||||
'sections' => [
|
||||
'login' => [
|
||||
'key' => 'login',
|
||||
'title' => '登录权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_captcha_config',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础开关', 'fields' => [
|
||||
['key' => 'enabled', 'label' => '启用人机验证', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'active_provider', 'label' => '当前服务商', 'type' => 'radio', 'default' => '', 'options' => [
|
||||
['label' => '腾讯云 Captcha', 'value' => 'tencent'],
|
||||
['label' => '阿里云 CAPTCHA 2.0', 'value' => 'aliyun'],
|
||||
['label' => 'Cloudflare Turnstile', 'value' => 'cloudflare'],
|
||||
['label' => '极验 GeeTest', 'value' => 'geetest'],
|
||||
]],
|
||||
['key' => 'mode', 'label' => '触发模式', 'type' => 'radio', 'default' => 'always', 'options' => [
|
||||
['label' => '始终显示', 'value' => 'always'],
|
||||
]],
|
||||
['key' => 'disabled_scenarios', 'label' => '停用场景', 'type' => 'checkbox', 'default' => [], 'options' => [
|
||||
['label' => '发表评论', 'value' => 'comment'],
|
||||
['label' => '用户登录', 'value' => 'login'],
|
||||
['label' => '用户注册', 'value' => 'register'],
|
||||
['label' => '绑定第三方', 'value' => 'bind'],
|
||||
['label' => '找回密码', 'value' => 'lostpassword'],
|
||||
['label' => '修改密码', 'value' => 'change_password'],
|
||||
['label' => '更改邮箱', 'value' => 'change_email'],
|
||||
['label' => '邮箱验证', 'value' => 'verify_email'],
|
||||
['label' => '资源下载', 'value' => 'download'],
|
||||
['label' => '每日签到', 'value' => 'signin'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '服务商配置', 'fields' => [
|
||||
['key' => 'tencent', 'label' => '腾讯云配置', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array'],
|
||||
['key' => 'aliyun', 'label' => '阿里云配置', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array'],
|
||||
['key' => 'cloudflare', 'label' => 'Cloudflare 配置', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array'],
|
||||
['key' => 'geetest', 'label' => '极验配置', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'comment' => [
|
||||
'key' => 'comment',
|
||||
'title' => '评论权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础权限', 'fields' => [
|
||||
['key' => 'comment_enabled', 'label' => '全局评论开关', 'type' => 'switch', 'default' => true, 'help' => '关闭后全站禁用评论功能'],
|
||||
['key' => 'comment_guest_allowed', 'label' => '允许游客评论', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_min_length', 'label' => '评论最少字数', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 100],
|
||||
['key' => 'comment_max_length', 'label' => '评论最多字数', 'type' => 'number', 'default' => 1000, 'min' => 50, 'max' => 10000],
|
||||
['key' => 'comment_cool_down_seconds', 'label' => '评论冷却时间(秒)', 'type' => 'number', 'default' => 15, 'min' => 0, 'max' => 300],
|
||||
['key' => 'comment_max_links', 'label' => '最大链接数', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 10],
|
||||
['key' => 'comment_max_images', 'label' => '最大图片数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 20],
|
||||
]],
|
||||
['key' => 'sensitive', 'title' => '敏感词', 'fields' => [
|
||||
['key' => 'comment_sensitive_words', 'label' => '敏感词列表', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'comment_sensitive_action', 'label' => '敏感词处理', 'type' => 'radio', 'default' => 'reject', 'options' => [
|
||||
['label' => '拒绝评论', 'value' => 'reject'],
|
||||
['label' => '替换为***', 'value' => 'replace'],
|
||||
['label' => '转入审核', 'value' => 'pending'],
|
||||
]],
|
||||
['key' => 'comment_sensitive_log', 'label' => '记录敏感词日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'anti-spam', 'title' => '反垃圾', 'fields' => [
|
||||
['key' => 'comment_link_filter', 'label' => '链接过滤', 'type' => 'switch', 'default' => true, 'help' => '评论包含链接自动进入审核'],
|
||||
['key' => 'comment_flood_protection', 'label' => '防刷评论', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_flood_interval', 'label' => '防刷间隔(秒)', 'type' => 'number', 'default' => 10, 'min' => 3, 'max' => 60],
|
||||
['key' => 'comment_max_per_hour', 'label' => '每小时最大评论数', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 200],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'comment_avatar_required', 'label' => '要求设置头像', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_nickname_required', 'label' => '要求填写昵称', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_email_required', 'label' => '要求填写邮箱', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'attachment' => [
|
||||
'key' => 'attachment',
|
||||
'title' => '附件权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'profile', 'title' => '头像与封面', 'fields' => [
|
||||
['key' => 'allow_avatar_upload', 'label' => '允许用户上传头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'allow_cover_upload', 'label' => '允许用户上传封面图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'allow_background_upload', 'label' => '允许用户上传背景图', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'avatar_max_size', 'label' => '头像/封面大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
|
||||
]],
|
||||
['key' => 'content', 'title' => '内容上传', 'fields' => [
|
||||
['key' => 'allow_submit_images', 'label' => '允许投稿插入图片', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'submit_image_max_size', 'label' => '投稿图片大小上限 (MB)', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 20],
|
||||
['key' => 'allow_comment_images', 'label' => '允许评论插入图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_image_max_size', 'label' => '评论图片大小上限 (MB)', 'type' => 'number', 'default' => 2, 'min' => 1, 'max' => 10],
|
||||
]],
|
||||
['key' => 'resource', 'title' => '资源附件', 'fields' => [
|
||||
['key' => 'allow_resource_upload', 'label' => '允许资源附件上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'resource_max_size', 'label' => '资源附件大小上限 (MB)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 200],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'download' => [
|
||||
'key' => 'download',
|
||||
'title' => '下载权限',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '下载设置', 'fields' => [
|
||||
['key' => 'download_require_login', 'label' => '下载需要登录', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'download_require_credits', 'label' => '下载需要积分', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'download_credits_cost', 'label' => '下载所需积分', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 100],
|
||||
['key' => 'download_daily_limit', 'label' => '每日下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为不限制'],
|
||||
['key' => 'download_link_expire', 'label' => '下载链接有效期(分钟)', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 1440],
|
||||
['key' => 'download_speed_limit', 'label' => '下载速度限制(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为不限制'],
|
||||
]],
|
||||
['key' => 'vip', 'title' => 'VIP 特权', 'fields' => [
|
||||
['key' => 'download_vip_free', 'label' => 'VIP 免费下载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'download_vip_daily_extra', 'label' => 'VIP每日额外次数', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 999],
|
||||
]],
|
||||
['key' => 'role', 'title' => '角色权限', 'fields' => [
|
||||
['key' => 'download_role_limits', 'label' => '角色下载限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|daily_limit,每行一条'],
|
||||
['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'],
|
||||
]],
|
||||
['key' => 'captcha', 'title' => '安全设置', 'fields' => [
|
||||
['key' => 'download_captcha', 'label' => '下载需要验证码', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'moderation' => [
|
||||
'key' => 'moderation',
|
||||
'title' => '审核和风控',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_moderation_config',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '审核总开关', 'fields' => [
|
||||
['key' => 'enabled', 'label' => '启用审核系统', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'akismet', 'title' => 'Akismet', 'fields' => [
|
||||
['key' => 'akismet_enabled', 'label' => '启用 Akismet', 'type' => 'switch', 'default' => false, 'dependsOn' => 'enabled'],
|
||||
['key' => 'akismet_site_url', 'label' => 'Site URL', 'type' => 'input', 'default' => '', 'dependsOn' => { 'key' => 'akismet_enabled', 'value' => true }],
|
||||
['key' => 'akismet_blog_lang', 'label' => '博客语言', 'type' => 'input', 'default' => 'zh-CN', 'dependsOn' => { 'key' => 'akismet_enabled', 'value' => true }],
|
||||
['key' => 'akismet_api_key', 'label' => 'API Key', 'type' => 'input', 'inputType' => 'password', 'default' => '', 'dependsOn' => { 'key' => 'akismet_enabled', 'value' => true }],
|
||||
]],
|
||||
['key' => 'ai', 'title' => 'AI 审核', 'fields' => [
|
||||
['key' => 'ai_enabled', 'label' => '启用 AI 审核', 'type' => 'switch', 'default' => false, 'dependsOn' => 'enabled'],
|
||||
['key' => 'ai_api_hint', 'label' => '审核 API 配置', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => 'API 接入(百度/阿里云等)在「扩展 → API内容审核」中统一配置,此处仅控制审核范围和策略。', 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'ai_moderate_comments', 'label' => '用户评论', 'type' => 'switch', 'default' => true, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'ai_moderate_background', 'label' => '用户资料背景', 'type' => 'switch', 'default' => true, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'auto_approve_low_risk', 'label' => '低风险自动通过', 'type' => 'switch', 'default' => false, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'auto_reject_high_risk', 'label' => '高风险自动拒绝', 'type' => 'switch', 'default' => false, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'low_risk_max_score', 'label' => '低风险上限', 'type' => 'number', 'default' => 35, 'min' => 0, 'max' => 100, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
['key' => 'high_risk_min_score', 'label' => '高风险下限', 'type' => 'number', 'default' => 80, 'min' => 0, 'max' => 100, 'dependsOn' => { 'key' => 'ai_enabled', 'value' => true }],
|
||||
]],
|
||||
['key' => 'keywords', 'title' => '关键词拦截', 'fields' => [
|
||||
['key' => 'blocked_keywords', 'label' => '屏蔽关键词列表', 'type' => 'textarea', 'default' => '', 'rows' => 4, 'dependsOn' => 'enabled'],
|
||||
]],
|
||||
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
||||
['key' => 'rate_limit_enabled', 'label' => '启用频率限制', 'type' => 'switch', 'default' => false, 'dependsOn' => 'enabled'],
|
||||
['key' => 'rate_limit_min_interval', 'label' => '最小间隔(秒)', 'type' => 'number', 'default' => 8, 'min' => 1, 'max' => 300, 'dependsOn' => { 'key' => 'rate_limit_enabled', 'value' => true }],
|
||||
['key' => 'rate_limit_max_per_hour', 'label' => '每小时上限', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 200, 'dependsOn' => { 'key' => 'rate_limit_enabled', 'value' => true }],
|
||||
]],
|
||||
['key' => 'geoip', 'title' => 'GeoIP 拦截', 'fields' => [
|
||||
['key' => 'geoip_enabled', 'label' => '启用 GeoIP 拦截', 'type' => 'switch', 'default' => false, 'dependsOn' => 'enabled'],
|
||||
['key' => 'geoip_hint', 'label' => 'IP 数据源', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '复用「扩展 → IP归属地」中配置的数据源和 API Key。', 'dependsOn' => { 'key' => 'geoip_enabled', 'value' => true }],
|
||||
['key' => 'geoip_block_countries', 'label' => '屏蔽国家/地区', 'type' => 'custom', 'default' => [], 'sanitize_type' => 'array', 'dependsOn' => { 'key' => 'geoip_enabled', 'value' => true }],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'capabilities' => [
|
||||
'key' => 'capabilities',
|
||||
'title' => '基本权限 / 能力矩阵',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => self::build_cap_matrix_groups(),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Post_List schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Post_List {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'post_list',
|
||||
'title' => '文章&列表',
|
||||
'sections' => [
|
||||
'list' => [
|
||||
'key' => 'list',
|
||||
'title' => '文章列表',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'card', 'title' => '卡片样式', 'fields' => [
|
||||
['key' => 'post_card_style', 'label' => '卡片样式', 'type' => 'select', 'default' => 'standard', 'options' => [
|
||||
['label' => '标准卡', 'value' => 'standard'],
|
||||
['label' => '紧凑卡', 'value' => 'compact'],
|
||||
['label' => '大图卡', 'value' => 'large_thumb'],
|
||||
['label' => '多图卡', 'value' => 'multi_thumb'],
|
||||
['label' => '纯文字', 'value' => 'text'],
|
||||
['label' => '有图则图文', 'value' => 'thumb_if_has'],
|
||||
]],
|
||||
['key' => 'post_excerpt_length', 'label' => '摘要长度', 'type' => 'number', 'default' => 120, 'min' => 20, 'max' => 500],
|
||||
]],
|
||||
['key' => 'meta', 'title' => '元信息显示', 'fields' => [
|
||||
['key' => 'post_show_author', 'label' => '显示作者', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_show_date', 'label' => '显示日期', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_show_views', 'label' => '显示浏览数', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_show_category', 'label' => '显示分类', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_show_tags', 'label' => '显示标签', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_show_comments', 'label' => '显示评论数', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_show_likes', 'label' => '显示点赞数', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'list_badge_show', 'label' => '显示徽章类型', 'type' => 'checkbox', 'default' => ['pay', 'tag', 'topics', 'cat'], 'options' => [
|
||||
['label' => '付费', 'value' => 'pay'],
|
||||
['label' => '分类', 'value' => 'cat'],
|
||||
['label' => '专题', 'value' => 'topics'],
|
||||
['label' => '标签', 'value' => 'tag'],
|
||||
]],
|
||||
['key' => 'post_list_meta_show', 'label' => '显示元信息', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'list_meta_show', 'label' => '移动端优先部件', 'type' => 'radio', 'default' => 'like', 'options' => [
|
||||
['label' => '浏览数', 'value' => 'view'],
|
||||
['label' => '点赞数', 'value' => 'like'],
|
||||
['label' => '评论数', 'value' => 'comm'],
|
||||
], 'description' => '移动端空间不足时保留显示的单一部件'],
|
||||
]],
|
||||
['key' => 'layout', 'title' => '布局设置', 'fields' => [
|
||||
['key' => 'post_list_columns', 'label' => '列表列数', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 3],
|
||||
['key' => 'post_list_per_page', 'label' => '每页文章数', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 48],
|
||||
['key' => 'sticky_s', 'label' => '置顶范围', 'type' => 'checkbox', 'default' => ['home', 'cat'], 'options' => [
|
||||
['label' => '首页', 'value' => 'home'],
|
||||
['label' => '分类页', 'value' => 'cat'],
|
||||
['label' => '标签页', 'value' => 'tag'],
|
||||
['label' => '专题页', 'value' => 'topics'],
|
||||
]],
|
||||
['key' => 'post_target_blank', 'label' => '新窗口打开', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'item_heading_bold', 'label' => '标题加粗', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'list_show_type', 'label' => '列表样式', 'type' => 'radio', 'default' => 'separate', 'options' => [
|
||||
['label' => '分隔卡片', 'value' => 'separate'],
|
||||
['label' => '无间距', 'value' => 'no_margin'],
|
||||
]],
|
||||
['key' => 'list_card_home', 'label' => '首页卡片模式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'list_card_cat', 'label' => '分类卡片模式', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'list_card_tag', 'label' => '标签页卡片模式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'list_card_topics', 'label' => '专题页卡片模式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'list_card_author', 'label' => '用户页卡片模式', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'pagination', 'title' => '分页方式', 'fields' => [
|
||||
['key' => 'post_pagination_mode', 'label' => '分页模式', 'type' => 'radio', 'default' => 'numbered', 'options' => [
|
||||
['label' => '数字分页', 'value' => 'numbered'],
|
||||
['label' => '加载更多', 'value' => 'load_more'],
|
||||
['label' => '无限滚动', 'value' => 'infinite'],
|
||||
]],
|
||||
['key' => 'ias_max', 'label' => '最大加载页数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 10, 'description' => '0=不限'],
|
||||
['key' => 'ajax_trigger_text', 'label' => '加载更多文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'ajax_nomore_text', 'label' => '没有更多文案', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'sort', 'title' => '默认排序', 'fields' => [
|
||||
['key' => 'list_orderby', 'label' => '列表默认排序', 'type' => 'radio', 'default' => 'modified', 'options' => [
|
||||
['label' => '更新时间', 'value' => 'modified'],
|
||||
['label' => '发布时间', 'value' => 'date'],
|
||||
['label' => '评论数', 'value' => 'comments'],
|
||||
['label' => 'WordPress 默认', 'value' => 'default'],
|
||||
], 'help' => '分类/标签/归档页面的默认排序方式'],
|
||||
]],
|
||||
['key' => 'list_option', 'title' => '列表模式配置', 'fields' => [
|
||||
['key' => 'list_list_style', 'label' => '列表样式变体', 'type' => 'radio', 'default' => '', 'options' => [
|
||||
['label' => '默认', 'value' => ''],
|
||||
['label' => '样式2', 'value' => 'style2'],
|
||||
]],
|
||||
['key' => 'list_list_img_position', 'label' => '缩略图位置', 'type' => 'radio', 'default' => 'left', 'options' => [
|
||||
['label' => '左侧', 'value' => 'left'],
|
||||
['label' => '右侧', 'value' => 'right'],
|
||||
]],
|
||||
['key' => 'list_list_scale', 'label' => '缩略图比例 (%)', 'type' => 'number', 'default' => 70, 'min' => 50, 'max' => 200],
|
||||
]],
|
||||
['key' => 'card_option', 'title' => '卡片模式配置', 'fields' => [
|
||||
['key' => 'list_card_style', 'label' => '卡片样式变体', 'type' => 'radio', 'default' => '', 'options' => [
|
||||
['label' => '默认', 'value' => ''],
|
||||
['label' => '样式3', 'value' => 'style3'],
|
||||
]],
|
||||
['key' => 'list_card_scale', 'label' => '缩略图比例 (%)', 'type' => 'number', 'default' => 70, 'min' => 20, 'max' => 200],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'thumb' => [
|
||||
'key' => 'thumb',
|
||||
'title' => '列表缩略图',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'source', 'title' => '图片来源', 'fields' => [
|
||||
['key' => 'thumb_priority', 'label' => '图片优先级', 'type' => 'input', 'default' => 'featured,attachment,resource,custom'],
|
||||
['key' => 'thumb_fallback', 'label' => '无图时处理', 'type' => 'radio', 'default' => 'placeholder', 'options' => [
|
||||
['label' => '显示占位图', 'value' => 'placeholder'],
|
||||
['label' => '隐藏缩略图', 'value' => 'hide'],
|
||||
['label' => '随机封面', 'value' => 'random'],
|
||||
]],
|
||||
['key' => 'thumb_spare_images', 'label' => '备用缩略图', 'type' => 'gallery', 'default' => [], 'description' => '当 thumb_fallback 为 random 时从此图库随机选取'],
|
||||
['key' => 'thumb_aspect_ratio', 'label' => '图片比例', 'type' => 'select', 'default' => '16:9', 'options' => [
|
||||
['label' => '16:9 宽屏', 'value' => '16:9'],
|
||||
['label' => '4:3 标准', 'value' => '4:3'],
|
||||
['label' => '1:1 正方形', 'value' => '1:1'],
|
||||
['label' => '自适应', 'value' => 'auto'],
|
||||
]],
|
||||
['key' => 'list_thumb_fit', 'label' => '缩略图填充方式', 'type' => 'radio', 'default' => 'cover', 'options' => [
|
||||
['label' => '填充裁剪', 'value' => 'cover'],
|
||||
['label' => '完整显示', 'value' => 'contain'],
|
||||
]],
|
||||
['key' => 'list_thumb_slides_s', 'label' => '幻灯片缩略图', 'type' => 'switch', 'default' => true, 'description' => '允许幻灯片作为列表缩略图'],
|
||||
['key' => 'list_thumb_video_s', 'label' => '视频缩略图', 'type' => 'switch', 'default' => true, 'description' => '鼠标悬停时自动播放视频缩略图'],
|
||||
['key' => 'list_thumb_video_mute_s', 'label' => '视频缩略图静音', 'type' => 'switch', 'default' => true, 'description' => '视频缩略图播放时静音'],
|
||||
['key' => 'thumb_postfirstimg_s', 'label' => '自动获取文章首图', 'type' => 'switch', 'default' => true, 'description' => '自动从文章内容中提取第一张图片作为缩略图'],
|
||||
['key' => 'thumb_catimg_s', 'label' => '自动获取分类封面', 'type' => 'switch', 'default' => true, 'description' => '无特色图时使用分类封面图作为缩略图'],
|
||||
['key' => 'thumb_postfirstimg_size', 'label' => '首图尺寸', 'type' => 'radio', 'default' => 'medium', 'options' => [
|
||||
['label' => '缩略图', 'value' => 'thumbnail'],
|
||||
['label' => '中等', 'value' => 'medium'],
|
||||
['label' => '大', 'value' => 'large'],
|
||||
['label' => '原始', 'value' => 'full'],
|
||||
], 'description' => '自动获取文章首图时使用的 WP 图片尺寸'],
|
||||
]],
|
||||
['key' => 'multi', 'title' => '多图模式', 'fields' => [
|
||||
['key' => 'thumb_multi_enabled', 'label' => '多图卡片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'thumb_multi_count', 'label' => '最多显示几张', 'type' => 'number', 'default' => 3, 'min' => 2, 'max' => 6],
|
||||
['key' => 'mult_thumb_cat', 'label' => '按分类多图显示', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'thumb_lazy', 'label' => '懒加载', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'page' => [
|
||||
'key' => 'page',
|
||||
'title' => '文章页面',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'navigation', 'title' => '文章导航', 'fields' => [
|
||||
['key' => 'post_toc_enabled', 'label' => '目录树导航', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_toc_depth', 'label' => '目录深度', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 6],
|
||||
['key' => 'article_nav_mobile_nav_s', 'label' => '移动端弹出菜单', 'type' => 'switch', 'default' => true, 'description' => '在移动端弹出菜单中显示目录导航'],
|
||||
['key' => 'post_reading_progress', 'label' => '阅读进度条', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'breadcrumbs_single_s', 'label' => '面包屑导航', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'breadcrumbs_home_text', 'label' => '面包屑首页文字', 'type' => 'input', 'default' => '首页', 'depends_on' => 'breadcrumbs_single_s'],
|
||||
['key' => 'post_prevnext_s', 'label' => '上一篇/下一篇', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_authordesc_s', 'label' => '作者简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_single_hide_time_s', 'label' => '隐藏文章时间', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'cover', 'title' => '封面展示', 'fields' => [
|
||||
['key' => 'post_article_image_cover', 'label' => '图片封面', 'type' => 'switch', 'default' => true, 'description' => '文章有特色图时自动展示封面'],
|
||||
['key' => 'post_article_image_cover_scale', 'label' => '封面高度比例(%)', 'type' => 'number', 'default' => 35, 'min' => 10, 'max' => 100],
|
||||
['key' => 'post_article_slide_cover', 'label' => '幻灯片封面', 'type' => 'switch', 'default' => true, 'description' => '文章有图集时以轮播形式展示封面'],
|
||||
['key' => 'post_article_video_cover', 'label' => '视频封面', 'type' => 'switch', 'default' => true, 'description' => '文章有视频时以视频播放器作为封面'],
|
||||
['key' => 'post_article_video_cover_scale', 'label' => '视频封面高度比例(%)', 'type' => 'number', 'default' => 56, 'min' => 20, 'max' => 100],
|
||||
]],
|
||||
['key' => 'media', 'title' => '媒体功能', 'fields' => [
|
||||
['key' => 'post_lightbox', 'label' => '图片灯箱', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_lightbox_thumbs', 'label' => '灯箱缩略图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_lightbox_zoom', 'label' => '灯箱缩放', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_lightbox_full', 'label' => '灯箱全屏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_lightbox_down', 'label' => '灯箱下载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_lightbox_play', 'label' => '灯箱播放', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'related', 'title' => '相关推荐', 'fields' => [
|
||||
['key' => 'post_related_enabled', 'label' => '显示相关推荐', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_related_count', 'label' => '推荐数量', 'type' => 'number', 'default' => 6, 'min' => 1, 'max' => 20],
|
||||
['key' => 'post_related_type', 'label' => '推荐方式', 'type' => 'radio', 'default' => 'category', 'options' => [
|
||||
['label' => '同分类', 'value' => 'category'],
|
||||
['label' => '同标签', 'value' => 'tag'],
|
||||
['label' => '随机', 'value' => 'random'],
|
||||
]],
|
||||
['key' => 'post_related_orderby', 'label' => '推荐排序', 'type' => 'select', 'default' => 'rand', 'options' => [
|
||||
['label' => '随机', 'value' => 'rand'],
|
||||
['label' => '相关度', 'value' => 'relevance'],
|
||||
['label' => '时间', 'value' => 'date'],
|
||||
]],
|
||||
['key' => 'post_related_title', 'label' => '推荐标题', 'type' => 'input', 'default' => '相关推荐'],
|
||||
]],
|
||||
['key' => 'interactions', 'title' => '互动功能', 'fields' => [
|
||||
['key' => 'post_like_enabled', 'label' => '点赞', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_favorite_enabled', 'label' => '收藏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'share_s', 'label' => '分享按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_copyright_enabled', 'label' => '版权信息', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_copyright_text', 'label' => '版权默认文案', 'type' => 'textarea', 'default' => '', 'description' => '留空则自动生成"本文由 XX 原创发布"'],
|
||||
['key' => 'post_wechat_share', 'label' => '微信分享', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_rewards_enabled', 'label' => '打赏功能', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_rewards_text', 'label' => '打赏文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'post_button_toptext', 'label' => '按钮上方文案', 'type' => 'input', 'default' => '喜欢就支持一下吧'],
|
||||
]],
|
||||
['key' => 'content', 'title' => '内容处理', 'fields' => [
|
||||
['key' => 'post_p_indent', 'label' => '段落缩进', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_maxheight_kg', 'label' => '内容高度限制', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_maxheight', 'label' => '最大高度', 'type' => 'number', 'default' => 1000, 'min' => 600, 'max' => 3000],
|
||||
['key' => 'post_front_content', 'label' => '文章头部内容', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'post_after_content', 'label' => '文章底部内容', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'yiyan', 'title' => '一言', 'fields' => [
|
||||
['key' => 'post_yiyan_header', 'label' => '文章头部一言', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_yiyan_footer', 'label' => '文章底部一言', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_yiyan_box', 'label' => '一言盒子', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'meta', 'title' => 'Meta设置', 'fields' => [
|
||||
['key' => 'post_default_views_min', 'label' => '默认浏览最小值', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'post_default_views_max', 'label' => '默认浏览最大值', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 9999],
|
||||
['key' => 'post_default_likes_min', 'label' => '默认点赞最小值', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'post_default_likes_max', 'label' => '默认点赞最大值', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 9999],
|
||||
['key' => 'post_views_s', 'label' => '浏览统计', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_close_comments', 'label' => '关闭评论', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'filter' => [
|
||||
'key' => 'filter',
|
||||
'title' => '高级筛选',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '筛选基础', 'fields' => [
|
||||
['key' => 'filter_enabled', 'label' => '启用筛选栏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'custom_filter_show', 'label' => '筛选显示页面', 'type' => 'checkbox', 'default' => ['cat', 'tag'], 'options' => [
|
||||
['label' => '分类页', 'value' => 'cat'],
|
||||
['label' => '标签页', 'value' => 'tag'],
|
||||
['label' => '专题页', 'value' => 'topics'],
|
||||
], 'help' => '选择在哪些页面显示筛选栏'],
|
||||
['key' => 'filter_categories', 'label' => '分类筛选', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'filter_tags', 'label' => '标签筛选', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'filter_sort', 'label' => '排序选项', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'filter_ajax_enabled', 'label' => 'AJAX 无刷新筛选', 'type' => 'switch', 'default' => true, 'help' => '启用后分类/标签/排序切换将通过 AJAX 加载,无需刷新页面'],
|
||||
]],
|
||||
['key' => 'ajax_dimensions', 'title' => 'AJAX 筛选维度', 'fields' => [
|
||||
['key' => 'filter_cat_show_cat', 'label' => '分类页:显示分类筛选', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'filter_cat_show_tag', 'label' => '分类页:显示标签筛选', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'filter_tag_show_cat', 'label' => '标签页:显示分类筛选', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'filter_tag_show_tag', 'label' => '标签页:显示标签筛选', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'filter_topics_show_cat', 'label' => '专题页:显示分类筛选', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'filter_topics_show_tag', 'label' => '专题页:显示标签筛选', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'custom_filter', 'title' => '自定义筛选维度', 'fields' => [
|
||||
['key' => 'custom_filter', 'label' => '筛选维度定义', 'type' => 'repeater', 'default' => [], 'help' => '每项定义一个筛选维度(如"类型""地区""年份"),及其可选值列表', 'fields' => [
|
||||
['key' => 'name', 'label' => '维度名称', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'key', 'label' => '维度标识', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'vals', 'label' => '可选值', 'type' => 'repeater', 'default' => [], 'fields' => [
|
||||
['key' => 'key', 'label' => '标识', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'name', 'label' => '显示名', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
]],
|
||||
]],
|
||||
['key' => 'cat_custom_filter', 'title' => '按分类绑定筛选', 'fields' => [
|
||||
['key' => 'cat_custom_filter', 'label' => '分类筛选绑定', 'type' => 'repeater', 'default' => [], 'help' => '为指定分类绑定不同的筛选维度组合', 'fields' => [
|
||||
['key' => 'cats', 'label' => '绑定分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'cat_s', 'label' => '显示分类筛选', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'tag_s', 'label' => '显示标签筛选', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'topics_s', 'label' => '显示专题筛选', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'orderby_s', 'label' => '显示排序按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'custom_filters', 'label' => '启用的自定义维度', 'type' => 'input', 'default' => '', 'help' => '逗号分隔维度标识,留空使用全部'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'feature' => [
|
||||
'key' => 'feature',
|
||||
'title' => '文章功能',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'tools', 'title' => '工具功能', 'fields' => [
|
||||
['key' => 'feature_print', 'label' => '打印文章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'feature_word_count', 'label' => '字数统计', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'feature_reading_time', 'label' => '预计阅读时间', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'feature_pdf_export', 'label' => 'PDF 导出', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'comment' => [
|
||||
'key' => 'comment',
|
||||
'title' => '评论设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '评论展示', 'fields' => [
|
||||
['key' => 'comment_order', 'label' => '评论排序', 'type' => 'radio', 'default' => 'asc', 'options' => [
|
||||
['label' => '正序', 'value' => 'asc'],
|
||||
['label' => '倒序', 'value' => 'desc'],
|
||||
]],
|
||||
['key' => 'comment_per_page', 'label' => '每页评论数', 'type' => 'number', 'default' => 20, 'min' => 5, 'max' => 100],
|
||||
['key' => 'comment_pagination_type', 'label' => '分页方式', 'type' => 'radio', 'default' => 'numbered', 'options' => [
|
||||
['label' => '翻页', 'value' => 'numbered'],
|
||||
['label' => '加载更多', 'value' => 'load_more'],
|
||||
['label' => '无限滚动', 'value' => 'infinite'],
|
||||
]],
|
||||
['key' => 'comment_paging_ajax_ias_s', 'label' => '评论自动加载', 'type' => 'switch', 'default' => true, 'description' => '评论无限滚动时自动触发加载'],
|
||||
['key' => 'comment_paging_ajax_ias_max', 'label' => '评论最大加载页数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 10, 'description' => '0=不限'],
|
||||
['key' => 'comment_nested_depth', 'label' => '回复嵌套深度', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 10],
|
||||
['key' => 'comment_avatar_size', 'label' => '头像大小', 'type' => 'number', 'default' => 48, 'min' => 24, 'max' => 96],
|
||||
]],
|
||||
['key' => 'permissions', 'title' => '评论权限', 'fields' => [
|
||||
['key' => 'comment_require_login', 'label' => '评论需要登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_allow_reply', 'label' => '允许回复', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_close_comments', 'label' => '关闭评论', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'comment_edit_window', 'label' => '作者自助编辑时限(分钟)', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 1440],
|
||||
]],
|
||||
['key' => 'toolbar', 'title' => '评论工具栏', 'fields' => [
|
||||
['key' => 'comment_smilie', 'label' => 'Emoji表情', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_code', 'label' => '代码插入', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_img', 'label' => '图片上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_upload_img', 'label' => '评论图片上传', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'interaction', 'title' => '互动功能', 'fields' => [
|
||||
['key' => 'comment_like_s', 'label' => '评论点赞', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_quick_s', 'label' => '快捷回复', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_city_s', 'label' => '评论城市', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_author_tag', 'label' => '作者标签', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'comment_corderby', 'label' => '评论排序切换', 'type' => 'switch', 'default' => true, 'description' => '前台显示最新/最早/最热切换按钮'],
|
||||
]],
|
||||
['key' => 'text', 'title' => '文案配置', 'fields' => [
|
||||
['key' => 'comment_empty_text', 'label' => '空评论提示', 'type' => 'input', 'default' => '暂无评论,来做第一个评论的人吧~'],
|
||||
['key' => 'comment_title', 'label' => '评论区标题', 'type' => 'input', 'default' => '评论'],
|
||||
['key' => 'comment_submit_text', 'label' => '提交按钮文案', 'type' => 'input', 'default' => '提交评论'],
|
||||
['key' => 'comment_text', 'label' => '评论框提示', 'type' => 'input', 'default' => '说点什么吧...'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'pay' => [
|
||||
'key' => 'pay',
|
||||
'title' => '付费内容',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '付费设置', 'fields' => [
|
||||
['key' => 'post_pay_enable', 'label' => '启用付费内容', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_pay_price_default', 'label' => '默认价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999],
|
||||
['key' => 'post_pay_free_logged_show', 'label' => '登录用户显示免费', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_pay_box_position', 'label' => '付费框位置', 'type' => 'radio', 'default' => 'content', 'options' => [
|
||||
['label' => '内容前', 'value' => 'content'],
|
||||
['label' => '全文遮罩', 'value' => 'mask'],
|
||||
]],
|
||||
['key' => 'post_pay_mark', 'label' => '付费标记', 'type' => 'input', 'default' => '付费'],
|
||||
['key' => 'post_pay_show_user', 'label' => '显示购买用户', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'download', 'title' => '下载设置', 'fields' => [
|
||||
['key' => 'post_download_enable', 'label' => '启用下载功能', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_download_remote_s', 'label' => '远程文件下载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_download_verify_nonce', 'label' => '下载鉴权', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_download_user_log', 'label' => '用户下载日志', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_download_post_log', 'label' => '文章下载日志', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'share' => [
|
||||
'key' => 'share',
|
||||
'title' => '分享设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '分享设置', 'fields' => [
|
||||
['key' => 'share_wechat_s', 'label' => '微信分享', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'share_wechat_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'share_wechat_app_secret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'share_only_logo', 'label' => '仅显示Logo', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'share_img_compatible_s', 'label' => '海报兼容模式', 'type' => 'switch', 'default' => false, 'description' => '启用后使用兼容模式生成分享海报'],
|
||||
['key' => 'share_img_byimg', 'label' => '海报默认图', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'share_logo', 'label' => '海报LOGO', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'share_img', 'label' => '分享图片', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'share_desc', 'label' => '分享描述', 'type' => 'textarea', 'default' => '扫一扫 立即查看'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'key' => 'submit',
|
||||
'title' => '前台投稿',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础配置', 'fields' => [
|
||||
['key' => 'front_submit_enabled', 'label' => '开启前台投稿', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'front_submit_roles', 'label' => '允许投稿的角色', 'type' => 'input', 'default' => 'author,editor,administrator'],
|
||||
['key' => 'post_article_btn_txte', 'label' => '投稿按钮文案', 'type' => 'input', 'default' => '发布文章'],
|
||||
['key' => 'post_article_title_strlen_limit_min', 'label' => '标题最少字数', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 100],
|
||||
['key' => 'post_article_title_strlen_limit_max', 'label' => '标题最多字数', 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 200],
|
||||
['key' => 'post_t_placeholder', 'label' => '标题占位符', 'type' => 'input', 'default' => '', 'description' => '留空使用默认'],
|
||||
['key' => 'post_c_placeholder', 'label' => '内容占位符', 'type' => 'textarea', 'default' => '', 'description' => '留空使用默认'],
|
||||
]],
|
||||
['key' => 'fields', 'title' => '表单字段', 'fields' => [
|
||||
['key' => 'front_submit_title', 'label' => '标题', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'front_submit_content', 'label' => '正文', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'front_submit_excerpt', 'label' => '摘要', 'type' => 'switch', 'default' => true],
|
||||
['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' => 'post_article_user', 'label' => '免登录归属用户', 'type' => 'user_select', 'default' => 0, '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' => '投稿时可选择自定义筛选维度值'],
|
||||
['key' => 'front_submit_custom_fields', 'label' => '自定义字段', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'review', 'title' => '审核策略', 'fields' => [
|
||||
['key' => 'front_submit_review_mode', 'label' => '审核模式', 'type' => 'radio', 'default' => 'pending', 'options' => [
|
||||
['label' => '待审核', 'value' => 'pending'],
|
||||
['label' => '直接发布', 'value' => 'publish'],
|
||||
['label' => '自动草稿', 'value' => 'draft'],
|
||||
]],
|
||||
['key' => 'front_submit_cover_required', 'label' => '封面图必填', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'front_submit_max_images', 'label' => '正文最大图片数', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 50],
|
||||
]],
|
||||
['key' => 'editor', 'title' => '编辑器设置', 'fields' => [
|
||||
['key' => 'front_submit_editor_mode', 'label' => '编辑器模式', 'type' => 'radio', 'default' => 'rich', 'options' => [
|
||||
['label' => '富文本', 'value' => 'rich'],
|
||||
['label' => 'Markdown', 'value' => 'markdown'],
|
||||
['label' => '纯文本', 'value' => 'plain'],
|
||||
]],
|
||||
['key' => 'front_submit_draft_save', 'label' => '自动保存草稿', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'front_submit_leave_warning', 'label' => '离开页面提醒', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'paid', 'title' => '付费投稿权限', 'fields' => [
|
||||
['key' => 'post_article_pay_free_s', 'label' => '允许设置免费', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_article_pay_price_min', 'label' => '最低价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999, 'help' => '投稿可设置的最低付费金额'],
|
||||
['key' => 'post_article_pay_price_max', 'label' => '最高价格', 'type' => 'number', 'default' => 100, 'min' => 0, 'max' => 9999, 'help' => '投稿可设置的最高付费金额'],
|
||||
['key' => 'post_article_pay_vip_price_s', 'label' => '允许设置会员价', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_article_pay_vip_1_discount', 'label' => 'VIP1 折扣(%)', 'type' => 'number', 'default' => 80, 'min' => 0, 'max' => 100, 'help' => 'VIP1 会员折扣百分比,80 即 8 折'],
|
||||
['key' => 'post_article_pay_vip_2_discount', 'label' => 'VIP2 折扣(%)', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 100, 'help' => 'VIP2 会员折扣百分比,50 即 5 折'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Basic section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Basic {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'basic',
|
||||
'title' => '商城基础',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'general', 'title' => '商城开关', 'fields' => [
|
||||
['key' => 'shop_s', 'label' => '启用商城', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'shop_home_url', 'label' => '商城首页链接', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'breadcrumbs', 'title' => '面包屑', 'fields' => [
|
||||
['key' => 'shop_breadcrumbs_s', 'label' => '显示面包屑', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'shop_breadcrumbs_home', 'label' => '显示首页链接', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'shop_breadcrumbs_shop_home', 'label' => '显示商城首页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'shop_breadcrumbs_shop_home_name', 'label' => '商城首页名称', 'type' => 'input', 'default' => '商城首页'],
|
||||
]],
|
||||
['key' => 'rewrite', 'title' => 'URL 别名', 'fields' => [
|
||||
['key' => 'shop_product_rewrite_slug', 'label' => '商品 URL 别名', 'type' => 'input', 'default' => 'product'],
|
||||
['key' => 'shop_cart_rewrite_slug', 'label' => '购物车 URL 别名', 'type' => 'input', 'default' => 'cart'],
|
||||
['key' => 'shop_rewrite_suffix_html_s', 'label' => '.html 伪静态后缀', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Contact section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Contact {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'contact',
|
||||
'title' => '客服配置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'button', 'title' => '客服按钮', 'fields' => [
|
||||
['key' => 'shop_contact_msg_s', 'label' => '显示客服按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'shop_contact_name', 'label' => '按钮文案', 'type' => 'input', 'default' => '联系客服'],
|
||||
['key' => 'shop_contact_note', 'label' => '客服备注', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'channels', 'title' => '联系方式', 'fields' => [
|
||||
['key' => 'shop_contact_more', 'label' => '多客服联系方式', 'type' => 'textarea', 'default' => '', 'help' => '每行一个:名称|图标|链接|二维码图片 URL'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_List section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_List {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'list',
|
||||
'title' => '商品列表',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'pagination', 'title' => '列表分页', 'fields' => [
|
||||
['key' => 'shop_list_orderby', 'label' => '默认排序', 'type' => 'select', 'default' => 'date', 'options' => [
|
||||
['label' => '最新发布', 'value' => 'date'],
|
||||
['label' => '销量最高', 'value' => 'sales'],
|
||||
['label' => '价格最低', 'value' => 'price_asc'],
|
||||
['label' => '价格最高', 'value' => 'price_desc'],
|
||||
['label' => '评价最多', 'value' => 'comments'],
|
||||
]],
|
||||
['key' => 'shop_list_count', 'label' => '每页数量', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 100],
|
||||
['key' => 'shop_list_paging', 'label' => '翻页模式', 'type' => 'radio', 'default' => 'number', 'options' => [
|
||||
['label' => '数字翻页', 'value' => 'number'],
|
||||
['label' => 'AJAX 加载', 'value' => 'ajax'],
|
||||
]],
|
||||
['key' => 'shop_list_ias_s', 'label' => '滚动自动加载', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'shop_list_ias_max', 'label' => '自动加载页数', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 50],
|
||||
]],
|
||||
['key' => 'style', 'title' => '列表样式', 'fields' => [
|
||||
['key' => 'shop_list_style', 'label' => '卡片样式', 'type' => 'select', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '竖向大卡片', 'value' => 'card-lg'],
|
||||
['label' => '横向小卡片', 'value' => 'card-sm'],
|
||||
]],
|
||||
['key' => 'shop_sales_show', 'label' => '销量显示', 'type' => 'select', 'default' => 'show', 'options' => [
|
||||
['label' => '显示', 'value' => 'show'],
|
||||
['label' => '不显示', 'value' => 'hide'],
|
||||
['label' => '超量显示', 'value' => 'threshold'],
|
||||
]],
|
||||
['key' => 'shop_sales_show_min', 'label' => '起显阈值', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 99999],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Product_Defaults section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Product_Defaults {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'product-defaults',
|
||||
'title' => '商品默认参数',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'policy', 'title' => '购买策略', 'fields' => [
|
||||
['key' => 'shop_limit_buy', 'label' => '默认限购', 'type' => 'number', 'default' => -1, 'min' => -1, 'max' => 9999, 'help' => '-1 不限,0 禁购,正数为限购数量'],
|
||||
['key' => 'shop_guest_buy', 'label' => '免登录购买', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'shop_email_fill', 'label' => '邮箱必留', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'shop_rebate', 'label' => '允许返佣', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'shipping', 'title' => '运费默认', 'fields' => [
|
||||
['key' => 'shop_shipping_fee', 'label' => '默认运费类型', 'type' => 'select', 'default' => 'free', 'options' => [
|
||||
['label' => '包邮', 'value' => 'free'],
|
||||
['label' => '固定运费', 'value' => 'fixed'],
|
||||
['label' => '按金额计算', 'value' => 'amount'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'afterSale', 'title' => '售后与服务', 'fields' => [
|
||||
['key' => 'shop_after_sale', 'label' => '默认售后政策', 'type' => 'select', 'default' => 'refund_return', 'options' => [
|
||||
['label' => '仅退款', 'value' => 'refund'],
|
||||
['label' => '退货退款', 'value' => 'refund_return'],
|
||||
['label' => '不支持售后', 'value' => 'none'],
|
||||
]],
|
||||
['key' => 'shop_service', 'label' => '服务保障', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'params', 'title' => '默认参数模板', 'fields' => [
|
||||
['key' => 'shop_product_params_default', 'label' => '商品参数模板', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Shipping_Config section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Shipping_Config {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'shipping-config',
|
||||
'title' => '物流配置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'address', 'title' => '收货地址', 'fields' => [
|
||||
['key' => 'shop_manual_address', 'label' => '手动填写收货地址', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'express', 'title' => '快递查询', 'fields' => [
|
||||
['key' => 'shop_express_api_sdk', 'label' => '快递查询接口', 'type' => 'select', 'default' => 'kuaidi100', 'options' => [
|
||||
['label' => '快递100', 'value' => 'kuaidi100'],
|
||||
['label' => '阿里云', 'value' => 'aliyun'],
|
||||
['label' => '快递鸟', 'value' => 'kdniao'],
|
||||
]],
|
||||
['key' => 'shop_express_query_interval', 'label' => '查询刷新间隔(分钟)', 'type' => 'number', 'default' => 60, 'min' => 30, 'max' => 240],
|
||||
]],
|
||||
['key' => 'credentials', 'title' => '接口凭据', 'fields' => [
|
||||
['key' => 'shop_express_kdniao_opt', 'label' => '快递鸟凭据', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'shop_express_kuaidi100_opt', 'label' => '快递100凭据', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'shop_express_aliyun_opt', 'label' => '阿里云凭据', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'companies', 'title' => '物流公司', 'fields' => [
|
||||
['key' => 'shop_shipping_company', 'label' => '物流公司列表', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Single section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Single {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'single',
|
||||
'title' => '商品详情',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'layout', 'title' => '详情页布局', 'fields' => [
|
||||
['key' => 'shop_content_layout', 'label' => '内容布局', 'type' => 'select', 'default' => 'adapt', 'options' => [
|
||||
['label' => '适应宽度', 'value' => 'adapt'],
|
||||
['label' => '全屏', 'value' => 'full'],
|
||||
['label' => '适应宽度+侧栏', 'value' => 'sidebar'],
|
||||
]],
|
||||
['key' => 'shop_content_show_bg', 'label' => '详情背景盒子', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'shop_single_tab', 'label' => '自定义 TAB 栏目', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'shop_content_after', 'label' => '详情页底部内容', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'related', 'title' => '相关推荐', 'fields' => [
|
||||
['key' => 'shop_single_related_s', 'label' => '显示相关推荐', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'shop_single_related_opt', 'label' => '推荐配置', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'image', 'title' => '商品图片', 'fields' => [
|
||||
['key' => 'shop_main_image_default', 'label' => '默认商品主图', 'type' => 'media', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop_Timing section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop_Timing {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'timing',
|
||||
'title' => '时效与售后原因',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'receipt', 'title' => '确认收货', 'fields' => [
|
||||
['key' => 'shop_order_receipt_max_day', 'label' => '自动确认收货(天)', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 60],
|
||||
]],
|
||||
['key' => 'comment', 'title' => '评价时效', 'fields' => [
|
||||
['key' => 'shop_comment_max_day', 'label' => '评价时效(天)', 'type' => 'number', 'default' => 7, 'min' => 1, 'max' => 60],
|
||||
['key' => 'shop_comment_placeholder', 'label' => '评价占位文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'shop_comment_img_num', 'label' => '评价图片数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 9],
|
||||
]],
|
||||
['key' => 'afterSale', 'title' => '售后退货', 'fields' => [
|
||||
['key' => 'shop_after_sale_return_express_max_day', 'label' => '退货快递时效(天)', 'type' => 'number', 'default' => 7, 'min' => 1, 'max' => 30],
|
||||
['key' => 'shop_after_sale_reason_refund', 'label' => '仅退款原因', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'shop_after_sale_reason_return', 'label' => '退货退款原因', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'author', 'title' => '商家信息', 'fields' => [
|
||||
['key' => 'shop_author_show', 'label' => '显示商家信息', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Shop schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Shop {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'shop',
|
||||
'title' => '商城&商品',
|
||||
'version' => '1.0.0',
|
||||
'sections' => [
|
||||
'basic' => AKSRT_Admin_Schema_Shop_Basic::build(),
|
||||
'list' => AKSRT_Admin_Schema_Shop_List::build(),
|
||||
'contact' => AKSRT_Admin_Schema_Shop_Contact::build(),
|
||||
'single' => AKSRT_Admin_Schema_Shop_Single::build(),
|
||||
'product-defaults' => AKSRT_Admin_Schema_Shop_Product_Defaults::build(),
|
||||
'shipping-config' => AKSRT_Admin_Schema_Shop_Shipping_Config::build(),
|
||||
'timing' => AKSRT_Admin_Schema_Shop_Timing::build(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools_Backup section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools_Backup {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'backup',
|
||||
'title' => '数据备份',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'backup', 'title' => '备份设置', 'fields' => [
|
||||
['key' => 'backup_auto', 'label' => '自动备份', 'type' => 'switch', 'help' => '启用自动定时备份'],
|
||||
['key' => 'backup_frequency', 'label' => '备份频率', 'type' => 'select', 'default' => 'weekly', 'options' => [
|
||||
['label' => '每天', 'value' => 'daily'],
|
||||
['label' => '每周', 'value' => 'weekly'],
|
||||
['label' => '每月', 'value' => 'monthly'],
|
||||
]],
|
||||
['key' => 'backup_retain', 'label' => '保留份数', 'type' => 'number', 'default' => 7, 'min' => 3, 'max' => 20],
|
||||
['key' => 'backup_now', 'label' => '立即备份', 'type' => 'button', 'help' => '手动触发一次完整备份'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools_Email_Rollback section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools_Email_Rollback {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'email-rollback',
|
||||
'title' => '邮件回滚',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'rollback', 'title' => '邮件回滚', 'fields' => [
|
||||
['key' => 'rollback_type', 'label' => '回滚类型', 'type' => 'select', 'options' => [
|
||||
['label' => '按时间范围', 'value' => 'date_range'],
|
||||
['label' => '按邮件类型', 'value' => 'email_type'],
|
||||
['label' => '全部回滚', 'value' => 'all'],
|
||||
]],
|
||||
['key' => 'rollback_date_from', 'label' => '开始日期', 'type' => 'date'],
|
||||
['key' => 'rollback_date_to', 'label' => '结束日期', 'type' => 'date'],
|
||||
['key' => 'rollback_execute', 'label' => '执行回滚', 'type' => 'button', 'danger' => true, 'help' => '此操作不可逆'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools_Health section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools_Health {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'health',
|
||||
'title' => '健康检查',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'check', 'title' => '环境检查', 'fields' => [
|
||||
['key' => 'php_version', 'label' => 'PHP 版本', 'type' => 'display', 'help' => '建议 7.4+'],
|
||||
['key' => 'wp_version', 'label' => 'WordPress 版本', 'type' => 'display', 'help' => '建议 5.8+'],
|
||||
['key' => 'memory_limit', 'label' => '内存限制', 'type' => 'display', 'help' => '建议 256M+'],
|
||||
['key' => 'upload_max_size', 'label' => '上传限制', 'type' => 'display'],
|
||||
['key' => 'extensions', 'label' => '必要扩展', 'type' => 'display', 'help' => 'curl, mbstring, json, gd'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools_Import_Export section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools_Import_Export {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'import-export',
|
||||
'title' => '导入导出',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'export', 'title' => '导出配置', 'fields' => [
|
||||
['key' => 'export_settings', 'label' => '导出主题设置', 'type' => 'button', 'help' => '导出当前所有主题配置为 JSON 文件'],
|
||||
['key' => 'export_options', 'label' => '导出 WordPress 选项', 'type' => 'button', 'help' => '导出 aksrt_settings 等选项'],
|
||||
]],
|
||||
['key' => 'import', 'title' => '导入配置', 'fields' => [
|
||||
['key' => 'import_settings', 'label' => '导入主题设置', 'type' => 'file', 'accept' => '.json', 'help' => '导入主题配置文件(将覆盖当前设置)'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools_Migration section schema.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools_Migration {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'migration',
|
||||
'title' => '数据迁移',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'status', 'title' => '迁移状态', 'fields' => [
|
||||
['key' => 'current_version', 'label' => '当前版本', 'type' => 'display'],
|
||||
['key' => 'pending_migrations', 'label' => '待执行迁移', 'type' => 'display'],
|
||||
['key' => 'run_migration', 'label' => '执行迁移', 'type' => 'button', 'help' => '手动触发数据库迁移'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Tools schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Tools {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'tools',
|
||||
'title' => '工具&诊断',
|
||||
'sections' => [
|
||||
'import-export' => AKSRT_Admin_Schema_Tools_Import_Export::build(),
|
||||
'health' => AKSRT_Admin_Schema_Tools_Health::build(),
|
||||
'migration' => AKSRT_Admin_Schema_Tools_Migration::build(),
|
||||
'backup' => AKSRT_Admin_Schema_Tools_Backup::build(),
|
||||
'email-rollback' => AKSRT_Admin_Schema_Tools_Email_Rollback::build(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,697 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT_Admin_Schema_Users schema builder.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AKSRT_Admin_Schema_Users {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'domain' => 'users',
|
||||
'title' => '用户&互动',
|
||||
'sections' => [
|
||||
'auth' => [
|
||||
'key' => 'auth',
|
||||
'title' => '注册登录',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'register', 'title' => '注册设置', 'fields' => [
|
||||
['key' => 'close_sign', 'label' => '一键禁用登录注册', 'type' => 'switch', 'default' => false, 'desc' => '开启后前台禁用全部注册/登录入口(含社交登录),不影响后台登录'],
|
||||
['key' => 'user_close_signup', 'label' => '关闭注册', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'close_signin', 'label' => '禁用账密登录', 'type' => 'switch', 'default' => false, 'desc' => '仅允许社交账号登录'],
|
||||
['key' => 'user_agreement_s', 'label' => '用户协议', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_agreement_page', 'label' => '协议页面', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_privacy_s', 'label' => '隐私政策', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_privacy_page', 'label' => '隐私页面', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_agreement_check', 'label' => '强制勾选协议', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'no_repetition_name', 'label' => '禁止重复昵称', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_nickname_out', 'label' => '昵称黑名单关键词', 'type' => 'textarea', 'default' => "赌博,博彩,代发,代理,刷单,博彩,六合彩,admin,test,管理员,客服", 'desc' => '一行一个关键词,注册昵称包含关键词将被拒绝'],
|
||||
['key' => 'user_email_limit_type', 'label' => '邮箱限制类型', 'type' => 'radio', 'default' => 'black', 'options' => [
|
||||
['label' => '黑名单(拒绝以下后缀)', 'value' => 'black'],
|
||||
['label' => '白名单(仅允许以下后缀)', 'value' => 'white'],
|
||||
]],
|
||||
['key' => 'user_email_out', 'label' => '邮箱限制后缀', 'type' => 'textarea', 'default' => "@tempmail.com\n@throwaway.email\n@guerrillamail.com", 'desc' => '一行一个邮箱后缀,如 @tempmail.com'],
|
||||
]],
|
||||
['key' => 'login', 'title' => '登录设置', 'fields' => [
|
||||
['key' => 'user_close_login', 'label' => '关闭登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_sign_type', 'label' => '登录注册样式', 'type' => 'radio', 'default' => 'page', 'options' => [
|
||||
['label' => '弹窗', 'value' => 'modal'],
|
||||
['label' => '跳转页面', 'value' => 'page'],
|
||||
]],
|
||||
['key' => 'sign_modal_auto_s', 'label' => '自动弹出登录窗', 'type' => 'switch', 'default' => false, 'desc' => '仅在 user_sign_type=modal 时生效'],
|
||||
['key' => 'sign_modal_auto_waiting_time', 'label' => '弹窗等待秒数', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 60, 'desc' => '页面加载多少秒后弹出登录窗'],
|
||||
['key' => 'sign_modal_auto_expires', 'label' => '弹窗周期(小时)', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 720, 'desc' => '关闭弹窗后多少小时内不再自动弹出,0=每次刷新都弹'],
|
||||
['key' => 'replace_wp_login', 'label' => '替换WP登录页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'admin_sign_key', 'label' => '应急登录 Key', 'type' => 'input', 'default' => '', 'desc' => '留空为禁用。管理员可通过 /wp-login.php?aksrt_bypass=<key> 绕过前台替换'],
|
||||
['key' => 'user_signin_phone_s', 'label' => '手机号登录', 'type' => 'switch', 'default' => false, 'desc' => '允许用手机号作为用户名登录'],
|
||||
['key' => 'user_signin_nopas_s', 'label' => '免密登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_signin_nopas_type', 'label' => '免密登录方式', 'type' => 'radio', 'default' => 'email', 'options' => [
|
||||
['label' => '邮箱验证码', 'value' => 'email'],
|
||||
['label' => '手机验证码', 'value' => 'phone'],
|
||||
['label' => '邮箱或手机', 'value' => 'email_phone'],
|
||||
]],
|
||||
['key' => 'user_signin_nopas_active', 'label' => '免密显示模式', 'type' => 'radio', 'default' => 'nopas_first', 'options' => [
|
||||
['label' => '优先免密', 'value' => 'nopas_first'],
|
||||
['label' => '优先密码', 'value' => 'password_first'],
|
||||
['label' => '仅免密', 'value' => 'nopas_only'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'signup_captcha', 'title' => '注册验证', 'fields' => [
|
||||
['key' => 'user_signup_captch', 'label' => '注册需验证', 'type' => 'switch', 'default' => true, 'desc' => '注册时需完成邮箱或手机验证'],
|
||||
['key' => 'captch_type', 'label' => '验证方式', 'type' => 'radio', 'default' => 'email', 'options' => [
|
||||
['label' => '邮箱', 'value' => 'email'],
|
||||
['label' => '手机', 'value' => 'phone'],
|
||||
['label' => '邮箱或手机', 'value' => 'email_phone'],
|
||||
]],
|
||||
['key' => 'user_signup_no_repas', 'label' => '无需重复密码', 'type' => 'switch', 'default' => false, 'desc' => '注册时不要求二次确认密码'],
|
||||
['key' => 'user_repas_captch_type', 'label' => '找回密码验证方式', 'type' => 'radio', 'default' => 'email', 'options' => [
|
||||
['label' => '邮箱', 'value' => 'email'],
|
||||
['label' => '手机', 'value' => 'phone'],
|
||||
['label' => '邮箱或手机', 'value' => 'email_phone'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'bind', 'title' => '绑定设置', 'fields' => [
|
||||
['key' => 'bind_phone', 'label' => '绑定手机', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'bind_reminder', 'label' => '绑定提醒', 'type' => 'radio', 'default' => 'close', 'options' => [
|
||||
['label' => '提醒绑定邮箱', 'value' => 'email'],
|
||||
['label' => '提醒绑定手机', 'value' => 'phone'],
|
||||
['label' => '提醒绑定邮箱和手机', 'value' => 'email_phone'],
|
||||
['label' => '关闭', 'value' => 'close'],
|
||||
]],
|
||||
['key' => 'bind_reminder_text', 'label' => '提醒文案', 'type' => 'input', 'default' => '请绑定手机号以正常使用'],
|
||||
['key' => 'bind_reminder_expires', 'label' => '提醒周期(小时)', 'type' => 'number', 'default' => 24, 'min' => 1, 'max' => 720, 'desc' => '用户关闭提醒后多少小时内不再提醒'],
|
||||
['key' => 'mandatory_bind', 'label' => '强制绑定', 'type' => 'radio', 'default' => 'close', 'options' => [
|
||||
['label' => '强制绑定邮箱', 'value' => 'email'],
|
||||
['label' => '强制绑定手机', 'value' => 'phone'],
|
||||
['label' => '强制绑定邮箱和手机', 'value' => 'email_phone'],
|
||||
['label' => '关闭', 'value' => 'close'],
|
||||
]],
|
||||
['key' => 'email_set_captch', 'label' => '绑定邮箱需验证', 'type' => 'switch', 'default' => true, 'desc' => '绑定邮箱时需发送验证码确认'],
|
||||
]],
|
||||
['key' => 'invite_inline', 'title' => '邀请码注册', 'fields' => [
|
||||
['key' => 'invit_code_s', 'label' => '启用邀请码', 'type' => 'switch', 'default' => false, 'desc' => '与「邀请码」子页联动,此处开关控制注册表单是否显示邀请码字段'],
|
||||
['key' => 'invit_code_mode', 'label' => '邀请模式', 'type' => 'select', 'default' => 'open', 'options' => [
|
||||
['label' => '开放注册(邀请码选填)', 'value' => 'open'],
|
||||
['label' => '必须邀请码', 'value' => 'must'],
|
||||
]],
|
||||
['key' => 'invit_code_open_desc', 'label' => '开放注册提示', 'type' => 'input', 'default' => '邀请码为选填,没有邀请码也可以注册'],
|
||||
['key' => 'invit_code_must_desc', 'label' => '必填提示', 'type' => 'input', 'default' => '请输入邀请码'],
|
||||
]],
|
||||
['key' => 'sign_page_ui', 'title' => '页面 UI 配置', 'fields' => [
|
||||
['key' => 'sign_page_background', 'label' => '页面背景图', 'type' => 'gallery', 'default' => [], 'desc' => '登录注册页背景图,多图随机'],
|
||||
['key' => 'sign_theme_mode', 'label' => '主题模式', 'type' => 'radio', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '亮色', 'value' => 'light'],
|
||||
['label' => '暗色', 'value' => 'dark'],
|
||||
]],
|
||||
['key' => 'sign_show_header', 'label' => '显示顶部导航', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sign_card_position', 'label' => '卡片位置', 'type' => 'radio', 'default' => 'center', 'options' => [
|
||||
['label' => '靠左', 'value' => 'left'],
|
||||
['label' => '居中', 'value' => 'center'],
|
||||
['label' => '靠右', 'value' => 'right'],
|
||||
]],
|
||||
['key' => 'sign_show_logo', 'label' => '显示 LOGO', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sign_page_footer', 'label' => '页脚 HTML', 'type' => 'textarea', 'default' => '', 'desc' => '登录注册页自定义页脚内容,支持 HTML'],
|
||||
]],
|
||||
['key' => 'modal_ui', 'title' => '弹窗 UI 配置', 'fields' => [
|
||||
['key' => 'sign_modal_background', 'label' => '弹窗背景图', 'type' => 'gallery', 'default' => [], 'desc' => '弹窗左侧背景图,多图随机'],
|
||||
['key' => 'sign_modal_show_logo', 'label' => '弹窗显示 LOGO', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'sign_logo', 'title' => '登录框 LOGO', 'fields' => [
|
||||
['key' => 'user_logo', 'label' => '日间 LOGO', 'type' => 'gallery', 'default' => [], 'desc' => '登录框专用日间 LOGO,留空使用站点全局 LOGO'],
|
||||
['key' => 'user_logo_dark', 'label' => '夜间 LOGO', 'type' => 'gallery', 'default' => [], 'desc' => '登录框专用夜间 LOGO,留空使用站点全局夜间 LOGO'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'notifications' => [
|
||||
'key' => 'notifications',
|
||||
'title' => '消息通知',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础设置', 'fields' => [
|
||||
['key' => 'message_s', 'label' => '站内通知总开关', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'msg_center_rewrite_slug', 'label' => '消息中心 URL 别名', 'type' => 'input', 'default' => 'message'],
|
||||
['key' => 'message_icon_show', 'label' => '通知图标位置', 'type' => 'checkbox', 'default' => ['nav_menu'], 'options' => [
|
||||
['label' => '导航菜单', 'value' => 'nav_menu'],
|
||||
['label' => '用户头像', 'value' => 'avatar'],
|
||||
['label' => '移动端底部', 'value' => 'mobile_bar'],
|
||||
]],
|
||||
['key' => 'message_paginate_type', 'label' => '列表翻页模式', 'type' => 'radio', 'default' => 'ajax_lists', 'options' => [
|
||||
['label' => 'AJAX 翻页', 'value' => 'ajax_lists'],
|
||||
['label' => '传统翻页', 'value' => 'default'],
|
||||
]],
|
||||
['key' => 'message_user_set', 'label' => '允许用户设置推送', 'type' => 'switch', 'default' => true, 'desc' => '允许用户在前台自行设置通知推送偏好'],
|
||||
]],
|
||||
['key' => 'types', 'title' => '通知类型', 'fields' => [
|
||||
['key' => 'message_close_msg_type', 'label' => '关闭部分推送', 'type' => 'checkbox', 'default' => [], 'options' => [
|
||||
['label' => '文章通知', 'value' => 'posts'],
|
||||
['label' => '评论回复', 'value' => 'comment'],
|
||||
['label' => '收藏通知', 'value' => 'favorite'],
|
||||
['label' => '点赞通知', 'value' => 'like'],
|
||||
['label' => '热门通知', 'value' => 'hot'],
|
||||
['label' => '关注通知', 'value' => 'followed'],
|
||||
['label' => '系统通知', 'value' => 'system'],
|
||||
['label' => '支付通知', 'value' => 'pay'],
|
||||
['label' => '提现回复', 'value' => 'withdraw_reply'],
|
||||
['label' => '勋章通知', 'value' => 'medal'],
|
||||
['label' => '认证回复', 'value' => 'auth_apply_reply'],
|
||||
], 'desc' => '勾选的类型将不向用户推送通知'],
|
||||
]],
|
||||
['key' => 'auto_clear', 'title' => '自动清理', 'fields' => [
|
||||
['key' => 'message_auto_clear_s', 'label' => '自动清理已读消息', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'message_auto_clear_time', 'label' => '过期天数', 'type' => 'number', 'default' => 365, 'min' => 1, 'max' => 3650, 'desc' => '已读消息超过此天数将被自动清理'],
|
||||
['key' => 'message_auto_clear_types', 'label' => '清理类型', 'type' => 'checkbox', 'default' => ['posts', 'comment', 'favorite', 'like', 'hot', 'followed', 'system'], 'options' => [
|
||||
['label' => '文章通知', 'value' => 'posts'],
|
||||
['label' => '评论回复', 'value' => 'comment'],
|
||||
['label' => '收藏通知', 'value' => 'favorite'],
|
||||
['label' => '点赞通知', 'value' => 'like'],
|
||||
['label' => '热门通知', 'value' => 'hot'],
|
||||
['label' => '关注通知', 'value' => 'followed'],
|
||||
['label' => '系统通知', 'value' => 'system'],
|
||||
['label' => '支付通知', 'value' => 'pay'],
|
||||
['label' => '提现回复', 'value' => 'withdraw_reply'],
|
||||
['label' => '勋章通知', 'value' => 'medal'],
|
||||
['label' => '认证回复', 'value' => 'auth_apply_reply'],
|
||||
['label' => '下载通知', 'value' => 'download'],
|
||||
['label' => '积分变动', 'value' => 'credits'],
|
||||
['label' => '私信', 'value' => 'private'],
|
||||
['label' => '举报反馈', 'value' => 'report'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'private', 'title' => '私信设置', 'fields' => [
|
||||
['key' => 'private_s', 'label' => '启用私信功能', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'private_smilie_s', 'label' => '支持表情', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'private_code_s', 'label' => '支持代码', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'private_image_s', 'label' => '支持图片', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'private_upload_img', 'label' => '支持上传图片', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'private_quick_s', 'label' => '快捷回复', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'private_quick_often', 'label' => '常用快捷回复', 'type' => 'textarea', 'default' => "你好,请问有什么可以帮你的?\n感谢你的反馈\n已处理,请查收", 'desc' => '一行一条快捷回复文案'],
|
||||
['key' => 'private_submit_text', 'label' => '发送按钮文案', 'type' => 'input', 'default' => '发送'],
|
||||
['key' => 'private_placeholder', 'label' => '输入占位符', 'type' => 'input', 'default' => '请输入消息...'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'oauth' => [
|
||||
'key' => 'oauth',
|
||||
'title' => '第三方登录',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '基础设置', 'fields' => [
|
||||
['key' => 'oauth_s', 'label' => '启用第三方登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_proxy_mode', 'label' => '代理登录模式', 'type' => 'radio', 'default' => 'close', 'options' => [
|
||||
['label' => '关闭', 'value' => 'close'],
|
||||
['label' => '服务端', 'value' => 'server'],
|
||||
['label' => '客户端', 'value' => 'client'],
|
||||
]],
|
||||
['key' => 'oauth_proxy_secret', 'label' => '代理 HMAC 密钥', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'oauth_proxy_mode', 'not' => 'close']],
|
||||
['key' => 'oauth_proxy_server_url', 'label' => '服务端地址', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'oauth_proxy_mode', 'value' => 'client']],
|
||||
['key' => 'oauth_proxy_server_providers', 'label' => '服务端允许平台', 'type' => 'checkbox', 'default' => ['github', 'gitee', 'qq', 'google'], 'options' => [
|
||||
['label' => 'GitHub', 'value' => 'github'],
|
||||
['label' => 'Gitee', 'value' => 'gitee'],
|
||||
['label' => 'QQ', 'value' => 'qq'],
|
||||
['label' => 'Google', 'value' => 'google'],
|
||||
['label' => '微博', 'value' => 'weibo'],
|
||||
['label' => '微信', 'value' => 'weixin'],
|
||||
['label' => '百度', 'value' => 'baidu'],
|
||||
['label' => '支付宝', 'value' => 'alipay'],
|
||||
], 'dependsOn' => ['key' => 'oauth_proxy_mode', 'value' => 'server']],
|
||||
['key' => 'oauth_proxy_client_providers', 'label' => '客户端启用平台', 'type' => 'checkbox', 'default' => ['github', 'gitee', 'qq', 'google'], 'options' => [
|
||||
['label' => 'GitHub', 'value' => 'github'],
|
||||
['label' => 'Gitee', 'value' => 'gitee'],
|
||||
['label' => 'QQ', 'value' => 'qq'],
|
||||
['label' => 'Google', 'value' => 'google'],
|
||||
['label' => '微博', 'value' => 'weibo'],
|
||||
['label' => '微信', 'value' => 'weixin'],
|
||||
['label' => '百度', 'value' => 'baidu'],
|
||||
['label' => '支付宝', 'value' => 'alipay'],
|
||||
], 'dependsOn' => ['key' => 'oauth_proxy_mode', 'value' => 'client']],
|
||||
['key' => 'oauth_proxy_gzh_type', 'label' => '代理公众号类型', 'type' => 'radio', 'default' => 'service', 'options' => [
|
||||
['label' => '服务号', 'value' => 'service'],
|
||||
['label' => '订阅号', 'value' => 'subscribe'],
|
||||
], 'dependsOn' => ['key' => 'oauth_proxy_mode', 'not' => 'close']],
|
||||
['key' => 'oauth_bind_type', 'label' => '绑定方式', 'type' => 'radio', 'default' => 'auto', 'options' => [
|
||||
['label' => '自动绑定', 'value' => 'auto'],
|
||||
['label' => '手动绑定', 'value' => 'manual'],
|
||||
]],
|
||||
['key' => 'oauth_button_lg', 'label' => '大按钮样式', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'weixingzh_priority', 'label' => 'PC端优先微信扫码', 'type' => 'switch', 'default' => false, 'help' => '开启后 PC 端登录框优先显示微信公众号扫码'],
|
||||
['key' => 'weixingzh_auto', 'label' => '微信APP内自动登录', 'type' => 'switch', 'default' => false, 'help' => '在微信内置浏览器中自动使用公众号授权登录'],
|
||||
['key' => 'weixingzh_auto_expires', 'label' => '自动登录频率限制 (秒)', 'type' => 'number', 'default' => 3600, 'min' => 0, 'max' => 86400, 'dependsOn' => ['key' => 'weixingzh_auto', 'value' => true]],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '登录提供商', 'fields' => [
|
||||
['key' => 'oauth_github_s', 'label' => 'GitHub登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_gitee_s', 'label' => 'Gitee登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_qq_s', 'label' => 'QQ登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_weixingzh_s', 'label' => '微信公众号登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_weixin_s', 'label' => '微信开放平台登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_weibo_s', 'label' => '微博登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_baidu_s', 'label' => '百度登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'oauth_alipay_s', 'label' => '支付宝登录', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'github', 'title' => 'GitHub配置', 'fields' => [
|
||||
['key' => 'github_appid', 'label' => 'Client ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'github_appkey', 'label' => 'Client Secret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'gitee', 'title' => 'Gitee配置', 'fields' => [
|
||||
['key' => 'gitee_appid', 'label' => 'Client ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'gitee_appkey', 'label' => 'Client Secret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'qq', 'title' => 'QQ配置', 'fields' => [
|
||||
['key' => 'qq_appid', 'label' => 'App ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'qq_appkey', 'label' => 'App Key', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'weixin', 'title' => '微信开放平台配置', 'fields' => [
|
||||
['key' => 'weixin_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weixin_appsecret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'weixingzh', 'title' => '微信公众号配置', 'fields' => [
|
||||
['key' => 'weixingzh_gzh_type', 'label' => '公众号类型', 'type' => 'radio', 'default' => 'service', 'options' => [
|
||||
['label' => '服务号', 'value' => 'service'],
|
||||
['label' => '订阅号', 'value' => 'subscribe'],
|
||||
]],
|
||||
['key' => 'weixingzh_token', 'label' => 'Token', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weixingzh_token_nocheck', 'label' => '不验证Token', 'type' => 'switch', 'default' => false, 'help' => '开发调试时可跳过 Token 验证'],
|
||||
['key' => 'weixingzh_encoding_aes_key', 'label' => 'EncodingAESKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weixingzh_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weixingzh_appsecret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weixingzh_code_qrcode', 'label' => '公众号二维码', 'type' => 'media', 'default' => '', 'help' => '引导用户扫码关注登录的二维码图片'],
|
||||
['key' => 'weixingzh_code_keyword', 'label' => '登录回复关键词', 'type' => 'input', 'default' => '登录', 'help' => '用户在公众号内回复此关键词触发登录验证码'],
|
||||
['key' => 'weixingzh_code_reply', 'label' => '验证码回复模板', 'type' => 'textarea', 'default' => '您的验证码是:%s,有效期 %d 分钟。', 'help' => '%s=验证码,%d=有效分钟数'],
|
||||
['key' => 'weixingzh_code_subscribe_reply', 'label' => '新关注回复模板', 'type' => 'textarea', 'default' => '欢迎关注!回复「%s」获取登录验证码。', 'help' => '%s=登录关键词'],
|
||||
['key' => 'weixingzh_subscribe_msg', 'label' => '关注后回复', 'type' => 'textarea', 'default' => '', 'help' => '用户关注公众号后的自动回复文案'],
|
||||
['key' => 'weixingzh_new_subscribe_msg', 'label' => '新用户关注回复', 'type' => 'textarea', 'default' => '', 'help' => '首次关注用户的专属回复文案,留空使用通用回复'],
|
||||
['key' => 'weixingzh_scan_msg', 'label' => '扫码回复', 'type' => 'textarea', 'default' => '', 'help' => '用户扫码关注公众号后的回复文案'],
|
||||
['key' => 'weixingzh_auto_reply_text', 'label' => '文本自动回复', 'type' => 'textarea', 'default' => '', 'help' => '收到文本消息的自动回复'],
|
||||
['key' => 'weixingzh_auto_reply_search', 'label' => '搜索回复', 'type' => 'textarea', 'default' => '', 'help' => '未匹配关键词时的搜索提示回复'],
|
||||
['key' => 'weixingzh_auto_reply_image', 'label' => '图片回复', 'type' => 'textarea', 'default' => '', 'help' => '收到图片消息的自动回复'],
|
||||
['key' => 'weixingzh_auto_reply_voice', 'label' => '语音回复', 'type' => 'textarea', 'default' => '', 'help' => '收到语音消息的自动回复'],
|
||||
['key' => 'weixingzh_auto_reply_default', 'label' => '默认回复', 'type' => 'textarea', 'default' => '暂不支持此类型的消息', 'help' => '其他未匹配消息类型的兜底回复'],
|
||||
['key' => 'weixingzh_gzh_menu', 'label' => '自定义菜单 JSON', 'type' => 'textarea', 'default' => '', 'help' => '微信公众号自定义菜单 JSON 配置,留空不创建菜单'],
|
||||
]],
|
||||
['key' => 'weibo', 'title' => '微博配置', 'fields' => [
|
||||
['key' => 'weibo_appid', 'label' => 'App Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'weibo_appkey', 'label' => 'App Secret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'baidu', 'title' => '百度配置', 'fields' => [
|
||||
['key' => 'baidu_appid', 'label' => 'API Key', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'baidu_appkey', 'label' => 'Secret Key', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'alipay', 'title' => '支付宝配置', 'fields' => [
|
||||
['key' => 'alipay_appid', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_privatekey', 'label' => '应用私钥', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'alipay_publickey', 'label' => '支付宝公钥', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'sms' => [
|
||||
'key' => 'sms',
|
||||
'title' => '短信接口',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'provider', 'title' => '供应商配置', 'fields' => [
|
||||
['key' => 'sms_enabled', 'label' => '启用短信', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'sms_provider', 'label' => '短信服务商', 'type' => 'select', 'default' => 'aliyun', 'options' => [
|
||||
['label' => '阿里云短信', 'value' => 'aliyun'],
|
||||
['label' => '腾讯云短信', 'value' => 'tencent'],
|
||||
['label' => '华为云短信', 'value' => 'huawei'],
|
||||
['label' => '自定义接口', 'value' => 'custom'],
|
||||
]],
|
||||
['key' => 'sms_number_limit_ch', 'label' => '仅发国内', 'type' => 'switch', 'default' => true, 'desc' => '开启后仅支持发送国内短信,关闭后可发送港澳台及国际短信'],
|
||||
['key' => 'sms_access_key', 'label' => 'AccessKey / SecretId / AppKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_access_secret', 'label' => 'AccessSecret / SecretKey / AppSecret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'aliyun', 'title' => '阿里云配置', 'fields' => [
|
||||
['key' => 'sms_ali_api_type', 'label' => '接口类型', 'type' => 'radio', 'default' => 'sms', 'options' => [
|
||||
['label' => '短信服务', 'value' => 'sms'],
|
||||
['label' => '号码认证', 'value' => 'verify'],
|
||||
]],
|
||||
['key' => 'sms_region_id', 'label' => 'RegionId', 'type' => 'input', 'default' => 'cn-hangzhou'],
|
||||
]],
|
||||
['key' => 'tencent', 'title' => '腾讯云配置', 'fields' => [
|
||||
['key' => 'sms_tencent_app_id', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_tencent_app_key', 'label' => 'AppKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_tencent_secret_id', 'label' => 'SecretId', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_tencent_secret_key', 'label' => 'SecretKey', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_tencent_region', 'label' => '地域', 'type' => 'input', 'default' => 'ap-guangzhou'],
|
||||
]],
|
||||
['key' => 'huawei', 'title' => '华为云配置', 'fields' => [
|
||||
['key' => 'sms_huawei_sender', 'label' => '通道号 (sender)', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_huawei_signature', 'label' => '短信签名', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_huawei_endpoint', 'label' => 'API Endpoint', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'custom', 'title' => '自定义接口配置', 'fields' => [
|
||||
['key' => 'sms_custom_url', 'label' => '请求 URL', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_custom_method', 'label' => '请求方法', 'type' => 'select', 'default' => 'POST', 'options' => [
|
||||
['label' => 'POST', 'value' => 'POST'],
|
||||
['label' => 'GET', 'value' => 'GET'],
|
||||
['label' => 'PUT', 'value' => 'PUT'],
|
||||
]],
|
||||
['key' => 'sms_custom_auth_type', 'label' => '鉴权方式', 'type' => 'select', 'default' => 'header', 'options' => [
|
||||
['label' => 'Header', 'value' => 'header'],
|
||||
]],
|
||||
['key' => 'sms_custom_auth_key', 'label' => '鉴权 Header 名', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_custom_auth_value', 'label' => '鉴权 Header 值', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_custom_headers', 'label' => '自定义 Headers', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'sms_custom_body', 'label' => '请求 Body', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'sms_custom_success_rule', 'label' => '成功判定规则', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'template', 'title' => '短信模板', 'fields' => [
|
||||
['key' => 'sms_sign_name', 'label' => '短信签名', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_template_code', 'label' => '验证码模板ID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'sms_code_length', 'label' => '验证码长度', 'type' => 'number', 'default' => 6, 'min' => 4, 'max' => 8],
|
||||
['key' => 'sms_code_expire', 'label' => '验证码有效期(秒)', 'type' => 'number', 'default' => 300, 'min' => 60, 'max' => 600],
|
||||
]],
|
||||
['key' => 'rate_limit', 'title' => '限流策略', 'fields' => [
|
||||
['key' => 'sms_rate_limit', 'label' => '发送间隔(秒)', 'type' => 'number', 'default' => 60, 'min' => 10, 'max' => 300],
|
||||
['key' => 'sms_daily_limit', 'label' => '每日发送上限', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 100],
|
||||
]],
|
||||
['key' => 'triggers', 'title' => '通知场景', 'fields' => [
|
||||
['key' => 'sms_notify_bind', 'label' => '绑定手机验证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sms_notify_login', 'label' => '短信登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'sms_notify_register', 'label' => '注册验证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sms_notify_reset_password', 'label' => '找回密码', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'signin' => [
|
||||
'key' => 'signin',
|
||||
'title' => '签到奖励',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'rules', 'title' => '签到规则', 'fields' => [
|
||||
['key' => 'signin_enabled', 'label' => '启用签到', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'signin_reward', 'label' => '每日签到奖励', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
|
||||
['key' => 'signin_consecutive_bonus', 'label' => '连续签到加成', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'signin_consecutive_days', 'label' => '加成周期', 'type' => 'number', 'default' => 7, 'min' => 2, 'max' => 30],
|
||||
['key' => 'signin_bonus_reward', 'label' => '周期奖励', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 100],
|
||||
]],
|
||||
['key' => 'display', 'title' => '签到展示', 'fields' => [
|
||||
['key' => 'signin_header_user_show', 'label' => '导航栏显示签到', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'signin_modal_auto_s', 'label' => '自动弹出签到', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'signin_modal_auto_expires', 'label' => '弹出间隔(天)', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 30],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'credits' => [
|
||||
'key' => 'credits',
|
||||
'title' => '积分设置',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '积分基础', 'fields' => [
|
||||
['key' => 'points_s', 'label' => '启用积分系统', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'points_pay_s', 'label' => '积分支付', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'points_rate', 'label' => '积分兑换比例', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 10000],
|
||||
['key' => 'points_name', 'label' => '积分名称', 'type' => 'input', 'default' => '积分'],
|
||||
['key' => 'points_decimal', 'label' => '积分小数位数', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 4],
|
||||
]],
|
||||
['key' => 'earn', 'title' => '积分获取', 'fields' => [
|
||||
['key' => 'points_earn_comment', 'label' => '评论奖励', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_earn_post', 'label' => '发布文章奖励', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_earn_login', 'label' => '每日登录奖励', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_earn_like_received', 'label' => '收到点赞奖励', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_earn_invite', 'label' => '邀请注册奖励', 'type' => 'number', 'default' => 10, 'min' => 0, 'max' => 1000],
|
||||
['key' => 'points_earn_daily_limit', 'label' => '每日获取上限', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 9999, 'help' => '0为不限制'],
|
||||
]],
|
||||
['key' => 'spend', 'title' => '积分消费', 'fields' => [
|
||||
['key' => 'points_spend_comment', 'label' => '评论消耗', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_spend_download', 'label' => '下载消耗', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'points_spend_view_hidden', 'label' => '查看隐藏内容消耗', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
]],
|
||||
['key' => 'transfer', 'title' => '积分转账', 'fields' => [
|
||||
['key' => 'points_transfer_s', 'label' => '启用积分转账', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'points_service_charge', 'label' => '转账手续费(%)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 50],
|
||||
['key' => 'points_transfer_min', 'label' => '最低转账金额', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 9999],
|
||||
]],
|
||||
['key' => 'deduction', 'title' => '积分抵扣', 'fields' => [
|
||||
['key' => 'points_deduction_s', 'label' => '启用积分抵扣', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'points_deduction_rate', 'label' => '抵扣比例(%)', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 100],
|
||||
['key' => 'points_deduction_max', 'label' => '最大抵扣比例(%)', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 100],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'points_show_balance', 'label' => '导航栏显示余额', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'points_show_log', 'label' => '显示积分记录', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'points_expire_days', 'label' => '积分有效期(天)', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为永久有效'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'invite' => [
|
||||
'key' => 'invite',
|
||||
'title' => '邀请码',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'basic', 'title' => '邀请设置', 'fields' => [
|
||||
['key' => 'invit_code_s', 'label' => '启用邀请码', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'invit_code_open_desc', 'label' => '开放注册提示', 'type' => 'input', 'default' => '邀请码为选填,没有邀请码也可以注册'],
|
||||
['key' => 'invit_code_must_desc', 'label' => '必填提示', 'type' => 'input', 'default' => '请输入邀请码'],
|
||||
['key' => 'invit_code_mode', 'label' => '邀请模式', 'type' => 'select', 'default' => 'open', 'options' => [
|
||||
['label' => '开放注册(邀请码选填)', 'value' => 'open'],
|
||||
['label' => '必须邀请码', 'value' => 'must'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'format', 'title' => '码面设置', 'fields' => [
|
||||
['key' => 'invit_code_length', 'label' => '邀请码长度', 'type' => 'number', 'default' => 8, 'min' => 4, 'max' => 32],
|
||||
['key' => 'invit_code_prefix', 'label' => '邀请码前缀', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'invit_code_max_use', 'label' => '单码最大使用次数', 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 9999],
|
||||
['key' => 'invit_code_expiry', 'label' => '有效期(天)', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '0为永久有效'],
|
||||
['key' => 'invit_code_batch', 'label' => '批量生成数量', 'type' => 'number', 'default' => 20, 'min' => 1, 'max' => 500],
|
||||
]],
|
||||
['key' => 'reward', 'title' => '邀请奖励', 'fields' => [
|
||||
['key' => 'invit_reward_points', 'label' => '邀请奖励积分', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 1000],
|
||||
['key' => 'invit_reward_level', 'label' => '邀请奖励等级', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 10],
|
||||
['key' => 'invit_reward_vip_days', 'label' => '奖励VIP天数', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 3650],
|
||||
['key' => 'invit_invitee_reward', 'label' => '被邀请人奖励', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'display', 'title' => '展示设置', 'fields' => [
|
||||
['key' => 'invit_code_show_count', 'label' => '显示已使用次数', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'invit_code_show_remain', 'label' => '显示剩余次数', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'features' => [
|
||||
'key' => 'features',
|
||||
'title' => '用户功能',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'center', 'title' => '用户中心', 'fields' => [
|
||||
['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,posts,comments,credits,favorites'],
|
||||
['key' => 'user_avatar_upload', 'label' => '头像上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_cover_upload', 'label' => '封面上传', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_social_links', 'label' => '社交链接', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_desc_std', 'label' => '默认签名', 'type' => 'input', 'default' => '这家伙很懒…', 'desc' => '用户未设置个人简介时显示的默认文案'],
|
||||
['key' => 'user_city_s', 'label' => '显示城市', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_join_day_my_name', 'label' => '站名', 'type' => 'input', 'default' => '本站', 'desc' => '用于「已加入 X 天」显示中的站点名称'],
|
||||
['key' => 'user_avatar_default', 'label' => '默认头像', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'user_cover_img', 'label' => '默认封面', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'user_rewrite_slug', 'label' => '用户页slug', 'type' => 'input', 'default' => 'user'],
|
||||
['key' => 'user_follow_s', 'label' => '启用关注功能', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_message_s', 'label' => '启用私信功能', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_online_s', 'label' => '显示在线状态', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_last_active_s', 'label' => '显示最后活跃', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
['key' => 'profile', 'title' => '资料页字段', 'fields' => [
|
||||
['key' => 'user_profile_nickname', 'label' => '昵称', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_email', 'label' => '邮箱', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_url', 'label' => '个人主页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_desc', 'label' => '个人简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_avatar', 'label' => '头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_cover', 'label' => '封面图', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_profile_qq', 'label' => 'QQ', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_wechat', 'label' => '微信', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_weibo', 'label' => '微博', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_github', 'label' => 'GitHub', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_bilibili', 'label' => 'B站', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_twitter', 'label' => 'Twitter', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_linkedin', 'label' => 'LinkedIn', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_facebook', 'label' => 'Facebook', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_telegram', 'label' => 'Telegram', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_douyin', 'label' => '抖音', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_profile_zhihu', 'label' => '知乎', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'card', 'title' => '用户卡片', 'fields' => [
|
||||
['key' => 'user_card_avatar', 'label' => '显示头像', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_card_name', 'label' => '显示用户名', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_card_desc', 'label' => '显示简介', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_card_count', 'label' => '显示统计', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_card_follow', 'label' => '显示关注按钮', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_card_position', 'label' => '卡片位置', 'type' => 'radio', 'default' => 'right', 'options' => [
|
||||
['label' => '右侧', 'value' => 'right'],
|
||||
['label' => '左侧', 'value' => 'left'],
|
||||
['label' => '顶部', 'value' => 'top'],
|
||||
]],
|
||||
['key' => 'user_card_style', 'label' => '卡片样式', 'type' => 'radio', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '简洁', 'value' => 'simple'],
|
||||
['label' => '详细', 'value' => 'detail'],
|
||||
]],
|
||||
['key' => 'user_card_bg', 'label' => '卡片背景', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'user_card_avatar_size', 'label' => '头像大小', 'type' => 'number', 'default' => 80, 'min' => 40, 'max' => 200],
|
||||
]],
|
||||
['key' => 'privacy', 'title' => '隐私设置', 'fields' => [
|
||||
['key' => 'user_privacy_posts', 'label' => '隐藏文章', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_comments', 'label' => '隐藏评论', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_favorites', 'label' => '隐藏收藏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_credits', 'label' => '隐藏积分', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_level', 'label' => '隐藏等级', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_badge', 'label' => '隐藏徽章', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_online', 'label' => '隐藏在线状态', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_last_active', 'label' => '隐藏最后活跃', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_ip', 'label' => '隐藏IP归属地', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_location', 'label' => '隐藏位置', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'rewards', 'title' => '打赏设置', 'fields' => [
|
||||
['key' => 'post_rewards_s', 'label' => '启用文章打赏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_rewards_text', 'label' => '打赏文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'post_rewards_img', 'label' => '打赏二维码', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'post_rewards_btn_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'],
|
||||
['key' => 'post_rewards_amounts', 'label' => '打赏金额', 'type' => 'input', 'default' => '5,10,20,50,100'],
|
||||
['key' => 'post_rewards_custom', 'label' => '自定义金额', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'verify' => [
|
||||
'key' => 'verify',
|
||||
'title' => '身份认证',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '公开展示', 'fields' => [
|
||||
['key' => 'user_verify_enabled', 'label' => '启用身份认证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_verify_public_display', 'label' => '公开页显示认证徽标', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_verify_badge_text', 'label' => '认证徽标文案', 'type' => 'input', 'default' => '已认证'],
|
||||
]],
|
||||
['key' => 'workflow', 'title' => '审核流程', 'fields' => [
|
||||
['key' => 'user_verify_accept_email_verified', 'label' => '邮箱验证通过视为已认证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_verify_require_manual_review', 'label' => '保留人工审核位', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_verify_apply_option', 'label' => '申请选项', 'type' => 'textarea', 'default' => "real_name|实名认证\norganization|企业认证"],
|
||||
]],
|
||||
['key' => 'restrict', 'title' => '限制设置', 'fields' => [
|
||||
['key' => 'user_verify_disable_desc', 'label' => '未认证限制说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'user_verify_limit_level', 'label' => '限制等级', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'user_verify_desc', 'label' => '认证说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'level' => [
|
||||
'key' => 'level',
|
||||
'title' => '用户等级',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '等级显示', 'fields' => [
|
||||
['key' => 'user_level_enabled', 'label' => '启用等级系统', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_level_public_display', 'label' => '公开页显示等级', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_level_prefix', 'label' => '等级前缀', 'type' => 'input', 'default' => 'Lv.'],
|
||||
['key' => 'user_level_color', 'label' => '等级颜色', 'type' => 'color', 'default' => '#f04494'],
|
||||
['key' => 'user_level_icon', 'label' => '等级图标', 'type' => 'icon', 'default' => 'fas fa-star'],
|
||||
]],
|
||||
['key' => 'rules', 'title' => '等级规则', 'fields' => [
|
||||
['key' => 'user_level_source', 'label' => '等级来源', 'type' => 'radio', 'default' => 'credits_balance'],
|
||||
['key' => 'user_level_thresholds', 'label' => '等级阈值', 'type' => 'textarea', 'default' => "1|0\n2|30\n3|100\n4|300"],
|
||||
['key' => 'user_level_max', 'label' => '最高等级', 'type' => 'number', 'default' => 10, 'min' => 1, 'max' => 99],
|
||||
['key' => 'user_level_decay', 'label' => '等级衰减', 'type' => 'switch', 'default' => false, 'help' => '长期不活跃自动降级'],
|
||||
['key' => 'user_level_decay_days', 'label' => '衰减周期(天)', 'type' => 'number', 'default' => 90, 'min' => 7, 'max' => 365],
|
||||
]],
|
||||
['key' => 'privilege', 'title' => '等级特权', 'fields' => [
|
||||
['key' => 'user_level_download_limit', 'label' => '下载限制加成', 'type' => 'switch', 'default' => false, 'help' => '高等级用户每日下载次数增加'],
|
||||
['key' => 'user_level_upload_limit', 'label' => '上传限制加成', 'type' => 'switch', 'default' => false, 'help' => '高等级用户可上传更大文件'],
|
||||
['key' => 'user_level_comment_cooldown', 'label' => '评论冷却缩短', 'type' => 'switch', 'default' => false, 'help' => '高等级用户评论间隔更短'],
|
||||
['key' => 'user_level_custom_title', 'label' => '自定义头衔', 'type' => 'switch', 'default' => false, 'help' => '允许高等级用户自定义头衔'],
|
||||
]],
|
||||
['key' => 'notify', 'title' => '升级通知', 'fields' => [
|
||||
['key' => 'user_level_notify', 'label' => '升级通知', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_level_notify_style', 'label' => '通知方式', 'type' => 'select', 'default' => 'toast', 'options' => [
|
||||
['label' => '弹窗提示', 'value' => 'toast'],
|
||||
['label' => '站内信', 'value' => 'message'],
|
||||
['label' => '两者都有', 'value' => 'both'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'badge' => [
|
||||
'key' => 'badge',
|
||||
'title' => '用户徽章',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '徽章展示', 'fields' => [
|
||||
['key' => 'user_badge_enabled', 'label' => '启用徽章系统', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_badge_public_display', 'label' => '公开页显示徽章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_badge_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8],
|
||||
]],
|
||||
['key' => 'dictionary', 'title' => '徽章字典', 'fields' => [
|
||||
['key' => 'user_badge_dictionary', 'label' => '徽章列表', 'type' => 'textarea', 'default' => "founder|创始成员|warning\nstaff|站务团队|info"],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'report' => [
|
||||
'key' => 'report',
|
||||
'title' => '禁封/举报',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'entry', 'title' => '前台入口', 'fields' => [
|
||||
['key' => 'user_report_enabled', 'label' => '启用举报/禁封结构', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_report_public_entry', 'label' => '公开主页展示举报入口', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_report_public_notice', 'label' => '公开显示限制提示', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_report_ban_notice_text', 'label' => '限制提示文案', 'type' => 'input', 'default' => '当前账号处于限制状态,部分互动能力已关闭。'],
|
||||
]],
|
||||
['key' => 'taxonomy', 'title' => '举报分类', 'fields' => [
|
||||
['key' => 'user_report_categories', 'label' => '举报分类', 'type' => 'textarea', 'default' => "spam|垃圾信息\nabuse|人身攻击\ncopyright|版权投诉"],
|
||||
]],
|
||||
['key' => 'ban', 'title' => '禁封设置', 'fields' => [
|
||||
['key' => 'ban_preset_reason', 'label' => '预设封禁原因', 'type' => 'textarea', 'default' => "spam|垃圾信息\nabuse|人身攻击\nviolation|违规操作"],
|
||||
['key' => 'ban_appeal_desc', 'label' => '申诉说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'ban_appeal_keys', 'label' => '申诉关键词', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'image', 'title' => '图片举报', 'fields' => [
|
||||
['key' => 'user_report_img_s', 'label' => '启用图片举报', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_report_desc', 'label' => '举报说明', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'wechat' => [
|
||||
'key' => 'wechat',
|
||||
'title' => '微信通知',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'account', 'title' => '公众号配置', 'fields' => [
|
||||
['key' => 'wechat_notify_enabled', 'label' => '启用微信通知', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'wechat_app_id', 'label' => 'AppID', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_app_secret', 'label' => 'AppSecret', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'verify', 'title' => '服务器验证', 'fields' => [
|
||||
['key' => 'wechat_token', 'label' => 'Token', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_encoding_aes_key', 'label' => 'EncodingAESKey', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'templates', 'title' => '模板消息', 'fields' => [
|
||||
['key' => 'wechat_template_comment', 'label' => '评论通知模板', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_template_reply', 'label' => '回复通知模板', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_template_system', 'label' => '系统通知模板', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'wechat_template_order', 'label' => '订单通知模板', 'type' => 'input', 'default' => ''],
|
||||
]],
|
||||
['key' => 'triggers', 'title' => '通知触发器', 'fields' => [
|
||||
['key' => 'wechat_notify_comment', 'label' => '新评论通知', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'wechat_notify_reply', 'label' => '评论回复通知', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'wechat_notify_system', 'label' => '系统通知', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'wechat_notify_order', 'label' => '订单通知', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'wechat_bind_entry', 'label' => '显示绑定入口', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user