'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[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'], ]); // Schema introspection (for frontend to discover available fields) register_rest_route(self::API_NAMESPACE, '/settings/schema/(?P[a-z_]+)', [ 'methods' => 'GET', 'callback' => [$this, 'rest_get_schema'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route(self::API_NAMESPACE, '/settings/schema/(?P[a-z_]+)/(?P
[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[a-z_]+)/(?P
[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\d+)/restore', [ 'methods' => 'POST', 'callback' => [$this, 'restore_setting_backup'], 'permission_callback' => [$this, 'check_permission'], ]); register_rest_route(self::API_NAMESPACE, '/settings/backups/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [$this, 'delete_setting_backup'], '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 = SchemaProvider::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\Settings::STANDALONE_OPTION_KEYS; $export['standalone'] = []; foreach ($standalone_keys as $key) { $val = get_option($key); if ($val !== false) { $export['standalone'][$key] = $val; } } $export = \AKSRT\Settings\Settings::redact_sensitive_settings($export); 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 = SchemaProvider::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. Secret-bearing options are never accepted from exports. $standalone = !empty($params['standalone']) && is_array($params['standalone']) ? \AKSRT\Settings\Settings::filter_sensitive_import($params['standalone']) : []; if ($standalone !== []) { foreach ($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\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 = SchemaProvider::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 = SchemaProvider::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\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_asset_account', $wpdb->prefix . 'aksrt_asset_transaction', $wpdb->prefix . 'aksrt_asset_entry', $wpdb->prefix . 'aksrt_signin_log', $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', ]; // 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]); } }