From 9bc856dd33216496fab63f4b000b7473703b4be8 Mon Sep 17 00:00:00 2001 From: jorgefilipecosta Date: Wed, 8 Apr 2026 15:48:47 +0000 Subject: [PATCH] Abilities: Strip internal schema keywords from abilities REST responses. Remove WordPress-internal properties (`sanitize_callback`, `validate_callback`, `arg_options`) from ability `input_schema` and `output_schema` fields in REST responses. These properties are used server-side but are not valid JSON Schema keywords and cause client-side validators to fail. Props jorgefilipecosta, ocean90, gziolo. Fixes #65035. Built from https://develop.svn.wordpress.org/trunk@62221 git-svn-id: http://core.svn.wordpress.org/trunk@61501 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- ...s-wp-rest-abilities-v1-list-controller.php | 83 ++++++++++++++++++- wp-includes/version.php | 2 +- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php index 6dfc540038..e3ce0c4f2e 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php @@ -215,6 +215,81 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller { return $schema; } + /** + * WordPress-internal schema keywords to strip from REST responses. + * + * @since 7.0.0 + * @var array + */ + private const INTERNAL_SCHEMA_KEYWORDS = array( + 'sanitize_callback' => true, + 'validate_callback' => true, + 'arg_options' => true, + ); + + /** + * Recursively removes WordPress-internal keywords from a schema. + * + * Ability schemas may include WordPress-internal properties like + * `sanitize_callback`, `validate_callback`, and `arg_options` that are + * used server-side but are not valid JSON Schema keywords. This method + * removes those specific keys so they are not exposed in REST responses. + * + * @since 7.0.0 + * + * @param array $schema The schema array. + * @return array The schema without WordPress-internal keywords. + */ + private function strip_internal_schema_keywords( array $schema ): array { + $schema = array_diff_key( $schema, self::INTERNAL_SCHEMA_KEYWORDS ); + + // Sub-schema maps: keys are user-defined, values are sub-schemas. + // Note: 'dependencies' values can also be property-dependency arrays + // (numeric arrays of strings) which are skipped via wp_is_numeric_array(). + foreach ( array( 'properties', 'patternProperties', 'definitions', 'dependencies' ) as $keyword ) { + if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) { + foreach ( $schema[ $keyword ] as $key => $child_schema ) { + if ( is_array( $child_schema ) && ! wp_is_numeric_array( $child_schema ) ) { + $schema[ $keyword ][ $key ] = $this->strip_internal_schema_keywords( $child_schema ); + } + } + } + } + + // Single sub-schema keywords. + foreach ( array( 'not', 'additionalProperties', 'additionalItems' ) as $keyword ) { + if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) { + $schema[ $keyword ] = $this->strip_internal_schema_keywords( $schema[ $keyword ] ); + } + } + + // Items: single schema or tuple array of schemas. + if ( isset( $schema['items'] ) ) { + if ( wp_is_numeric_array( $schema['items'] ) ) { + foreach ( $schema['items'] as $index => $item_schema ) { + if ( is_array( $item_schema ) ) { + $schema['items'][ $index ] = $this->strip_internal_schema_keywords( $item_schema ); + } + } + } elseif ( is_array( $schema['items'] ) ) { + $schema['items'] = $this->strip_internal_schema_keywords( $schema['items'] ); + } + } + + // Array-of-schemas keywords. + foreach ( array( 'anyOf', 'oneOf', 'allOf' ) as $keyword ) { + if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) { + foreach ( $schema[ $keyword ] as $index => $sub_schema ) { + if ( is_array( $sub_schema ) ) { + $schema[ $keyword ][ $index ] = $this->strip_internal_schema_keywords( $sub_schema ); + } + } + } + } + + return $schema; + } + /** * Prepares an ability for response. * @@ -230,8 +305,12 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller { 'label' => $ability->get_label(), 'description' => $ability->get_description(), 'category' => $ability->get_category(), - 'input_schema' => $this->normalize_schema_empty_object_defaults( $ability->get_input_schema() ), - 'output_schema' => $this->normalize_schema_empty_object_defaults( $ability->get_output_schema() ), + 'input_schema' => $this->strip_internal_schema_keywords( + $this->normalize_schema_empty_object_defaults( $ability->get_input_schema() ) + ), + 'output_schema' => $this->strip_internal_schema_keywords( + $this->normalize_schema_empty_object_defaults( $ability->get_output_schema() ) + ), 'meta' => $ability->get_meta(), ); diff --git a/wp-includes/version.php b/wp-includes/version.php index 42c64e1c43..1c11c6c548 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '7.1-alpha-62219'; +$wp_version = '7.1-alpha-62221'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.