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
[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
[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 = SchemaProvider::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 = SchemaProvider::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', ], ]); } }