190 lines
6.6 KiB
PHP
190 lines
6.6 KiB
PHP
<?php
|
|
/**
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Admin;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
abstract class ControllerBase {
|
|
|
|
/**
|
|
* REST API namespace (shared with 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(?\WP_REST_Request $request = null) {
|
|
if (!current_user_can('manage_options')) {
|
|
if ($request === null) {
|
|
return false;
|
|
}
|
|
return new \WP_Error('aksrt_admin_forbidden', __('无权执行此操作', 'aksrt'), ['status' => 403]);
|
|
}
|
|
if ($request !== null) {
|
|
$nonce = (string) $request->get_header('X-WP-Nonce');
|
|
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
|
|
return new \WP_Error('aksrt_rest_nonce', __('安全验证失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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 'textarea':
|
|
$out[$key] = sanitize_textarea_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;
|
|
case 'attachment_ids':
|
|
$ids = is_array($val) ? array_filter(array_map('absint', $val)) : [];
|
|
$out[$key] = array_values(array_unique($ids));
|
|
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 = 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 = $provider->sanitize_payload($domain, $payload, $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',
|
|
],
|
|
]);
|
|
}
|
|
}
|