From 2abf67f0c02c0886a81dd99342d70c42c9ed8962 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 7 Oct 2025 14:57:06 +0200 Subject: [PATCH 1/6] Add new `show_in_rest` arg to control REST API exposure --- includes/abilities-api.php | 3 +- .../class-wp-abilities-registry.php | 1 + includes/abilities-api/class-wp-ability.php | 32 +++++++++++++++++-- ...lass-wp-rest-abilities-list-controller.php | 10 ++++-- ...class-wp-rest-abilities-run-controller.php | 2 +- .../wpRestAbilitiesListController.php | 4 +++ .../rest-api/wpRestAbilitiesRunController.php | 16 ++++++++++ 7 files changed, 60 insertions(+), 8 deletions(-) diff --git a/includes/abilities-api.php b/includes/abilities-api.php index 4c892fb..fd68e50 100644 --- a/includes/abilities-api.php +++ b/includes/abilities-api.php @@ -25,7 +25,7 @@ * alphanumeric characters, dashes and the forward slash. * @param array $args An associative array of arguments for the ability. This should include * `label`, `description`, `input_schema`, `output_schema`, `execute_callback`, - * `permission_callback`, `annotations`, `meta`, and `ability_class`. + * `permission_callback`, `annotations`, `meta`, `show_in_rest`, and `ability_class`. * @return ?\WP_Ability An instance of registered ability on success, null on failure. * * @phpstan-param array{ @@ -37,6 +37,7 @@ * output_schema?: array, * annotations?: array, * meta?: array, + * show_in_rest?: bool, * ability_class?: class-string<\WP_Ability>, * ... * } $args diff --git a/includes/abilities-api/class-wp-abilities-registry.php b/includes/abilities-api/class-wp-abilities-registry.php index e7a970d..3161727 100644 --- a/includes/abilities-api/class-wp-abilities-registry.php +++ b/includes/abilities-api/class-wp-abilities-registry.php @@ -59,6 +59,7 @@ final class WP_Abilities_Registry { * output_schema?: array, * annotations?: array, * meta?: array, + * show_in_rest?: bool, * ability_class?: class-string<\WP_Ability>, * ... * } $args diff --git a/includes/abilities-api/class-wp-ability.php b/includes/abilities-api/class-wp-ability.php index 4224093..f81e9e4 100644 --- a/includes/abilities-api/class-wp-ability.php +++ b/includes/abilities-api/class-wp-ability.php @@ -116,6 +116,14 @@ class WP_Ability { */ protected $meta = array(); + /** + * Whether to show the ability in the REST API. + * + * @since n.e.x.t + * @var bool + */ + protected $show_in_rest = false; + /** * Constructor. * @@ -128,9 +136,9 @@ class WP_Ability { * @see wp_register_ability() * * @param string $name The name of the ability, with its namespace. - * @param array $args An associative array of arguments for the ability. This should - * include `label`, `description`, `input_schema`, `output_schema`, - * `execute_callback`, `permission_callback`, `annotations`, and `meta`. + * @param array $args An associative array of arguments for the ability. This should include + * `label`, `description`, `input_schema`, `output_schema`, `execute_callback`, + * `permission_callback`, `annotations`, `meta`, and `show_in_rest`. */ public function __construct( string $name, array $args ) { $this->name = $name; @@ -180,6 +188,7 @@ public function __construct( string $name, array $args ) { * output_schema?: array, * annotations?: array, * meta?: array, + * show_in_rest?: bool, * ..., * } $args */ @@ -234,6 +243,12 @@ protected function prepare_properties( array $args ): array { ); } + if ( isset( $args['show_in_rest'] ) && ! is_bool( $args['show_in_rest'] ) ) { + throw new \InvalidArgumentException( + esc_html__( 'The ability properties should provide a valid `show_in_rest` boolean.' ) + ); + } + // Set defaults for optional args. $args['annotations'] = wp_parse_args( $args['annotations'] ?? array(), @@ -321,6 +336,17 @@ public function get_meta(): array { return $this->meta; } + /** + * Checks whether the ability should be shown in the REST API. + * + * @since n.e.x.t + * + * @return bool True if the ability should be shown in the REST API, false otherwise. + */ + public function has_show_in_rest(): bool { + return $this->show_in_rest; + } + /** * Validates input data against the input schema. * diff --git a/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php b/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php index c996da5..fffd537 100644 --- a/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php +++ b/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php @@ -94,7 +94,12 @@ public function register_routes(): void { * @return \WP_REST_Response Response object on success. */ public function get_items( $request ) { - $abilities = wp_get_abilities(); + $abilities = array_filter( + wp_get_abilities(), + static function ( $ability ) { + return $ability->has_show_in_rest(); + } + ); // Handle pagination with explicit defaults. $params = $request->get_params(); @@ -150,8 +155,7 @@ public function get_items( $request ) { */ public function get_item( $request ) { $ability = wp_get_ability( $request->get_param( 'name' ) ); - - if ( ! $ability ) { + if ( ! $ability || ! $ability->has_show_in_rest() ) { return new \WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), diff --git a/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php b/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php index a54f357..638ec43 100644 --- a/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php +++ b/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php @@ -154,7 +154,7 @@ public function run_ability( $request ) { */ public function run_ability_permissions_check( $request ) { $ability = wp_get_ability( $request->get_param( 'name' ) ); - if ( ! $ability ) { + if ( ! $ability || ! $ability->has_show_in_rest() ) { return new \WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), diff --git a/tests/unit/rest-api/wpRestAbilitiesListController.php b/tests/unit/rest-api/wpRestAbilitiesListController.php index 72f428b..785a87f 100644 --- a/tests/unit/rest-api/wpRestAbilitiesListController.php +++ b/tests/unit/rest-api/wpRestAbilitiesListController.php @@ -124,6 +124,7 @@ private function register_test_abilities(): void { 'meta' => array( 'category' => 'math', ), + 'show_in_rest' => true, ) ); @@ -169,6 +170,7 @@ private function register_test_abilities(): void { 'meta' => array( 'category' => 'system', ), + 'show_in_rest' => true, ) ); @@ -183,6 +185,7 @@ private function register_test_abilities(): void { return "Result from ability {$i}"; }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); } @@ -442,6 +445,7 @@ public function test_ability_name_with_valid_special_characters(): void { return array( 'success' => true ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); diff --git a/tests/unit/rest-api/wpRestAbilitiesRunController.php b/tests/unit/rest-api/wpRestAbilitiesRunController.php index 957001d..d754987 100644 --- a/tests/unit/rest-api/wpRestAbilitiesRunController.php +++ b/tests/unit/rest-api/wpRestAbilitiesRunController.php @@ -121,6 +121,7 @@ private function register_test_abilities(): void { 'permission_callback' => static function () { return current_user_can( 'edit_posts' ); }, + 'show_in_rest' => true, ) ); @@ -163,6 +164,7 @@ private function register_test_abilities(): void { 'annotations' => array( 'readonly' => true, ), + 'show_in_rest' => true, ) ); @@ -190,6 +192,7 @@ private function register_test_abilities(): void { // Only allow if secret matches return isset( $input['secret'] ) && 'valid_secret' === $input['secret']; }, + 'show_in_rest' => true, ) ); @@ -203,6 +206,7 @@ private function register_test_abilities(): void { return null; }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -216,6 +220,7 @@ private function register_test_abilities(): void { return new \WP_Error( 'test_error', 'This is a test error' ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -232,6 +237,7 @@ private function register_test_abilities(): void { return 'not a number'; // Invalid - schema expects number }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -255,6 +261,7 @@ private function register_test_abilities(): void { 'annotations' => array( 'readonly' => true, ), + 'show_in_rest' => true, ) ); } @@ -315,6 +322,7 @@ public function test_regular_ability_requires_post(): void { return 'success'; }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -591,6 +599,7 @@ public function test_output_validation_failure_returns_error(): void { return array( 'wrong_field' => 'value' ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -632,6 +641,7 @@ public function test_input_validation_failure_returns_error(): void { return array( 'status' => 'success' ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -666,6 +676,7 @@ public function test_ability_without_annotations_defaults_to_post_method(): void return array( 'executed' => true ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -699,6 +710,7 @@ public function test_empty_input_handling(): void { 'annotations' => array( 'readonly' => true, ), + 'show_in_rest' => true, ) ); @@ -711,6 +723,7 @@ public function test_empty_input_handling(): void { return array( 'input_was_empty' => 0 === func_num_args() ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -783,6 +796,7 @@ public function test_php_type_strings_in_input(): void { return array( 'echo' => $input ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -826,6 +840,7 @@ public function test_mixed_encoding_in_input(): void { return array( 'echo' => $input ); }, 'permission_callback' => '__return_true', + 'show_in_rest' => true, ) ); @@ -885,6 +900,7 @@ public function test_invalid_http_methods( string $method ): void { return array( 'success' => true ); }, 'permission_callback' => '__return_true', // No permission requirements + 'show_in_rest' => true, ) ); From ac839111333573dc7677a6ab8241d2491833c458 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 7 Oct 2025 15:58:50 +0200 Subject: [PATCH 2/6] Add basic unit tests covering `has_show_in_rest` --- tests/unit/abilities-api/wpAbilitiesRegistry.php | 16 ++++++++++++++++ tests/unit/abilities-api/wpAbility.php | 3 --- tests/unit/abilities-api/wpRegisterAbility.php | 2 ++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/unit/abilities-api/wpAbilitiesRegistry.php b/tests/unit/abilities-api/wpAbilitiesRegistry.php index 328d694..85fbc57 100644 --- a/tests/unit/abilities-api/wpAbilitiesRegistry.php +++ b/tests/unit/abilities-api/wpAbilitiesRegistry.php @@ -62,6 +62,7 @@ public function set_up(): void { 'meta' => array( 'category' => 'math', ), + 'show_in_rest' => true, ); } @@ -297,6 +298,21 @@ public function test_register_invalid_meta_type() { $this->assertNull( $result ); } + /** + * Should reject ability registration with invalid show in REST type. + * + * @covers WP_Abilities_Registry::register + * @covers WP_Ability::prepare_properties + * + * @expectedIncorrectUsage WP_Abilities_Registry::register + */ + public function test_register_invalid_show_in_rest_type() { + self::$test_ability_args['show_in_rest'] = 5; + + $result = $this->registry->register( self::$test_ability_name, self::$test_ability_args ); + $this->assertNull( $result ); + } + /** * Should reject registration for already registered ability. * diff --git a/tests/unit/abilities-api/wpAbility.php b/tests/unit/abilities-api/wpAbility.php index 2ac5e21..f392340 100644 --- a/tests/unit/abilities-api/wpAbility.php +++ b/tests/unit/abilities-api/wpAbility.php @@ -36,9 +36,6 @@ public function set_up(): void { 'readonly' => true, 'destructive' => false, ), - 'meta' => array( - 'category' => 'math', - ), ); } diff --git a/tests/unit/abilities-api/wpRegisterAbility.php b/tests/unit/abilities-api/wpRegisterAbility.php index b4e7bd7..139e367 100644 --- a/tests/unit/abilities-api/wpRegisterAbility.php +++ b/tests/unit/abilities-api/wpRegisterAbility.php @@ -67,6 +67,7 @@ public function set_up(): void { 'meta' => array( 'category' => 'math', ), + 'show_in_rest' => true, ); } @@ -147,6 +148,7 @@ public function test_register_valid_ability(): void { $result->get_annotations() ); $this->assertSame( self::$test_ability_args['meta'], $result->get_meta() ); + $this->assertSame( self::$test_ability_args['show_in_rest'], $result->has_show_in_rest() ); $this->assertTrue( $result->check_permissions( array( From 7a3a2b0cc6f5fd09db03b8e7a8d2338616e42d4a Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 7 Oct 2025 16:04:21 +0200 Subject: [PATCH 3/6] Add more basic unit tests covering `has_show_in_rest` --- tests/unit/abilities-api/wpAbility.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/unit/abilities-api/wpAbility.php b/tests/unit/abilities-api/wpAbility.php index f392340..97a92d1 100644 --- a/tests/unit/abilities-api/wpAbility.php +++ b/tests/unit/abilities-api/wpAbility.php @@ -98,6 +98,7 @@ public function test_get_all_annotations_overridden() { $this->assertSame( $annotations, $ability->get_annotations() ); } + /** * Tests that invalid annotations throw an exception. */ @@ -115,6 +116,30 @@ public function test_annotations_throws_exception() { new WP_Ability( self::$test_ability_name, $args ); } + /** + * Tests that `show_in_rest` defaults to false when not provided. + */ + public function test_show_in_rest_defaults_to_false() { + $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties ); + + $this->assertFalse( $ability->has_show_in_rest(), '`show_in_rest` should default to false.' ); + } + + /** + * Tests that `show_in_rest` can be set to true. + */ + public function test_show_in_rest_can_be_set_to_true() { + $args = array_merge( + self::$test_ability_properties, + array( + 'show_in_rest' => true, + ) + ); + $ability = new WP_Ability( self::$test_ability_name, $args ); + + $this->assertTrue( $ability->has_show_in_rest(), '`show_in_rest` should be true.' ); + } + /** * Data provider for testing the execution of the ability. */ From 764e70e1a81ea256b3ba7aaf8b85e84892e4a658 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Tue, 7 Oct 2025 16:35:23 +0200 Subject: [PATCH 4/6] Improve test coverage for REST API --- .../wpRestAbilitiesListController.php | 29 ++++++++++++++++++- .../rest-api/wpRestAbilitiesRunController.php | 28 ++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/unit/rest-api/wpRestAbilitiesListController.php b/tests/unit/rest-api/wpRestAbilitiesListController.php index 785a87f..6c16e0b 100644 --- a/tests/unit/rest-api/wpRestAbilitiesListController.php +++ b/tests/unit/rest-api/wpRestAbilitiesListController.php @@ -174,6 +174,19 @@ private function register_test_abilities(): void { ) ); + // Ability that does not show in REST. + wp_register_ability( + 'test/not-show-in-rest', + array( + 'label' => 'Hidden from REST', + 'description' => 'It does not show in REST.', + 'execute_callback' => static function (): int { + return 0; + }, + 'permission_callback' => '__return_true', + ) + ); + // Register multiple abilities for pagination testing for ( $i = 1; $i <= 60; $i++ ) { wp_register_ability( @@ -209,6 +222,7 @@ public function test_get_items(): void { $ability_names = wp_list_pluck( $data, 'name' ); $this->assertContains( 'test/calculator', $ability_names ); $this->assertContains( 'test/system-info', $ability_names ); + $this->assertNotContains( 'test/not-show-in-rest', $ability_names ); } /** @@ -245,6 +259,19 @@ public function test_get_item_not_found(): void { $this->assertEquals( 'rest_ability_not_found', $data['code'] ); } + /** + * Test getting an ability that does not show in REST returns 404. + */ + public function test_get_item_not_show_in_rest(): void { + $request = new WP_REST_Request( 'GET', '/wp/v2/abilities/test/not-show-in-rest' ); + $response = $this->server->dispatch( $request ); + + $this->assertEquals( 404, $response->get_status() ); + + $data = $response->get_data(); + $this->assertEquals( 'rest_ability_not_found', $data['code'] ); + } + /** * Test permission check for listing abilities. */ @@ -272,7 +299,7 @@ public function test_pagination_headers(): void { $this->assertArrayHasKey( 'X-WP-Total', $headers ); $this->assertArrayHasKey( 'X-WP-TotalPages', $headers ); - $total_abilities = count( wp_get_abilities() ); + $total_abilities = count( wp_get_abilities() ) - 1; // Exclude the one that doesn't show in REST. $this->assertEquals( $total_abilities, (int) $headers['X-WP-Total'] ); $this->assertEquals( ceil( $total_abilities / 10 ), (int) $headers['X-WP-TotalPages'] ); } diff --git a/tests/unit/rest-api/wpRestAbilitiesRunController.php b/tests/unit/rest-api/wpRestAbilitiesRunController.php index d754987..fda7282 100644 --- a/tests/unit/rest-api/wpRestAbilitiesRunController.php +++ b/tests/unit/rest-api/wpRestAbilitiesRunController.php @@ -196,6 +196,19 @@ private function register_test_abilities(): void { ) ); + // Ability that does not show in REST. + wp_register_ability( + 'test/not-show-in-rest', + array( + 'label' => 'Hidden from REST', + 'description' => 'It does not show in REST.', + 'execute_callback' => static function (): int { + return 0; + }, + 'permission_callback' => '__return_true', + ) + ); + // Ability that returns null wp_register_ability( 'test/null-return', @@ -436,6 +449,21 @@ public function test_contextual_permission_check(): void { $this->assertEquals( 'Success: test data', $response->get_data() ); } + /** + * Test handling an ability that does not show in REST. + */ + public function test_do_not_show_in_rest(): void { + $request = new WP_REST_Request( 'POST', '/wp/v2/abilities/test/not-show-in-rest/run' ); + $request->set_header( 'Content-Type', 'application/json' ); + + $response = $this->server->dispatch( $request ); + + $this->assertEquals( 404, $response->get_status() ); + $data = $response->get_data(); + $this->assertEquals( 'rest_ability_not_found', $data['code'] ); + $this->assertEquals( 'Ability not found.', $data['message'] ); + } + /** * Test handling of null is a valid return value. */ From 4ff40cb250970c6e4013573bff25e9ba9c2e0814 Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Wed, 8 Oct 2025 09:18:11 +0200 Subject: [PATCH 5/6] Rename method to `show_in_rest()` and add additional test --- includes/abilities-api/class-wp-ability.php | 2 +- ...lass-wp-rest-abilities-list-controller.php | 4 ++-- ...class-wp-rest-abilities-run-controller.php | 2 +- tests/unit/abilities-api/wpAbility.php | 23 ++++++++++++++++--- .../unit/abilities-api/wpRegisterAbility.php | 2 +- 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/includes/abilities-api/class-wp-ability.php b/includes/abilities-api/class-wp-ability.php index f81e9e4..e4b6839 100644 --- a/includes/abilities-api/class-wp-ability.php +++ b/includes/abilities-api/class-wp-ability.php @@ -343,7 +343,7 @@ public function get_meta(): array { * * @return bool True if the ability should be shown in the REST API, false otherwise. */ - public function has_show_in_rest(): bool { + public function show_in_rest(): bool { return $this->show_in_rest; } diff --git a/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php b/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php index fffd537..8e43729 100644 --- a/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php +++ b/includes/rest-api/endpoints/class-wp-rest-abilities-list-controller.php @@ -97,7 +97,7 @@ public function get_items( $request ) { $abilities = array_filter( wp_get_abilities(), static function ( $ability ) { - return $ability->has_show_in_rest(); + return $ability->show_in_rest(); } ); @@ -155,7 +155,7 @@ static function ( $ability ) { */ public function get_item( $request ) { $ability = wp_get_ability( $request->get_param( 'name' ) ); - if ( ! $ability || ! $ability->has_show_in_rest() ) { + if ( ! $ability || ! $ability->show_in_rest() ) { return new \WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), diff --git a/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php b/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php index 638ec43..76675d3 100644 --- a/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php +++ b/includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php @@ -154,7 +154,7 @@ public function run_ability( $request ) { */ public function run_ability_permissions_check( $request ) { $ability = wp_get_ability( $request->get_param( 'name' ) ); - if ( ! $ability || ! $ability->has_show_in_rest() ) { + if ( ! $ability || ! $ability->show_in_rest() ) { return new \WP_Error( 'rest_ability_not_found', __( 'Ability not found.' ), diff --git a/tests/unit/abilities-api/wpAbility.php b/tests/unit/abilities-api/wpAbility.php index 97a92d1..2768df0 100644 --- a/tests/unit/abilities-api/wpAbility.php +++ b/tests/unit/abilities-api/wpAbility.php @@ -100,7 +100,7 @@ public function test_get_all_annotations_overridden() { } /** - * Tests that invalid annotations throw an exception. + * Tests that invalid `annotations` value throws an exception. */ public function test_annotations_throws_exception() { $args = array_merge( @@ -122,7 +122,7 @@ public function test_annotations_throws_exception() { public function test_show_in_rest_defaults_to_false() { $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties ); - $this->assertFalse( $ability->has_show_in_rest(), '`show_in_rest` should default to false.' ); + $this->assertFalse( $ability->show_in_rest(), '`show_in_rest` should default to false.' ); } /** @@ -137,7 +137,24 @@ public function test_show_in_rest_can_be_set_to_true() { ); $ability = new WP_Ability( self::$test_ability_name, $args ); - $this->assertTrue( $ability->has_show_in_rest(), '`show_in_rest` should be true.' ); + $this->assertTrue( $ability->show_in_rest(), '`show_in_rest` should be true.' ); + } + + /** + * Tests that invalid `show_in_rest` value throws an exception. + */ + public function test_show_in_rest_throws_exception() { + $args = array_merge( + self::$test_ability_properties, + array( + 'show_in_rest' => 5, + ) + ); + + $this->expectException( InvalidArgumentException::class ); + $this->expectExceptionMessage( 'The ability properties should provide a valid `show_in_rest` boolean.' ); + + new WP_Ability( self::$test_ability_name, $args ); } /** diff --git a/tests/unit/abilities-api/wpRegisterAbility.php b/tests/unit/abilities-api/wpRegisterAbility.php index 139e367..ecf0d48 100644 --- a/tests/unit/abilities-api/wpRegisterAbility.php +++ b/tests/unit/abilities-api/wpRegisterAbility.php @@ -148,7 +148,7 @@ public function test_register_valid_ability(): void { $result->get_annotations() ); $this->assertSame( self::$test_ability_args['meta'], $result->get_meta() ); - $this->assertSame( self::$test_ability_args['show_in_rest'], $result->has_show_in_rest() ); + $this->assertSame( self::$test_ability_args['show_in_rest'], $result->show_in_rest() ); $this->assertTrue( $result->check_permissions( array( From 31bd9f59a732c5904aa243933227a8486d0ecf1c Mon Sep 17 00:00:00 2001 From: Grzegorz Ziolkowski Date: Wed, 8 Oct 2025 10:11:59 +0200 Subject: [PATCH 6/6] Add developer documentation --- docs/1.intro.md | 5 +++-- docs/2.getting-started.md | 1 + docs/3.registering-abilities.md | 3 +++ docs/5.rest-api.md | 9 +++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/1.intro.md b/docs/1.intro.md index d985d57..5b9b327 100644 --- a/docs/1.intro.md +++ b/docs/1.intro.md @@ -12,7 +12,7 @@ It acts as a central registry, making it easier for different parts of WordPress - **Registry:** A central, singleton object (`WP_Abilities_Registry`) that holds all registered abilities. It provides methods for registering, unregistering, finding, and querying abilities. - **Callback:** The PHP function or method executed when an ability is called via `WP_Ability::execute()`. - **Schema:** JSON Schema definitions for an ability's expected input (`input_schema`) and its returned output (`output_schema`). This allows for validation and helps agents understand how to use the ability. -- **Permission Callback:** An optional function that determines if the current user can execute a specific ability. +- **Permission Callback:** An optional function that determines if the current user can execute a specific ability. - **Namespace:** The first part of an ability name (before the slash), typically matching the plugin or component name that registers the ability. ## Goals and Benefits @@ -76,7 +76,8 @@ function my_plugin_register_ability(){ 'execute_callback' => 'my_plugin_get_siteinfo', 'permission_callback' => function( $input ) { return current_user_can( 'manage_options' ); - } + }, + 'show_in_rest' => true, )); } ``` diff --git a/docs/2.getting-started.md b/docs/2.getting-started.md index 1b5b6c2..f024301 100644 --- a/docs/2.getting-started.md +++ b/docs/2.getting-started.md @@ -124,6 +124,7 @@ function my_plugin_register_abilities() { 'meta' => array( 'category' => 'site-info', ), + 'show_in_rest' => true, // Optional: expose via REST API ) ); } diff --git a/docs/3.registering-abilities.md b/docs/3.registering-abilities.md index 2e62c2e..04d491a 100644 --- a/docs/3.registering-abilities.md +++ b/docs/3.registering-abilities.md @@ -27,6 +27,9 @@ The `$args` array accepts the following keys: - The callback receives one optional argument: it can have any type as defined in the input schema (e.g., `array`, `object`, `string`, etc.). - The callback should return a boolean (`true` if the user has permission, `false` otherwise), or a `WP_Error` object on failure. - If the input does not validate against the input schema, the permission callback will not be called, and a `WP_Error` will be returned instead. +- `show_in_rest` (`boolean`, **Optional**): Whether to expose this ability via the REST API. Default: `false`. + - When `true`, the ability will be listed in REST API responses and can be executed via REST endpoints. + - When `false`, the ability will be hidden from REST API listings and cannot be executed via REST endpoints, but remains available for internal PHP usage. - `meta` (`array`, **Optional**): An associative array for storing arbitrary additional metadata about the ability. ## Ability ID Convention diff --git a/docs/5.rest-api.md b/docs/5.rest-api.md index 6686512..da3546a 100644 --- a/docs/5.rest-api.md +++ b/docs/5.rest-api.md @@ -6,6 +6,15 @@ The WordPress Abilities API provides REST endpoints that allow external systems Access to all Abilities REST API endpoints requires an authenticated user (see the [Authentication](#authentication) section). Access to execute individual Abilities is restricted based on the `permission_callback()` of the Ability. +## Controlling REST API Exposure + +By default, registered abilities are **not** exposed via the REST API. You can control whether an individual ability appears in the REST API by using the `show_in_rest` argument when registering the ability: + +- `show_in_rest => true`: The ability is listed in REST API responses and can be executed via REST endpoints. +- `show_in_rest => false` (default): The ability is hidden from REST API listings and cannot be executed via REST endpoints. The ability remains available for internal PHP usage via `wp_execute_ability()`. + +Abilities with `show_in_rest => false` will return a `rest_ability_not_found` error if accessed via REST endpoints. + ## Schema The Abilities API endpoints are available under the `/wp/v2/abilities` namespace.