PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.0
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Abilities / AbilityError.php

AbilityError.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.0, at includes/Abilities/AbilityError.php

314 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Typed errors returned by BetterDocs abilities.
4 *
5 * @package BetterDocs
6 * @since 4.9.0
7 */
8
9 namespace WPDeveloper\BetterDocs\Abilities;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit; // Exit if accessed directly.
13 }
14
15 /**
16 * Factories for the typed errors an ability may return.
17 *
18 * Every factory produces a `\WP_Error` whose **code** is the error slug and
19 * whose **data** repeats that slug under `error`, alongside the fields that make
20 * the failure actionable. `MCPServer` serialises that data object as the
21 * `tools/call` error payload, so an AI client sees a machine-readable reason
22 * rather than a sentence it has to parse.
23 *
24 * Two conventions hold across every factory:
25 *
26 * - `status` is the HTTP status the same failure would carry over REST.
27 * - `fixable_by_agent` is true **only** when the fix is a tool call the agent can
28 * make on its own — enabling a setting, or resending with a corrected field.
29 * Anything needing a human (a capability grant, installing Pro) is false.
30 *
31 * @since 4.9.0
32 */
33 class AbilityError {
34
35 /**
36 * The caller lacks the capability the ability is gated on.
37 *
38 * @since 4.9.0
39 *
40 * @param string $capability Capability that was missing.
41 * @param string $what What the caller was trying to do, as a phrase ("create a doc category").
42 * @return \WP_Error
43 */
44 public static function capability_missing( $capability, $what ) {
45 return self::make(
46 'capability_missing',
47 sprintf(
48 /* translators: 1: capability name, 2: what the caller was trying to do. */
49 __( 'You need the "%1$s" capability to %2$s.', 'betterdocs' ),
50 $capability,
51 $what
52 ),
53 [
54 'capability' => $capability,
55 'fixable_by_agent' => false,
56 'fix_hint' => __( 'Ask an administrator to grant it (bd-get-status lists missing capabilities).', 'betterdocs' ),
57 'status' => 403
58 ]
59 );
60 }
61
62 /**
63 * The feature lives in BetterDocs Pro, which is not usable on this site.
64 *
65 * Never returned for an unlicensed-but-active Pro: that state is reported,
66 * not refused (ADR-004).
67 *
68 * @since 4.9.0
69 *
70 * @param array $pro_state Pro state array; its `state` key is echoed back.
71 * @param string $what Feature name, as a phrase ("Knowledge bases").
72 * @return \WP_Error
73 */
74 public static function pro_required( array $pro_state, $what ) {
75 $state = isset( $pro_state['state'] ) ? (string) $pro_state['state'] : 'pro_not_installed';
76
77 switch ( $state ) {
78 case 'pro_not_active':
79 /* translators: %s: feature name. */
80 $message = sprintf( __( '%s need BetterDocs Pro, which is installed but not active on this site.', 'betterdocs' ), $what );
81 break;
82 case 'pro_unlicensed':
83 /* translators: %s: feature name. */
84 $message = sprintf( __( '%s need BetterDocs Pro, which is active but has no valid licence on this site.', 'betterdocs' ), $what );
85 break;
86 default:
87 /* translators: %s: feature name. */
88 $message = sprintf( __( '%s need BetterDocs Pro, which is not installed on this site.', 'betterdocs' ), $what );
89 break;
90 }
91
92 return self::make(
93 'pro_required',
94 $message,
95 [
96 'state' => $state,
97 'requires_pro' => true,
98 'fixable_by_agent' => false,
99 'status' => 403
100 ]
101 );
102 }
103
104 /**
105 * The feature is available but a setting switches it off.
106 *
107 * The only error the agent can clear by itself: `fix` is a literal
108 * `bd-update-settings` call.
109 *
110 * @since 4.9.0
111 *
112 * @param string $setting Setting key that is off.
113 * @param array $fix_args Settings map that would turn it on, e.g. `[ 'multiple_kb' => true ]`.
114 * @param string $what Feature name, as a phrase ("Multiple Knowledge Base").
115 * @param string $state Pro state slug this refusal belongs to. Default 'pro_active_setting_off'.
116 * @return \WP_Error
117 */
118 public static function setting_disabled( $setting, array $fix_args, $what, $state = 'pro_active_setting_off' ) {
119 return self::make(
120 'setting_disabled',
121 sprintf(
122 /* translators: %s: feature name. */
123 __( '%s is off. Enable it and this will work (from the next request).', 'betterdocs' ),
124 $what
125 ),
126 [
127 'state' => $state,
128 'setting' => $setting,
129 'requires_pro' => 0 === strpos( $state, 'pro_' ),
130 'fixable_by_agent' => true,
131 'fix' => [
132 'tool' => 'bd-update-settings',
133 'args' => [ 'settings' => $fix_args ]
134 ],
135 'status' => 409
136 ]
137 );
138 }
139
140 /**
141 * An input field is missing, malformed, or outside the allowed set.
142 *
143 * @since 4.9.0
144 *
145 * @param string $field Field name the caller got wrong.
146 * @param string $message Why it is wrong, and what would be right.
147 * @param array|null $allowed Optional. The allowed values, when there is a closed set.
148 * @return \WP_Error
149 */
150 public static function invalid_input( $field, $message, $allowed = null ) {
151 $data = [
152 'field' => $field,
153 'fixable_by_agent' => true,
154 'status' => 400
155 ];
156
157 if ( null !== $allowed ) {
158 $data['allowed'] = array_values( (array) $allowed );
159 }
160
161 return self::make( 'invalid_input', $message, $data );
162 }
163
164 /**
165 * The addressed object does not exist.
166 *
167 * @since 4.9.0
168 *
169 * @param string $object_type Object type ("doc", "term", "faq").
170 * @param int|string $id Identifier that was looked up.
171 * @return \WP_Error
172 */
173 public static function not_found( $object_type, $id ) {
174 return self::make(
175 'not_found',
176 sprintf(
177 /* translators: 1: object type, 2: identifier. */
178 __( 'No %1$s found with the id %2$s.', 'betterdocs' ),
179 $object_type,
180 $id
181 ),
182 [
183 'object' => $object_type,
184 'id' => $id,
185 'fixable_by_agent' => false,
186 'status' => 404
187 ]
188 );
189 }
190
191 /**
192 * The request collides with something that already exists, or with the
193 * object's current state.
194 *
195 * @since 4.9.0
196 *
197 * @param string $message What collided.
198 * @param array $extra Extra typed fields to merge into the data object.
199 * @return \WP_Error
200 */
201 public static function conflict( $message, array $extra = [] ) {
202 return self::make(
203 'conflict',
204 $message,
205 array_merge(
206 [
207 'fixable_by_agent' => false,
208 'status' => 409
209 ],
210 $extra
211 )
212 );
213 }
214
215 /**
216 * Something BetterDocs called failed — a REST controller, a service class,
217 * or a `\Throwable` caught in `AbilityBase::execute_wrapper()`.
218 *
219 * The message is the real reason, not a generic one: without it a failed
220 * write is indistinguishable from a refused one at the client.
221 *
222 * @since 4.9.0
223 *
224 * @param string $message Underlying failure message.
225 * @param array $extra Extra typed fields to merge into the data object.
226 * @return \WP_Error
227 */
228 public static function upstream( $message, array $extra = [] ) {
229 return self::make(
230 'upstream_error',
231 $message,
232 array_merge(
233 [
234 'fixable_by_agent' => false,
235 'status' => 500
236 ],
237 $extra
238 )
239 );
240 }
241
242 /**
243 * A write tool was called with a read-only credential.
244 *
245 * @since 4.9.0
246 *
247 * @param string $tool MCP tool name that was refused.
248 * @return \WP_Error
249 */
250 public static function read_only( $tool ) {
251 return self::make(
252 'read_only_credential',
253 sprintf(
254 /* translators: %s: MCP tool name. */
255 __( 'The credential this connection uses is read-only, so "%s" cannot run.', 'betterdocs' ),
256 $tool
257 ),
258 [
259 'tool' => $tool,
260 'fixable_by_agent' => false,
261 'status' => 403
262 ]
263 );
264 }
265
266 /**
267 * The MCP client asked for a tool this site does not have.
268 *
269 * @since 4.9.0
270 *
271 * @param string $name Tool name that was asked for.
272 * @return \WP_Error
273 */
274 public static function unknown_tool( $name ) {
275 return self::make(
276 'unknown_tool',
277 sprintf(
278 /* translators: %s: MCP tool name. */
279 __( 'There is no tool called "%s" on this site. Call tools/list for the current catalog.', 'betterdocs' ),
280 $name
281 ),
282 [
283 'tool' => $name,
284 'fixable_by_agent' => false,
285 'status' => 404
286 ]
287 );
288 }
289
290 /**
291 * Build the `\WP_Error`, keeping the code and `data['error']` in step.
292 *
293 * @since 4.9.0
294 *
295 * @param string $code Error slug.
296 * @param string $message Human-readable reason.
297 * @param array $data Typed fields.
298 * @return \WP_Error
299 */
300 private static function make( $code, $message, array $data ) {
301 return new \WP_Error(
302 $code,
303 $message,
304 array_merge(
305 [
306 'error' => $code,
307 'message' => $message
308 ],
309 $data
310 )
311 );
312 }
313 }
314