Files
wordpress-theme-aksrt/src/Admin/GlobalController.php
T

470 lines
20 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Admin;
defined('ABSPATH') || exit;
class GlobalController extends ControllerBase {
use GlobalControllerPart1;
use GlobalControllerPart2;
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 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'],
]);
// 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'],
'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'],
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
],
]);
// 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\Settings::get_cms_features()]);
}
$params = $this->get_json_params($request);
$allowed = [
'resource_cpt_enabled' => 'bool',
'search_include_resource' => 'bool',
];
$sanitized = $this->sanitize_fields($params, $allowed);
$settings = \AKSRT\Settings\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\Settings::flush_settings_cache();
return $this->success(['message' => 'CMS 功能已保存', 'cms_features' => \AKSRT\Settings\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');
$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) {
$term = get_term($term_id);
$taxonomy = ($term && !is_wp_error($term)) ? (string) $term->taxonomy : 'category';
if ($taxonomy !== 'category') {
$taxonomy = 'category';
}
$args['tax_query'] = [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => [$term_id],
]];
}
$exclude_ids = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\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\Settings')
? \AKSRT\Settings\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);
$render_config = aksrt_resolve_post_list_render_config([
'context' => 'home',
]);
$render_config['skip_wrapper'] = true;
$html = aksrt_render_post_list($render_config, $query, false);
$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 {
$archive_type = sanitize_key((string) ($request->get_param('archive_type') ?: 'category'));
$archive_type = in_array($archive_type, ['category', 'tag'], true) ? $archive_type : 'category';
$term_id = absint($request->get_param('term_id'));
$page = max(1, absint($request->get_param('page')));
$page_settings = $archive_type === 'tag'
? \AKSRT\Settings\Settings::get_tag_page_settings()
: \AKSRT\Settings\Settings::get_category_page_settings();
$per_page = min(50, max(1, (int) ($page_settings['per_page'] ?? get_option('posts_per_page', 10))));
$sort = sanitize_key((string) ($request->get_param('sort') ?: 'date'));
$allowed_sorts = \AKSRT\Settings\Settings::homepage_allowed_sorts();
if (!in_array($sort, $allowed_sorts, true)) {
$sort = 'date';
}
$args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $per_page,
'paged' => $page,
'ignore_sticky_posts' => true,
];
$tax_query = [];
if ($term_id > 0) {
$tax_query[] = [
'taxonomy' => $archive_type === 'tag' ? 'post_tag' : 'category',
'field' => 'term_id',
'terms' => [$term_id],
];
}
$filter_cat = absint($request->get_param('filter_cat'));
$filter_tag = absint($request->get_param('filter_tag'));
if ($filter_cat > 0) {
$tax_query[] = ['taxonomy' => 'category', 'field' => 'term_id', 'terms' => [$filter_cat]];
}
if ($filter_tag > 0) {
$tax_query[] = ['taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => [$filter_tag]];
}
if ($tax_query !== []) {
$args['tax_query'] = $tax_query;
}
if (function_exists('aksrt_homepage_apply_named_sort_args')) {
$args = aksrt_homepage_apply_named_sort_args($args, $sort);
}
$query = new \WP_Query($args);
$render_config = aksrt_resolve_post_list_render_config(['context' => $archive_type]);
$render_config['skip_wrapper'] = true;
$html = aksrt_render_post_list($render_config, $query, false);
return $this->success([
'html' => $html,
'total' => (int) $query->found_posts,
'has_more' => $page < (int) $query->max_num_pages,
]);
}
public function rest_homepage_categories(): \WP_REST_Response {
$data = [];
$cats = get_categories(['hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC']);
foreach ($cats as $cat) {
$data[] = [
'id' => (int) $cat->term_id,
'term_id' => (int) $cat->term_id,
'name' => $cat->name,
'slug' => $cat->slug,
'count' => (int) $cat->count,
'parent' => (int) $cat->parent,
'taxonomy' => 'category',
];
}
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_tabbar(\WP_REST_Request $request): \WP_REST_Response {
if ($request->get_method() === 'GET') {
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\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\Settings')) {
\AKSRT\Settings\Settings::flush_settings_cache();
}
return rest_ensure_response(['code' => 'success', 'message' => __('Footer tabbar settings saved.', 'aksrt')]);
}
}