feat: rewrite schema-driven widget system
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace AKSRT\WidgetSystem;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Public read-only endpoints for saved widget instances and quotes. */
|
||||
final class RestController extends \WP_REST_Controller {
|
||||
public function __construct() {
|
||||
$this->namespace = 'aksrt/v1';
|
||||
$this->rest_base = 'widgets';
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<widget>[a-z][a-z0-9_]*)/items', [
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_items'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'widget' => ['type' => 'string', 'required' => true, 'pattern' => '^[a-z][a-z0-9_]*$', 'validate_callback' => [$this, 'validate_widget']],
|
||||
'instance' => ['type' => 'string', 'required' => true, 'pattern' => '^aksrt_widget_[a-z0-9_]+-[1-9][0-9]*$'],
|
||||
'page' => ['type' => 'integer', 'default' => 1, 'minimum' => 1, 'maximum' => 1000],
|
||||
'segment' => ['type' => 'string', 'default' => '', 'pattern' => '^[0-9]{0,2}$'],
|
||||
],
|
||||
]);
|
||||
register_rest_route($this->namespace, '/' . $this->rest_base . '/quote', [
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'get_quote'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
public function validate_widget($value): bool {
|
||||
return is_string($value) && WidgetRegistry::get($value) !== null;
|
||||
}
|
||||
|
||||
/** @return \WP_REST_Response|\WP_Error */
|
||||
public function get_items(\WP_REST_Request $request) {
|
||||
$unknown = array_diff(array_keys($request->get_params()), ['widget', 'instance', 'page', 'segment']);
|
||||
if ($unknown) {
|
||||
return new \WP_Error('aksrt_widget_unknown_parameter', __('请求包含不支持的参数。', 'aksrt'), ['status' => 400, 'parameters' => array_values($unknown)]);
|
||||
}
|
||||
$slug = (string) $request->get_param('widget');
|
||||
if (!RendererRegistry::supports_items($slug)) {
|
||||
return new \WP_Error('aksrt_widget_items_unsupported', __('该小工具不提供动态列表。', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$instance_id = (string) $request->get_param('instance');
|
||||
$instance = $this->load_instance($slug, $instance_id);
|
||||
if (is_wp_error($instance)) {
|
||||
return $instance;
|
||||
}
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$segment = (string) $request->get_param('segment');
|
||||
$payload = PostRenderer::payload($instance, $slug, $page, $segment);
|
||||
return rest_ensure_response(['success' => true, 'code' => 'ok'] + $payload);
|
||||
}
|
||||
|
||||
/** @return \WP_REST_Response|\WP_Error */
|
||||
public function get_quote(\WP_REST_Request $request) {
|
||||
if ($request->get_params()) {
|
||||
return new \WP_Error('aksrt_widget_unknown_parameter', __('一言接口不接受查询参数。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$cached = get_transient('aksrt_widget_quote');
|
||||
if (is_array($cached) && !empty($cached['text'])) {
|
||||
return rest_ensure_response($cached);
|
||||
}
|
||||
$fallbacks = [__('保持好奇,持续创造。', 'aksrt'), __('知识因为分享而更有价值。', 'aksrt'), __('把复杂的事,做得清楚而简单。', 'aksrt')];
|
||||
$data = ['text' => $fallbacks[array_rand($fallbacks)], 'author' => 'AKSRT', 'source' => 'local'];
|
||||
$response = wp_remote_get('https://v1.hitokoto.cn/?encode=json', ['timeout' => 3, 'redirection' => 2, 'user-agent' => 'AKSRT Widget/1.0']);
|
||||
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
|
||||
$remote = json_decode((string) wp_remote_retrieve_body($response), true);
|
||||
if (is_array($remote) && !empty($remote['hitokoto'])) {
|
||||
$data = ['text' => sanitize_text_field((string) $remote['hitokoto']), 'author' => sanitize_text_field((string) ($remote['from_who'] ?? $remote['from'] ?? '')), 'source' => 'hitokoto'];
|
||||
}
|
||||
}
|
||||
set_transient('aksrt_widget_quote', $data, 10 * MINUTE_IN_SECONDS);
|
||||
return rest_ensure_response($data);
|
||||
}
|
||||
|
||||
/** @return array<string,mixed>|\WP_Error */
|
||||
private function load_instance(string $slug, string $instance_id) {
|
||||
$definition = WidgetRegistry::get($slug);
|
||||
if ($definition === null || !preg_match('/^' . preg_quote($definition->id_base(), '/') . '-([1-9][0-9]*)$/', $instance_id, $matches)) {
|
||||
return new \WP_Error('aksrt_widget_invalid_instance', __('小工具实例无效。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
$registered = false;
|
||||
foreach ($sidebars as $widgets) {
|
||||
if (is_array($widgets) && in_array($instance_id, $widgets, true)) {
|
||||
$registered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$registered) {
|
||||
return new \WP_Error('aksrt_widget_instance_not_registered', __('小工具实例未挂载。', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
$widget = new SchemaWidget($definition);
|
||||
$settings = $widget->get_settings();
|
||||
$number = (int) $matches[1];
|
||||
if (!is_array($settings) || !isset($settings[$number]) || !is_array($settings[$number])) {
|
||||
return new \WP_Error('aksrt_widget_instance_not_found', __('未找到小工具配置。', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
return $definition->normalize($settings[$number]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user