Changeset 61067
- Timestamp:
- 10/27/2025 08:18:29 AM (4 months ago)
- File:
-
- 1 edited
-
trunk/src/wp-includes/abilities-api.php (modified) (13 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/abilities-api.php
r61032 r61067 1 1 <?php 2 2 /** 3 * Abilities API 4 * 5 * Defines functions for managing abilities in WordPress. 3 * Abilities API: core functions for registering and managing abilities. 4 * 5 * The Abilities API provides a unified, extensible framework for registering 6 * and executing discrete capabilities within WordPress. An "ability" is a 7 * self-contained unit of functionality with defined inputs, outputs, permissions, 8 * and execution logic. 9 * 10 * ## Overview 11 * 12 * The Abilities API enables developers to: 13 * 14 * - Register custom abilities with standardized interfaces. 15 * - Define permission checks and execution callbacks. 16 * - Organize abilities into logical categories. 17 * - Validate inputs and outputs using JSON Schema. 18 * - Expose abilities through the REST API. 19 * 20 * ## Working with Abilities 21 * 22 * Abilities must be registered on the `wp_abilities_api_init` action hook. 23 * Attempting to register an ability outside of this hook will fail and 24 * trigger a `_doing_it_wrong()` notice. 25 26 * Example: 27 * 28 * function my_plugin_register_abilities(): void { 29 * wp_register_ability( 30 * 'my-plugin/export-users', 31 * array( 32 * 'label' => __( 'Export Users', 'my-plugin' ), 33 * 'description' => __( 'Exports user data to CSV format.', 'my-plugin' ), 34 * 'category' => 'data-export', 35 * 'execute_callback' => 'my_plugin_export_users', 36 * 'permission_callback' => function(): bool { 37 * return current_user_can( 'export' ); 38 * }, 39 * 'input_schema' => array( 40 * 'type' => 'string', 41 * 'enum' => array( 'subscriber', 'contributor', 'author', 'editor', 'administrator' ), 42 * 'description' => __( 'Limits the export to users with this role.', 'my-plugin' ), 43 * 'required' => false, 44 * ), 45 * 'output_schema' => array( 46 * 'type' => 'string', 47 * 'description' => __( 'User data in CSV format.', 'my-plugin' ), 48 * 'required' => true, 49 * ), 50 * 'meta' => array( 51 * 'show_in_rest' => true, 52 * ), 53 * ) 54 * ); 55 * } 56 * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); 57 * 58 * Once registered, abilities can be checked, retrieved, and managed: 59 * 60 * // Checks if an ability is registered, and prints its label. 61 * if ( wp_has_ability( 'my-plugin/export-users' ) ) { 62 * $ability = wp_get_ability( 'my-plugin/export-users' ); 63 * 64 * echo $ability->get_label(); 65 * } 66 * 67 * // Gets all registered abilities. 68 * $all_abilities = wp_get_abilities(); 69 * 70 * // Unregisters when no longer needed. 71 * wp_unregister_ability( 'my-plugin/export-users' ); 72 * 73 * ## Best Practices 74 * 75 * - Always register abilities on the `wp_abilities_api_init` hook. 76 * - Use namespaced ability names to prevent conflicts. 77 * - Implement robust permission checks in permission callbacks. 78 * - Provide an `input_schema` to ensure data integrity and document expected inputs. 79 * - Define an `output_schema` to describe return values and validate responses. 80 * - Return `WP_Error` objects for failures rather than throwing exceptions. 81 * - Use internationalization functions for all user-facing strings. 6 82 * 7 83 * @package WordPress … … 13 89 14 90 /** 15 * Registers a new ability using Abilities API. 16 * 17 * Note: Should only be used on the {@see 'wp_abilities_api_init'} hook. 91 * Registers a new ability using the Abilities API. It requires three steps: 92 * 93 * 1. Hook into the `wp_abilities_api_init` action. 94 * 2. Call `wp_register_ability()` with a namespaced name and configuration. 95 * 3. Provide execute and permission callbacks. 96 * 97 * Example: 98 * 99 * function my_plugin_register_abilities(): void { 100 * wp_register_ability( 101 * 'my-plugin/analyze-text', 102 * array( 103 * 'label' => __( 'Analyze Text', 'my-plugin' ), 104 * 'description' => __( 'Performs sentiment analysis on provided text.', 'my-plugin' ), 105 * 'category' => 'text-processing', 106 * 'input_schema' => array( 107 * 'type' => 'string', 108 * 'description' => __( 'The text to be analyzed.', 'my-plugin' ), 109 * 'minLength' => 10, 110 * 'required' => true, 111 * ), 112 * 'output_schema' => array( 113 * 'type' => 'string', 114 * 'enum' => array( 'positive', 'negative', 'neutral' ), 115 * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ), 116 * 'required' => true, 117 * ), 118 * 'execute_callback' => 'my_plugin_analyze_text', 119 * 'permission_callback' => 'my_plugin_can_analyze_text', 120 * 'meta' => array( 121 * 'annotations' => array( 122 * 'readonly' => true, 123 * ), 124 * 'show_in_rest' => true, 125 * ), 126 * ) 127 * ); 128 * } 129 * add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); 130 * 131 * ### Naming Conventions 132 * 133 * Ability names must follow these rules: 134 * 135 * - Include a namespace prefix (e.g., `my-plugin/my-ability`). 136 * - Use only lowercase alphanumeric characters, dashes, and forward slashes. 137 * - Use descriptive, action-oriented names (e.g., `process-payment`, `generate-report`). 138 * 139 * ### Categories 140 * 141 * Abilities must be organized into categories. Ability categories provide better 142 * discoverability and must be registered before the abilities that reference them: 143 * 144 * function my_plugin_register_categories(): void { 145 * wp_register_ability_category( 146 * 'text-processing', 147 * array( 148 * 'label' => __( 'Text Processing', 'my-plugin' ), 149 * 'description' => __( 'Abilities for analyzing and transforming text.', 'my-plugin' ), 150 * ) 151 * ); 152 * } 153 * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' ); 154 * 155 * ### Input and Output Schemas 156 * 157 * Schemas define the expected structure, type, and constraints for ability inputs 158 * and outputs using JSON Schema syntax. They serve two critical purposes: automatic 159 * validation of data passed to and returned from abilities, and self-documenting 160 * API contracts for developers. 161 * 162 * WordPress implements a validator based on a subset of the JSON Schema Version 4 163 * specification (https://json-schema.org/specification-links.html#draft-4). 164 * For details on supported JSON Schema properties and syntax, see the 165 * related WordPress REST API Schema documentation: 166 * https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/#json-schema-basics 167 * 168 * Defining schemas is mandatory when there is a value to pass or return. 169 * They ensure data integrity, improve developer experience, and enable 170 * better documentation: 171 * 172 * 'input_schema' => array( 173 * 'type' => 'string', 174 * 'description' => __( 'The text to be analyzed.', 'my-plugin' ), 175 * 'minLength' => 10, 176 * 'required' => true, 177 * ), 178 * 'output_schema' => array( 179 * 'type' => 'string', 180 * 'enum' => array( 'positive', 'negative', 'neutral' ), 181 * 'description' => __( 'The sentiment result: positive, negative, or neutral.', 'my-plugin' ), 182 * 'required' => true, 183 * ), 184 * 185 * ### Callbacks 186 * 187 * #### Execute Callback 188 * 189 * The execute callback performs the ability's core functionality. It receives 190 * optional input data and returns either a result or `WP_Error` on failure. 191 * 192 * function my_plugin_analyze_text( string $input ): string|WP_Error { 193 * $score = My_Plugin::perform_sentiment_analysis( $input ); 194 * if ( is_wp_error( $score ) ) { 195 * return $score; 196 * } 197 * return My_Plugin::interpret_sentiment_score( $score ); 198 * } 199 * 200 * #### Permission Callback 201 * 202 * The permission callback determines whether the ability can be executed. 203 * It receives the same input as the execute callback and must return a 204 * boolean or `WP_Error`. Common use cases include checking user capabilities, 205 * validating API keys, or verifying system state: 206 * 207 * function my_plugin_can_analyze_text( string $input ): bool|WP_Error { 208 * return current_user_can( 'edit_posts' ); 209 * } 210 * 211 * ### REST API Integration 212 * 213 * Abilities can be exposed through the REST API by setting `show_in_rest` 214 * to `true` in the meta configuration: 215 * 216 * 'meta' => array( 217 * 'show_in_rest' => true, 218 * ), 219 * 220 * This allows abilities to be invoked via HTTP requests to the WordPress REST API. 18 221 * 19 222 * @since 6.9.0 20 223 * 21 224 * @see WP_Abilities_Registry::register() 22 * 23 * @param string $name The name of the ability. The name must be a string containing a namespace 24 * prefix, i.e. `my-plugin/my-ability`. It can only contain lowercase 25 * alphanumeric characters, dashes and the forward slash. 225 * @see wp_register_ability_category() 226 * @see wp_unregister_ability() 227 * 228 * @param string $name The name of the ability. Must be a namespaced string containing 229 * a prefix, e.g., `my-plugin/my-ability`. Can only contain lowercase 230 * alphanumeric characters, dashes, and forward slashes. 26 231 * @param array<string, mixed> $args { 27 * An associative array of arguments for the ability. 28 * 29 * @type string $label The human-readable label for the ability. 30 * @type string $description A detailed description of what the ability does. 31 * @type string $category The ability category slug this ability belongs to. 32 * @type callable $execute_callback A callback function to execute when the ability is invoked. 33 * Receives optional mixed input and returns mixed result or WP_Error. 34 * @type callable $permission_callback A callback function to check permissions before execution. 35 * Receives optional mixed input and returns bool or WP_Error. 36 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input. 232 * An associative array of arguments for configuring the ability. 233 * 234 * @type string $label Required. The human-readable label for the ability. 235 * @type string $description Required. A detailed description of what the ability does 236 * and when it should be used. 237 * @type string $category Required. The ability category slug this ability belongs to. 238 * The ability category must be registered via `wp_register_ability_category()` 239 * before registering the ability. 240 * @type callable $execute_callback Required. A callback function to execute when the ability is invoked. 241 * Receives optional mixed input data and must return either a result 242 * value (any type) or a `WP_Error` object on failure. 243 * @type callable $permission_callback Required. A callback function to check permissions before execution. 244 * Receives optional mixed input data (same as `execute_callback`) and 245 * must return `true`/`false` for simple checks, or `WP_Error` for 246 * detailed error responses. 247 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for validating the ability's input. 248 * Must be a valid JSON Schema object defining the structure and 249 * constraints for input data. Used for automatic validation and 250 * API documentation. 37 251 * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output. 38 * @type array<string, mixed> $meta { 252 * Describes the structure of successful return values from 253 * `execute_callback`. Used for documentation and validation. 254 * @type array<string, mixed> $meta { 39 255 * Optional. Additional metadata for the ability. 40 256 * 41 * @type array<string, null|bool> $annotations Optional. Annotation metadata for the ability. 42 * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. Default false. 43 * } 44 * @type string $ability_class Optional. Custom class to instantiate instead of WP_Ability. 257 * @type array<string, bool|null> $annotations Optional. Annotation metadata for the ability. Provides 258 * additional semantic information about the ability's 259 * characteristics and behavior. 260 * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. 261 * When true, the ability can be invoked via HTTP requests. 262 * Default false. 263 * } 264 * @type string $ability_class Optional. Fully-qualified custom class name to instantiate 265 * instead of the default `WP_Ability` class. The custom class 266 * must extend `WP_Ability`. Useful for advanced customization 267 * of ability behavior. 45 268 * } 46 * @return WP_Ability|null An instance of registered ability on success, nullon failure.269 * @return WP_Ability|null The registered ability instance on success, `null` on failure. 47 270 */ 48 271 function wp_register_ability( string $name, array $args ): ?WP_Ability { … … 51 274 __FUNCTION__, 52 275 sprintf( 53 /* translators: 1: abilities_api_init, 2: string value of the ability name. */276 /* translators: 1: wp_abilities_api_init, 2: string value of the ability name. */ 54 277 esc_html__( 'Abilities must be registered on the %1$s action. The ability %2$s was not registered.' ), 55 '<code> abilities_api_init</code>',278 '<code>wp_abilities_api_init</code>', 56 279 '<code>' . esc_html( $name ) . '</code>' 57 280 ), … … 72 295 * Unregisters an ability from the Abilities API. 73 296 * 297 * Removes a previously registered ability from the global registry. Use this to 298 * disable abilities provided by other plugins or when an ability is no longer needed. 299 * 300 * Can be called at any time after the ability has been registered. 301 * 302 * Example: 303 * 304 * if ( wp_has_ability( 'other-plugin/some-ability' ) ) { 305 * wp_unregister_ability( 'other-plugin/some-ability' ); 306 * } 307 * 74 308 * @since 6.9.0 75 309 * 76 310 * @see WP_Abilities_Registry::unregister() 77 * 78 * @param string $name The name of the registered ability, with its namespace. 79 * @return WP_Ability|null The unregistered ability instance on success, null on failure. 311 * @see wp_register_ability() 312 * 313 * @param string $name The name of the ability to unregister, including namespace prefix 314 * (e.g., 'my-plugin/my-ability'). 315 * @return WP_Ability|null The unregistered ability instance on success, `null` on failure. 80 316 */ 81 317 function wp_unregister_ability( string $name ): ?WP_Ability { … … 91 327 * Checks if an ability is registered. 92 328 * 329 * Use this for conditional logic and feature detection before attempting to 330 * retrieve or use an ability. 331 * 332 * Example: 333 * 334 * // Displays different UI based on available abilities. 335 * if ( wp_has_ability( 'premium-plugin/advanced-export' ) ) { 336 * echo 'Export with Premium Features'; 337 * } else { 338 * echo 'Basic Export'; 339 * } 340 * 93 341 * @since 6.9.0 94 342 * 95 343 * @see WP_Abilities_Registry::is_registered() 96 * 97 * @param string $name The name of the registered ability, with its namespace. 98 * @return bool True if the ability is registered, false otherwise. 344 * @see wp_get_ability() 345 * 346 * @param string $name The name of the ability to check, including namespace prefix 347 * (e.g., 'my-plugin/my-ability'). 348 * @return bool `true` if the ability is registered, `false` otherwise. 99 349 */ 100 350 function wp_has_ability( string $name ): bool { … … 108 358 109 359 /** 110 * Retrieves a registered ability using Abilities API. 360 * Retrieves a registered ability. 361 * 362 * Returns the ability instance for inspection or use. The instance provides access 363 * to the ability's configuration, metadata, and execution methods. 364 * 365 * Example: 366 * 367 * // Prints information about a registered ability. 368 * $ability = wp_get_ability( 'my-plugin/export-data' ); 369 * if ( $ability ) { 370 * echo $ability->get_label() . ': ' . $ability->get_description(); 371 * } 111 372 * 112 373 * @since 6.9.0 113 374 * 114 375 * @see WP_Abilities_Registry::get_registered() 115 * 116 * @param string $name The name of the registered ability, with its namespace. 117 * @return WP_Ability|null The registered ability instance, or null if it is not registered. 376 * @see wp_has_ability() 377 * 378 * @param string $name The name of the ability, including namespace prefix 379 * (e.g., 'my-plugin/my-ability'). 380 * @return WP_Ability|null The registered ability instance, or `null` if not registered. 118 381 */ 119 382 function wp_get_ability( string $name ): ?WP_Ability { … … 127 390 128 391 /** 129 * Retrieves all registered abilities using Abilities API. 392 * Retrieves all registered abilities. 393 * 394 * Returns an array of all ability instances currently registered in the system. 395 * Use this for discovery, debugging, or building administrative interfaces. 396 * 397 * Example: 398 * 399 * // Prints information about all available abilities. 400 * $abilities = wp_get_abilities(); 401 * foreach ( $abilities as $ability ) { 402 * echo $ability->get_label() . ': ' . $ability->get_description() . "\n"; 403 * } 130 404 * 131 405 * @since 6.9.0 … … 133 407 * @see WP_Abilities_Registry::get_all_registered() 134 408 * 135 * @return WP_Ability[] The array of registered abilities. 409 * @return WP_Ability[] An array of registered WP_Ability instances. Returns an empty 410 * array if no abilities are registered or if the registry is unavailable. 136 411 */ 137 412 function wp_get_abilities(): array { … … 147 422 * Registers a new ability category. 148 423 * 424 * Ability categories provide a way to organize and group related abilities for better 425 * discoverability and management. Ability categories must be registered before abilities 426 * that reference them. 427 * 428 * Ability categories must be registered on the `wp_abilities_api_categories_init` action hook. 429 * 430 * Example: 431 * 432 * function my_plugin_register_categories() { 433 * wp_register_ability_category( 434 * 'content-management', 435 * array( 436 * 'label' => __( 'Content Management', 'my-plugin' ), 437 * 'description' => __( 'Abilities for managing and organizing content.', 'my-plugin' ), 438 * ) 439 * ); 440 * } 441 * add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_categories' ); 442 * 149 443 * @since 6.9.0 150 444 * 151 445 * @see WP_Ability_Categories_Registry::register() 446 * @see wp_register_ability() 447 * @see wp_unregister_ability_category() 152 448 * 153 449 * @param string $slug The unique slug for the ability category. Must contain only lowercase 154 * alphanumeric characters and dashes .450 * alphanumeric characters and dashes (e.g., 'data-export'). 155 451 * @param array<string, mixed> $args { 156 452 * An associative array of arguments for the ability category. 157 453 * 158 * @type string $label The human-readable label for the ability category.159 * @type string $description A description of the ability category.454 * @type string $label Required. The human-readable label for the ability category. 455 * @type string $description Required. A description of what abilities in this category do. 160 456 * @type array<string, mixed> $meta Optional. Additional metadata for the ability category. 161 457 * } 162 * @return WP_Ability_Category|null The registered ability category instance on success, nullon failure.458 * @return WP_Ability_Category|null The registered ability category instance on success, `null` on failure. 163 459 */ 164 460 function wp_register_ability_category( string $slug, array $args ): ?WP_Ability_Category { 165 461 if ( ! did_action( 'wp_abilities_api_categories_init' ) ) { 166 462 _doing_it_wrong( 167 __ METHOD__,463 __FUNCTION__, 168 464 sprintf( 169 /* translators: 1: abilities_api_categories_init, 2: ability category slug. */465 /* translators: 1: wp_abilities_api_categories_init, 2: ability category slug. */ 170 466 __( 'Ability categories must be registered on the %1$s action. The ability category %2$s was not registered.' ), 171 467 '<code>wp_abilities_api_categories_init</code>', … … 188 484 * Unregisters an ability category. 189 485 * 486 * Removes a previously registered ability category from the global registry. Use this to 487 * disable ability categories that are no longer needed. 488 * 489 * Can be called at any time after the ability category has been registered. 490 * 491 * Example: 492 * 493 * if ( wp_has_ability_category( 'deprecated-category' ) ) { 494 * wp_unregister_ability_category( 'deprecated-category' ); 495 * } 496 * 190 497 * @since 6.9.0 191 498 * 192 499 * @see WP_Ability_Categories_Registry::unregister() 193 * 194 * @param string $slug The slug of the registered ability category. 195 * @return WP_Ability_Category|null The unregistered ability category instance on success, null on failure. 500 * @see wp_register_ability_category() 501 * 502 * @param string $slug The slug of the ability category to unregister. 503 * @return WP_Ability_Category|null The unregistered ability category instance on success, `null` on failure. 196 504 */ 197 505 function wp_unregister_ability_category( string $slug ): ?WP_Ability_Category { … … 207 515 * Checks if an ability category is registered. 208 516 * 517 * Use this for conditional logic and feature detection before attempting to 518 * retrieve or use an ability category. 519 * 520 * Example: 521 * 522 * // Displays different UI based on available ability categories. 523 * if ( wp_has_ability_category( 'premium-features' ) ) { 524 * echo 'Premium Features Available'; 525 * } else { 526 * echo 'Standard Features'; 527 * } 528 * 209 529 * @since 6.9.0 210 530 * 211 531 * @see WP_Ability_Categories_Registry::is_registered() 212 * 213 * @param string $slug The slug of the ability category. 214 * @return bool True if the ability category is registered, false otherwise. 532 * @see wp_get_ability_category() 533 * 534 * @param string $slug The slug of the ability category to check. 535 * @return bool `true` if the ability category is registered, `false` otherwise. 215 536 */ 216 537 function wp_has_ability_category( string $slug ): bool { … … 226 547 * Retrieves a registered ability category. 227 548 * 549 * Returns the ability category instance for inspection or use. The instance provides access 550 * to the ability category's configuration and metadata. 551 * 552 * Example: 553 * 554 * // Prints information about a registered ability category. 555 * $ability_category = wp_get_ability_category( 'content-management' ); 556 * if ( $ability_category ) { 557 * echo $ability_category->get_label() . ': ' . $ability_category->get_description(); 558 * } 559 * 228 560 * @since 6.9.0 229 561 * 230 562 * @see WP_Ability_Categories_Registry::get_registered() 231 * 232 * @param string $slug The slug of the registered ability category. 233 * @return WP_Ability_Category|null The registered ability category instance, or null if it is not registered. 563 * @see wp_has_ability_category() 564 * @see wp_get_ability_categories() 565 * 566 * @param string $slug The slug of the ability category. 567 * @return WP_Ability_Category|null The ability category instance, or `null` if not registered. 234 568 */ 235 569 function wp_get_ability_category( string $slug ): ?WP_Ability_Category { … … 245 579 * Retrieves all registered ability categories. 246 580 * 581 * Returns an array of all ability category instances currently registered in the system. 582 * Use this for discovery, debugging, or building administrative interfaces. 583 * 584 * Example: 585 * 586 * // Prints information about all available ability categories. 587 * $ability_categories = wp_get_ability_categories(); 588 * foreach ( $ability_categories as $ability_category ) { 589 * echo $ability_category->get_label() . ': ' . $ability_category->get_description() . "\n"; 590 * } 591 * 247 592 * @since 6.9.0 248 593 * 249 594 * @see WP_Ability_Categories_Registry::get_all_registered() 250 * 251 * @return WP_Ability_Category[] The array of registered ability categories. 595 * @see wp_get_ability_category() 596 * 597 * @return WP_Ability_Category[] An array of registered ability category instances. Returns an empty array 598 * if no ability categories are registered or if the registry is unavailable. 252 599 */ 253 600 function wp_get_ability_categories(): array {
Note: See TracChangeset
for help on using the changeset viewer.