@@ -34,18 +34,6 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
3434 */
3535 protected $ rest_base = 'abilities ' ;
3636
37- /**
38- * Lookup map of allowed schema keywords for preparing ability schemas in REST responses.
39- *
40- * Keyword names are stored as keys so they can be matched with
41- * array_intersect_key(). Computed lazily on first use and reused while
42- * preparing nested schemas.
43- *
44- * @since 7.1.0
45- * @var array<string, true>
46- */
47- private array $ allowed_schema_keyword_lookup ;
48-
4937 /**
5038 * Registers the routes for abilities.
5139 *
@@ -205,154 +193,6 @@ public function get_item_permissions_check( $request ) {
205193 return current_user_can ( 'read ' );
206194 }
207195
208- /**
209- * Determines whether the value is an associative array.
210- *
211- * @since 7.1.0
212- *
213- * @param mixed $value Value.
214- * @return bool Whether it is associative array.
215- *
216- * @phpstan-assert-if-true array<string, mixed> $value
217- */
218- private function is_associative_array ( $ value ): bool {
219- return is_array ( $ value ) && ! wp_is_numeric_array ( $ value );
220- }
221-
222- /**
223- * Gets the allowed schema keywords for preparing ability schemas in REST responses.
224- *
225- * Uses the fuller draft-04 keyword set, not the smaller REST API subset.
226- * The published schema is consumed by clients that re-validate values
227- * against standard draft-04, so it keeps the keywords those validators
228- * expect.
229- *
230- * @since 7.1.0
231- *
232- * @return array<string, true> Allowed schema keywords.
233- */
234- private function get_allowed_schema_keywords_for_response (): array {
235- if ( ! isset ( $ this ->allowed_schema_keyword_lookup ) ) {
236- $ this ->allowed_schema_keyword_lookup = array_fill_keys ( wp_get_json_schema_allowed_keywords ( 'draft-04 ' ), true );
237- }
238-
239- return $ this ->allowed_schema_keyword_lookup ;
240- }
241-
242- /**
243- * Transforms an ability schema for REST response output.
244- *
245- * The input and output schemas are a public contract: REST clients (such as
246- * the `@wordpress/abilities` JS client) consume them as standard JSON Schema
247- * and validate ability input and output against them. The response must
248- * therefore use JSON Schema draft-04 forms that standard validators
249- * understand, not the WordPress-internal conventions that
250- * `rest_validate_value_from_schema()` also accepts on the server.
251- *
252- * Ability schemas may include WordPress-internal properties or unsupported
253- * schema keywords that should not be exposed in REST responses. This method
254- * strips keys not recognized by the REST API schema handling. It also
255- * converts empty array defaults to objects when the schema type is 'object'
256- * to ensure proper JSON serialization as {} instead of [], and normalizes
257- * the `required` keyword from the draft-03 per-property boolean form into
258- * the draft-04 array of property names.
259- *
260- * @since 7.1.0
261- *
262- * @param array<string, mixed> $schema The schema array.
263- * @return array<string, mixed> The transformed schema.
264- */
265- private function prepare_schema_for_response ( array $ schema ): array {
266- if ( isset ( $ schema ['type ' ] ) && 'object ' === $ schema ['type ' ] && isset ( $ schema ['default ' ] ) ) {
267- $ default = $ schema ['default ' ];
268- if ( is_array ( $ default ) && empty ( $ default ) ) {
269- $ schema ['default ' ] = (object ) $ default ;
270- }
271- }
272-
273- $ schema = array_intersect_key ( $ schema , $ this ->get_allowed_schema_keywords_for_response () );
274-
275- // Collect draft-03 per-property `required: true` flags into a draft-04
276- // `required` array of property names on the parent object schema.
277- //
278- // This mirrors rest_validate_object_value_from_schema(), where a draft-04
279- // `required` array takes precedence: when one is present, per-property
280- // booleans are ignored during validation. They are therefore left out of
281- // the array here as well (but still stripped from the output) so the
282- // published schema describes exactly what gets enforced.
283- if ( isset ( $ schema ['properties ' ] ) && is_array ( $ schema ['properties ' ] ) ) {
284- $ has_required_array = isset ( $ schema ['required ' ] ) && is_array ( $ schema ['required ' ] );
285- $ required = array ();
286- foreach ( $ schema ['properties ' ] as $ property => &$ property_schema ) {
287- if ( $ this ->is_associative_array ( $ property_schema ) && isset ( $ property_schema ['required ' ] ) && is_bool ( $ property_schema ['required ' ] ) ) {
288- if ( ! $ has_required_array && true === $ property_schema ['required ' ] ) {
289- $ required [] = (string ) $ property ;
290- }
291- unset( $ property_schema ['required ' ] );
292- }
293- }
294- unset( $ property_schema );
295-
296- // Property keys are unique, so the collected list needs no deduplication.
297- // When a draft-04 array is already present, leave it untouched.
298- if ( ! $ has_required_array && count ( $ required ) > 0 ) {
299- $ schema ['required ' ] = $ required ;
300- }
301- }
302-
303- // A boolean `required` outside of an object's property list has no draft-04
304- // equivalent, so drop it rather than emit an invalid keyword.
305- if ( isset ( $ schema ['required ' ] ) && is_bool ( $ schema ['required ' ] ) ) {
306- unset( $ schema ['required ' ] );
307- }
308-
309- // Sub-schema maps: keys are user-defined, values are sub-schemas.
310- // Note: 'dependencies' values can also be property-dependency arrays
311- // (numeric arrays of strings) which are skipped via wp_is_numeric_array().
312- foreach ( array ( 'properties ' , 'patternProperties ' , 'definitions ' , 'dependencies ' ) as $ keyword ) {
313- if ( isset ( $ schema [ $ keyword ] ) && is_array ( $ schema [ $ keyword ] ) ) {
314- foreach ( $ schema [ $ keyword ] as $ key => $ child_schema ) {
315- if ( $ this ->is_associative_array ( $ child_schema ) ) {
316- $ schema [ $ keyword ][ $ key ] = $ this ->prepare_schema_for_response ( $ child_schema );
317- }
318- }
319- }
320- }
321-
322- // Single sub-schema keywords.
323- foreach ( array ( 'not ' , 'additionalProperties ' , 'additionalItems ' ) as $ keyword ) {
324- if ( isset ( $ schema [ $ keyword ] ) && $ this ->is_associative_array ( $ schema [ $ keyword ] ) ) {
325- $ schema [ $ keyword ] = $ this ->prepare_schema_for_response ( $ schema [ $ keyword ] );
326- }
327- }
328-
329- // Items: single schema or tuple array of schemas.
330- if ( isset ( $ schema ['items ' ] ) && is_array ( $ schema ['items ' ] ) ) {
331- if ( $ this ->is_associative_array ( $ schema ['items ' ] ) ) {
332- $ schema ['items ' ] = $ this ->prepare_schema_for_response ( $ schema ['items ' ] );
333- } else {
334- foreach ( $ schema ['items ' ] as $ index => $ item_schema ) {
335- if ( $ this ->is_associative_array ( $ item_schema ) ) {
336- $ schema ['items ' ][ $ index ] = $ this ->prepare_schema_for_response ( $ item_schema );
337- }
338- }
339- }
340- }
341-
342- // Array-of-schemas keywords.
343- foreach ( array ( 'anyOf ' , 'oneOf ' , 'allOf ' ) as $ keyword ) {
344- if ( isset ( $ schema [ $ keyword ] ) && is_array ( $ schema [ $ keyword ] ) ) {
345- foreach ( $ schema [ $ keyword ] as $ index => $ sub_schema ) {
346- if ( $ this ->is_associative_array ( $ sub_schema ) ) {
347- $ schema [ $ keyword ][ $ index ] = $ this ->prepare_schema_for_response ( $ sub_schema );
348- }
349- }
350- }
351- }
352-
353- return $ schema ;
354- }
355-
356196 /**
357197 * Prepares an ability for response.
358198 *
@@ -368,8 +208,8 @@ public function prepare_item_for_response( $ability, $request ) {
368208 'label ' => $ ability ->get_label (),
369209 'description ' => $ ability ->get_description (),
370210 'category ' => $ ability ->get_category (),
371- 'input_schema ' => $ this -> prepare_schema_for_response ( $ ability ->get_input_schema () ),
372- 'output_schema ' => $ this -> prepare_schema_for_response ( $ ability ->get_output_schema () ),
211+ 'input_schema ' => wp_prepare_json_schema_for_client ( $ ability ->get_input_schema () ),
212+ 'output_schema ' => wp_prepare_json_schema_for_client ( $ ability ->get_output_schema () ),
373213 'meta ' => $ ability ->get_meta (),
374214 );
375215
0 commit comments