Make WordPress Core

Changeset 62729


Ignore:
Timestamp:
07/14/2026 09:10:53 AM (4 weeks ago)
Author:
gziolo
Message:

Add public meta flag to control exposure defaults for abilities

Introduce a public flag in ability metadata as a single high-level control over client exposure. When set, public seeds the default for channel-specific flags such as show_in_rest: an explicit show_in_rest value wins, then public, then the built-in default of false. This lets an ability opt in to all channels at once, while keeping the ability to opt out of a single channel.

The public value is stored in the ability metadata and declared in the REST item schema, so other channels such as MCP and AI agents can read it through the wp_register_ability_args filter.

Props iamadisingh, gziolo.
Fixes #65568.

Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/abilities-api.php

    r62548 r62729  
    265265 *                                          will have no additional effect on its environment.
    266266 *         }
     267 *         @type bool                     $public       Optional. Whether the ability is intended to be publicly
     268 *                                                      available to clients. When true, channel-specific exposure
     269 *                                                      flags such as `$show_in_rest` default to true. An explicitly
     270 *                                                      set channel flag always takes precedence.
    267271 *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API.
    268272 *                                                      When true, the ability can be invoked via HTTP requests.
    269  *                                                      Default false.
     273 *                                                      Default is the value of `$public` when set, false otherwise.
    270274 *     }
    271275 *     @type string               $ability_class       Optional. Fully-qualified custom class name to instantiate
  • trunk/src/wp-includes/abilities-api/class-wp-abilities-registry.php

    r62094 r62729  
    7272         *                                          will have no additional effect on its environment.
    7373         *         }
    74          *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API. Default false.
     74         *         @type bool                     $public       Optional. Whether the ability is intended to be publicly
     75         *                                                      available to clients. When true, channel-specific exposure
     76         *                                                      flags such as `$show_in_rest` default to true. An explicitly
     77         *                                                      set channel flag always takes precedence.
     78         *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API.
     79         *                                                      Default is the value of `$public` when set, false otherwise.
    7580         *     }
    7681         *     @type string               $ability_class         Optional. Custom class to instantiate instead of WP_Ability.
     
    121126                 *
    122127                 *         @type array<string, bool|string> $annotations  Optional. Annotation metadata for the ability.
    123                  *         @type bool                       $show_in_rest Optional. Whether to expose this ability in the REST API. Default false.
     128                 *         @type bool                       $public       Optional. Whether the ability is intended to be
     129                 *                                                        publicly available to clients. Seeds the default for
     130                 *                                                        channel-specific exposure flags like `$show_in_rest`.
     131                 *         @type bool                       $show_in_rest Optional. Whether to expose this ability in the REST API.
     132                 *                                                        Default is the value of `$public` when set, false otherwise.
    124133                 *     }
    125134                 *     @type string               $ability_class         Optional. Custom class to instantiate instead of WP_Ability.
  • trunk/src/wp-includes/abilities-api/class-wp-ability.php

    r62418 r62729  
    161161         *                                          will have no additional effect on its environment.
    162162         *         }
    163          *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API. Default false.
     163         *         @type bool                     $public       Optional. Whether the ability is intended to be publicly
     164         *                                                      available to clients. When true, channel-specific exposure
     165         *                                                      flags such as `$show_in_rest` default to true. An explicitly
     166         *                                                      set channel flag always takes precedence.
     167         *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API.
     168         *                                                      Default is the value of `$public` when set, false otherwise.
    164169         *     }
    165170         * }
     
    225230         *                                          will have no additional effect on its environment.
    226231         *         }
    227          *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API. Default false.
     232         *         @type bool                     $public       Optional. Whether the ability is intended to be publicly
     233         *                                                      available to clients. When true, channel-specific exposure
     234         *                                                      flags such as `$show_in_rest` default to true. An explicitly
     235         *                                                      set channel flag always takes precedence.
     236         *         @type bool                     $show_in_rest Optional. Whether to expose this ability in the REST API.
     237         *                                                      Default is the value of `$public` when set, false otherwise.
    228238         *     }
    229239         * }
     
    253263         *                                          will have no additional effect on its environment.
    254264         *         }
    255          *         @type bool                     $show_in_rest Whether to expose this ability in the REST API. Default false.
     265         *         @type bool                     $public       Whether the ability is intended to be publicly available
     266         *                                                      to clients. Only present when provided during registration.
     267         *         @type bool                     $show_in_rest Whether to expose this ability in the REST API.
    256268         *     }
    257269         * }
     
    323335                }
    324336
     337                if ( isset( $args['meta']['public'] ) && ! is_bool( $args['meta']['public'] ) ) {
     338                        throw new InvalidArgumentException(
     339                                __( 'The ability meta should provide a valid `public` boolean.' )
     340                        );
     341                }
     342
    325343                // Set defaults for optional meta.
    326                 $args['meta']                = wp_parse_args(
     344                $args['meta'] = wp_parse_args(
    327345                        $args['meta'] ?? array(),
    328346                        array(
    329                                 'annotations'  => static::$default_annotations,
    330                                 'show_in_rest' => self::DEFAULT_SHOW_IN_REST,
     347                                'annotations' => static::$default_annotations,
    331348                        )
    332349                );
     350
     351                /*
     352                 * Resolve `show_in_rest` from most specific to least specific: an explicit
     353                 * `show_in_rest` value wins, then the high-level `public` flag seeds the
     354                 * default, then the built-in default applies.
     355                 */
     356                $args['meta']['show_in_rest'] = $args['meta']['show_in_rest'] ?? $args['meta']['public'] ?? self::DEFAULT_SHOW_IN_REST;
     357
    333358                $args['meta']['annotations'] = wp_parse_args(
    334359                        $args['meta']['annotations'],
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php

    r62591 r62729  
    312312                                                        ),
    313313                                                        'additionalProperties' => true,
     314                                                ),
     315                                                'public'      => array(
     316                                                        'description' => __( 'Whether the ability author intends the ability to be publicly available to clients. Only present when set by the ability author.' ),
     317                                                        'type'        => 'boolean',
    314318                                                ),
    315319                                        ),
  • trunk/tests/phpunit/tests/abilities-api/wpAbilitiesRegistry.php

    r62094 r62729  
    359359        }
    360360
    361 
    362361        /**
    363362         * Should reject ability registration with invalid `annotations` type.
     
    406405        public function test_register_invalid_show_in_rest_type() {
    407406                self::$test_ability_args['meta']['show_in_rest'] = 5;
     407
     408                $result = $this->registry->register( self::$test_ability_name, self::$test_ability_args );
     409                $this->assertNull( $result );
     410        }
     411
     412        /**
     413         * Should reject ability registration with invalid public type.
     414         *
     415         * @ticket 65568
     416         *
     417         * @covers WP_Abilities_Registry::register
     418         * @covers WP_Ability::prepare_properties
     419         *
     420         * @expectedIncorrectUsage WP_Abilities_Registry::register
     421         */
     422        public function test_register_invalid_public_type() {
     423                self::$test_ability_args['meta']['public'] = 5;
    408424
    409425                $result = $this->registry->register( self::$test_ability_name, self::$test_ability_args );
  • trunk/tests/phpunit/tests/abilities-api/wpAbility.php

    r62441 r62729  
    259259                $this->expectException( InvalidArgumentException::class );
    260260                $this->expectExceptionMessage( 'The ability meta should provide a valid `show_in_rest` boolean.' );
     261
     262                new WP_Ability( self::$test_ability_name, $args );
     263        }
     264
     265        /**
     266         * Tests that `public` metadata seeds `show_in_rest` to true when it is not set explicitly.
     267         *
     268         * @ticket 65568
     269         */
     270        public function test_meta_public_true_defaults_show_in_rest_to_true() {
     271                $args    = array_merge(
     272                        self::$test_ability_properties,
     273                        array(
     274                                'meta' => array(
     275                                        'public' => true,
     276                                ),
     277                        )
     278                );
     279                $ability = new WP_Ability( self::$test_ability_name, $args );
     280
     281                $this->assertTrue(
     282                        $ability->get_meta_item( 'show_in_rest' ),
     283                        '`show_in_rest` metadata should default to the `public` value.'
     284                );
     285                $this->assertTrue(
     286                        $ability->get_meta_item( 'public' ),
     287                        '`public` metadata should be stored as provided.'
     288                );
     289        }
     290
     291        /**
     292         * Tests that an explicit `show_in_rest` value of false wins over `public` set to true.
     293         *
     294         * @ticket 65568
     295         */
     296        public function test_meta_explicit_show_in_rest_false_wins_over_public_true() {
     297                $args    = array_merge(
     298                        self::$test_ability_properties,
     299                        array(
     300                                'meta' => array(
     301                                        'public'       => true,
     302                                        'show_in_rest' => false,
     303                                ),
     304                        )
     305                );
     306                $ability = new WP_Ability( self::$test_ability_name, $args );
     307
     308                $this->assertFalse(
     309                        $ability->get_meta_item( 'show_in_rest' ),
     310                        'An explicit `show_in_rest` value of false should win over `public` set to true.'
     311                );
     312        }
     313
     314        /**
     315         * Tests that `public` metadata is not added to the stored meta when not provided.
     316         *
     317         * @ticket 65568
     318         */
     319        public function test_meta_public_is_not_defaulted_when_unset() {
     320                $ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties );
     321
     322                $this->assertArrayNotHasKey(
     323                        'public',
     324                        $ability->get_meta(),
     325                        '`public` metadata should only be present when provided during registration.'
     326                );
     327                $this->assertFalse(
     328                        $ability->get_meta_item( 'show_in_rest' ),
     329                        '`show_in_rest` metadata should still default to false.'
     330                );
     331        }
     332
     333        /**
     334         * Tests that invalid `public` value throws an exception.
     335         *
     336         * @ticket 65568
     337         */
     338        public function test_meta_public_throws_exception_for_non_boolean() {
     339                $args = array_merge(
     340                        self::$test_ability_properties,
     341                        array(
     342                                'meta' => array(
     343                                        'public' => 5,
     344                                ),
     345                        )
     346                );
     347
     348                $this->expectException( InvalidArgumentException::class );
     349                $this->expectExceptionMessage( 'The ability meta should provide a valid `public` boolean.' );
    261350
    262351                new WP_Ability( self::$test_ability_name, $args );
  • trunk/tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php

    r62591 r62729  
    416416
    417417        /**
     418         * Test that an ability with only the `public` meta flag is exposed in REST.
     419         *
     420         * @ticket 65568
     421         */
     422        public function test_get_item_public_meta_exposes_in_rest(): void {
     423                $this->register_test_ability(
     424                        'test/public-ability',
     425                        array(
     426                                'label'               => 'Public Ability',
     427                                'description'         => 'Exposed in REST via the public meta flag.',
     428                                'category'            => 'general',
     429                                'execute_callback'    => '__return_true',
     430                                'permission_callback' => '__return_true',
     431                                'meta'                => array(
     432                                        'public' => true,
     433                                ),
     434                        )
     435                );
     436
     437                $request  = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/public-ability' );
     438                $response = $this->server->dispatch( $request );
     439
     440                $this->assertEquals( 200, $response->get_status() );
     441
     442                $data = $response->get_data();
     443                $this->assertTrue( $data['meta']['public'] );
     444                $this->assertTrue( $data['meta']['show_in_rest'] );
     445        }
     446
     447        /**
     448         * Test that an explicit `show_in_rest` value of false hides an ability even when `public` is true.
     449         *
     450         * @ticket 65568
     451         */
     452        public function test_get_item_public_true_show_in_rest_false_is_hidden(): void {
     453                $this->register_test_ability(
     454                        'test/public-optout',
     455                        array(
     456                                'label'               => 'Public Opt-out',
     457                                'description'         => 'Opts out of REST exposure despite the public meta flag.',
     458                                'category'            => 'general',
     459                                'execute_callback'    => '__return_true',
     460                                'permission_callback' => '__return_true',
     461                                'meta'                => array(
     462                                        'public'       => true,
     463                                        'show_in_rest' => false,
     464                                ),
     465                        )
     466                );
     467
     468                $request  = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/public-optout' );
     469                $response = $this->server->dispatch( $request );
     470
     471                $this->assertEquals( 404, $response->get_status() );
     472
     473                $data = $response->get_data();
     474                $this->assertSame( 'rest_ability_not_found', $data['code'] );
     475        }
     476
     477        /**
    418478         * Test permission check for listing abilities.
    419479         *
     
    621681                $this->assertArrayHasKey( 'meta', $properties );
    622682                $this->assertArrayHasKey( 'category', $properties );
     683        }
     684
     685        /**
     686         * Test that the item schema declares the `public` meta property.
     687         *
     688         * @ticket 65568
     689         */
     690        public function test_get_schema_meta_declares_public(): void {
     691                $request  = new WP_REST_Request( 'OPTIONS', '/wp-abilities/v1/abilities' );
     692                $response = $this->server->dispatch( $request );
     693                $data     = $response->get_data();
     694
     695                $meta_properties = $data['schema']['properties']['meta']['properties'];
     696
     697                $this->assertArrayHasKey( 'public', $meta_properties );
     698                $this->assertSame( 'boolean', $meta_properties['public']['type'] );
    623699        }
    624700
  • trunk/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php

    r62674 r62729  
    649649                $this->assertSame( 'rest_ability_not_found', $data['code'] );
    650650                $this->assertSame( 'Ability not found.', $data['message'] );
     651        }
     652
     653        /**
     654         * Test running an ability exposed in REST via the `public` meta flag.
     655         *
     656         * @ticket 65568
     657         */
     658        public function test_run_public_meta_ability_is_executable(): void {
     659                $this->register_test_ability(
     660                        'test/public-ability',
     661                        array(
     662                                'label'               => 'Public Ability',
     663                                'description'         => 'Exposed in REST via the public meta flag.',
     664                                'category'            => 'general',
     665                                'execute_callback'    => '__return_true',
     666                                'permission_callback' => '__return_true',
     667                                'meta'                => array(
     668                                        'public' => true,
     669                                ),
     670                        )
     671                );
     672
     673                $request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/test/public-ability/run' );
     674                $request->set_header( 'Content-Type', 'application/json' );
     675
     676                $response = $this->server->dispatch( $request );
     677
     678                $this->assertEquals( 200, $response->get_status() );
     679                $this->assertTrue( $response->get_data() );
    651680        }
    652681
Note: See TracChangeset for help on using the changeset viewer.