PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
fluent-booking / app / Modules / MCP / Support / RestBridge.php

RestBridge.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 2.5.0, at app/Modules/MCP/Support/RestBridge.php

145 lines 4.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBooking\App\Modules\MCP\Support;
4
5 use FluentBooking\Framework\Support\Arr;
6
7 defined('ABSPATH') || exit;
8
9 /**
10 * Dispatches an MCP config write (event types, availability schedules) through
11 * the same fluent-booking/v2 routes the admin SPA calls, so the policies and
12 * validation rules run unchanged instead of being duplicated here.
13 *
14 * Reads don't go through here: admin responses carry UI scaffolding an agent
15 * doesn't need, so reads use hand-built projections.
16 *
17 * @since 2.2.6
18 */
19 class RestBridge
20 {
21 const NAMESPACE_PATH = '/fluent-booking/v2';
22
23 /**
24 * @param string $method GET|POST|PUT|DELETE
25 * @param string $route Path after the namespace, e.g. '/availability/3'.
26 * @param array $body Request parameters.
27 *
28 * @return array|\WP_Error The response data, or the failure translated into
29 * an MCP error the agent can act on.
30 */
31 public static function call($method, $route, $body = [])
32 {
33 // Controller::validate() only re-throws its ValidationException when
34 // REST_REQUEST is set. Without it the controller runs on with invalid
35 // data, so refuse rather than write unvalidated input.
36 if (!defined('REST_REQUEST') || !REST_REQUEST) {
37 return MCPHelper::error(
38 'not_a_rest_request',
39 __('Configuration writes are only available over the REST transport, because that is where validation runs.', 'fluent-booking')
40 );
41 }
42
43 $request = new \WP_REST_Request($method, self::NAMESPACE_PATH . $route);
44
45 foreach ($body as $key => $value) {
46 $request->set_param($key, $value);
47 }
48
49 // A JSON body keeps nested arrays (weekly schedules, booking fields)
50 // intact; a form-encoded body would flatten them.
51 if ($method !== 'GET') {
52 $request->set_header('content-type', 'application/json');
53 $request->set_body(wp_json_encode($body));
54 }
55
56 $response = rest_do_request($request);
57
58 if ($response->is_error()) {
59 return self::translateError($response);
60 }
61
62 return (array) $response->get_data();
63 }
64
65 /**
66 * Turn a REST failure into an MCP error carrying the message the admin UI
67 * would have shown.
68 *
69 * @return \WP_Error
70 */
71 private static function translateError($response)
72 {
73 $status = $response->get_status();
74 $data = (array) $response->get_data();
75
76 // Three payload shapes arrive here:
77 // {message, errors} Controller::sendError()
78 // {code, message, data} a WP_Error, e.g. from a policy
79 // {field: {rule: message}} a 422 from the framework's validator
80 // WP_Error::as_error() only handles the second, so read the payload directly.
81 $message = (string) Arr::get($data, 'message', '');
82 $fieldErrors = self::flattenFieldErrors($data);
83
84 if (!$message && $fieldErrors) {
85 // The field reasons let the agent fix its call in one step.
86 $message = implode(' ', array_values($fieldErrors));
87 }
88
89 if (!$message) {
90 $message = __('The request could not be completed.', 'fluent-booking');
91 }
92
93 if ($status === 401 || $status === 403) {
94 return MCPHelper::error('permission_denied', $message);
95 }
96
97 if ($status === 404) {
98 return MCPHelper::error('not_found', $message);
99 }
100
101 return MCPHelper::error(
102 $status === 422 ? 'validation_failed' : 'request_failed',
103 $message,
104 $fieldErrors ? ['field_errors' => $fieldErrors] : []
105 );
106 }
107
108 /**
109 * Reduce whichever error shape arrived to `field => message`.
110 *
111 * @param array $data
112 *
113 * @return array
114 */
115 private static function flattenFieldErrors($data)
116 {
117 $errors = Arr::get($data, 'errors');
118
119 if (!is_array($errors)) {
120 // A bare validator payload: every key except these is a field.
121 $errors = $data;
122 unset($errors['message'], $errors['code'], $errors['data']);
123 }
124
125 $flat = [];
126
127 foreach ((array) $errors as $field => $messages) {
128 if (is_string($messages)) {
129 $flat[$field] = $messages;
130 continue;
131 }
132
133 if (is_array($messages)) {
134 $first = reset($messages);
135
136 if (is_string($first)) {
137 $flat[$field] = $first;
138 }
139 }
140 }
141
142 return $flat;
143 }
144 }
145