| @@ -6,27 +6,15 @@ | ||
| 6 | 6 | |
| 7 | 7 | defined('ABSPATH') || exit; |
| 8 | 8 | |
| 9 | 9 | /** |
| 10 | - * Dispatches an MCP write through the plugin's own REST API. | |
| 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. | |
| 11 | 13 | * |
| 12 | - * Configuration writes — event types, availability schedules — carry a lot of | |
| 13 | - * validation: slug uniqueness, timezone conversion of weekly hours, duration | |
| 14 | - * lists, host assignment rules, refusal to delete a schedule still in use. | |
| 15 | - * Reimplementing any of it in the MCP layer would create a second copy that | |
| 16 | - * drifts, and the first drift is a schedule an agent saved that the admin UI | |
| 17 | - * then refuses to load. | |
| 14 | + * Reads don't go through here: admin responses carry UI scaffolding an agent | |
| 15 | + * doesn't need, so reads use hand-built projections. | |
| 18 | 16 | * |
| 19 | - * So the writes go through `rest_do_request()` against the same | |
| 20 | - * `fluent-booking/v2` routes the admin SPA calls. The policy layer runs | |
| 21 | - * unchanged — verified: a host without permission gets 403 from an internal | |
| 22 | - * dispatch exactly as they would over HTTP — and so does every validation rule. | |
| 23 | - * | |
| 24 | - * Reads deliberately do NOT go through here. Admin responses are shaped for a | |
| 25 | - * UI and carry scaffolding an agent has no use for; paying for that in context | |
| 26 | - * on every call is the thing this whole design exists to avoid. Reads get | |
| 27 | - * hand-built projections instead. | |
| 28 | - * | |
| 29 | 17 | * @since 2.2.6 |
| 30 | 18 | */ |
| 31 | 19 | class RestBridge |
| 32 | 20 | { |
| @@ -41,14 +29,11 @@ | ||
| 41 | 29 | * an MCP error the agent can act on. |
| 42 | 30 | */ |
| 43 | 31 | public static function call($method, $route, $body = []) |
| 44 | 32 | { |
| 45 | - // The framework's Controller::validate() only re-throws its | |
| 46 | - // ValidationException when REST_REQUEST is set; without it the | |
| 47 | - // exception is swallowed and the controller runs on with invalid data. | |
| 48 | - // MCP always serves over /wp-json/ so the constant is always there — | |
| 49 | - // but a silent validation bypass is not a failure to discover in | |
| 50 | - // production, so refuse loudly instead of writing unvalidated data. | |
| 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. | |
| 51 | 36 | if (!defined('REST_REQUEST') || !REST_REQUEST) { |
| 52 | 37 | return MCPHelper::error( |
| 53 | 38 | 'not_a_rest_request', |
| 54 | 39 | __('Configuration writes are only available over the REST transport, because that is where validation runs.', 'fluent-booking') |
| @@ -60,11 +45,10 @@ | ||
| 60 | 45 | foreach ($body as $key => $value) { |
| 61 | 46 | $request->set_param($key, $value); |
| 62 | 47 | } |
| 63 | 48 | |
| 64 | - // The plugin's controllers read the body for non-GET verbs; setting it | |
| 65 | - // as JSON keeps nested arrays (weekly schedules, booking fields) intact | |
| 66 | - // rather than flattening them the way a form-encoded body would. | |
| 49 | + // A JSON body keeps nested arrays (weekly schedules, booking fields) | |
| 50 | + // intact; a form-encoded body would flatten them. | |
| 67 | 51 | if ($method !== 'GET') { |
| 68 | 52 | $request->set_header('content-type', 'application/json'); |
| 69 | 53 | $request->set_body(wp_json_encode($body)); |
| 70 | 54 | } |
| @@ -78,11 +62,10 @@ | ||
| 78 | 62 | return (array) $response->get_data(); |
| 79 | 63 | } |
| 80 | 64 | |
| 81 | 65 | /** |
| 82 | - * Turn a REST failure into an MCP error whose message is the one the admin | |
| 83 | - * UI would have shown, so the agent gets the real reason rather than | |
| 84 | - * "request failed". | |
| 66 | + * Turn a REST failure into an MCP error carrying the message the admin UI | |
| 67 | + * would have shown. | |
| 85 | 68 | * |
| 86 | 69 | * @return \WP_Error |
| 87 | 70 | */ |
| 88 | 71 | private static function translateError($response) |
| @@ -89,23 +72,18 @@ | ||
| 89 | 72 | { |
| 90 | 73 | $status = $response->get_status(); |
| 91 | 74 | $data = (array) $response->get_data(); |
| 92 | 75 | |
| 93 | - // Three payload shapes reach here, and none of them is the other two: | |
| 94 | - // | |
| 76 | + // Three payload shapes arrive here: | |
| 95 | 77 | // {message, errors} Controller::sendError() |
| 96 | 78 | // {code, message, data} a WP_Error, e.g. from a policy |
| 97 | 79 | // {field: {rule: message}} a 422 from the framework's validator |
| 98 | - // | |
| 99 | - // WP_Error::as_error() only understands the second and warns on the | |
| 100 | - // others, so the payload is read directly. | |
| 80 | + // WP_Error::as_error() only handles the second, so read the payload directly. | |
| 101 | 81 | $message = (string) Arr::get($data, 'message', ''); |
| 102 | 82 | $fieldErrors = self::flattenFieldErrors($data); |
| 103 | 83 | |
| 104 | 84 | if (!$message && $fieldErrors) { |
| 105 | - // Lead with the real reasons rather than "the request failed" — | |
| 106 | - // an agent that is told "Event title field is required" fixes its | |
| 107 | - // call in one step. | |
| 85 | + // The field reasons let the agent fix its call in one step. | |
| 108 | 86 | $message = implode(' ', array_values($fieldErrors)); |
| 109 | 87 | } |
| 110 | 88 | |
| 111 | 89 | if (!$message) { |
| @@ -138,10 +116,9 @@ | ||
| 138 | 116 | { |
| 139 | 117 | $errors = Arr::get($data, 'errors'); |
| 140 | 118 | |
| 141 | 119 | if (!is_array($errors)) { |
| 142 | - // A bare validator payload: every key is a field, and `message` is | |
| 143 | - // the only key that is not. | |
| 120 | + // A bare validator payload: every key except these is a field. | |
| 144 | 121 | $errors = $data; |
| 145 | 122 | unset($errors['message'], $errors['code'], $errors['data']); |
| 146 | 123 | } |
| 147 | 124 | |