['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\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\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')]); } }