From d267d64bdfb8d31c7835bc8d8269b1190a6d6f13 Mon Sep 17 00:00:00 2001 From: Dhrupo Nil Date: Thu, 16 Jul 2026 13:54:09 +0600 Subject: [PATCH] Abilities API: Avoid _doing_it_wrong() on REST lookups of unknown abilities. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Abilities REST controllers look up an ability (or category) by the name/slug supplied in the request URL and return a 404 when it is not found. They called wp_get_ability() / wp_get_ability_category() directly, which route through WP_Abilities_Registry::get_registered() — a method that intentionally triggers _doing_it_wrong() for an unknown name to catch developer misuse. Because the request name is client-controlled, a routine 404 for a non-existent ability fired a developer notice on every such request (even via the run endpoint's permission callback), polluting debug logs, surfacing in Query Monitor, and potentially corrupting the JSON response under a custom doing_it_wrong handler. Guard each lookup with the non-warning wp_has_ability() / wp_has_ability_category() existence check before calling the getter, so a missing ability yields a clean 404 without a _doing_it_wrong() notice. The registry getters continue to warn on direct misuse. Removes the now-unnecessary @expectedIncorrectUsage annotations from the affected not-found REST tests, which now assert a clean 404. Fixes #65644. --- .../class-wp-rest-abilities-v1-categories-controller.php | 2 +- .../class-wp-rest-abilities-v1-list-controller.php | 2 +- .../endpoints/class-wp-rest-abilities-v1-run-controller.php | 6 +++--- .../rest-api/wpRestAbilitiesV1CategoriesController.php | 3 +-- .../tests/rest-api/wpRestAbilitiesV1ListController.php | 6 ++---- .../tests/rest-api/wpRestAbilitiesV1RunController.php | 3 +-- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php index 2156109e53b32..abbce844ef52a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php @@ -142,7 +142,7 @@ public function get_items( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { - $category = wp_get_ability_category( $request['slug'] ); + $category = wp_has_ability_category( $request['slug'] ) ? wp_get_ability_category( $request['slug'] ) : null; if ( ! $category ) { return new WP_Error( 'rest_ability_category_not_found', diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php index 21b417e83a1db..97d97ce379e8d 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php @@ -156,7 +156,7 @@ public function get_items( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_item( $request ) { - $ability = wp_get_ability( $request['name'] ); + $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) { return new WP_Error( 'rest_ability_not_found', diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php index e1ba08f549e93..9ba76fc2407b7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php @@ -80,7 +80,7 @@ public function register_routes(): void { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function execute_ability( $request ) { - $ability = wp_get_ability( $request['name'] ); + $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability ) { return new WP_Error( 'rest_ability_not_found', @@ -141,7 +141,7 @@ public function validate_request_method( string $request_method, array $annotati * @return true|WP_Error True if the request has execution permission, WP_Error object otherwise. */ public function check_ability_permissions( $request ) { - $ability = wp_get_ability( $request['name'] ); + $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) { return new WP_Error( 'rest_ability_not_found', @@ -239,7 +239,7 @@ private function get_input_from_request( $request ) { * @return mixed Coerced input, or the raw input when it cannot be safely coerced. */ public function sanitize_input_for_ability( $input, $request ) { - $ability = wp_get_ability( $request['name'] ); + $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null; if ( ! $ability instanceof WP_Ability ) { return $input; } diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1CategoriesController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1CategoriesController.php index f52fa32543d47..4a26112d7fd5c 100644 --- a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1CategoriesController.php +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1CategoriesController.php @@ -219,8 +219,7 @@ public function test_get_item_with_selected_fields(): void { * Test getting a non-existent ability category returns 404. * * @ticket 64098 - * - * @expectedIncorrectUsage WP_Ability_Categories_Registry::get_registered + * @ticket 65644 */ public function test_get_item_not_found(): void { $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/categories/non-existent' ); diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php index 8361c750b266b..2fdfe9ae99d54 100644 --- a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php @@ -386,8 +386,7 @@ public function test_get_item_with_embed_context(): void { * Test getting a non-existent ability returns 404. * * @ticket 64098 - * - * @expectedIncorrectUsage WP_Abilities_Registry::get_registered + * @ticket 65644 */ public function test_get_item_not_found(): void { $request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/non/existent' ); @@ -768,8 +767,7 @@ public function test_ability_name_with_invalid_special_characters( string $name * Test extremely long ability names. * * @ticket 64098 - * - * @expectedIncorrectUsage WP_Abilities_Registry::get_registered + * @ticket 65644 */ public function test_extremely_long_ability_names(): void { // Create a very long but valid ability name diff --git a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php index 1d7d57d9aa59b..e8bc257b2a812 100644 --- a/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php +++ b/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php @@ -716,8 +716,7 @@ public function test_wp_error_return_handling(): void { * Test non-existent ability returns 404. * * @ticket 64098 - * - * @expectedIncorrectUsage WP_Abilities_Registry::get_registered + * @ticket 65644 */ public function test_execute_non_existent_ability(): void { $request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/non/existent/run' );