| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Abilities API 4 * 5 * Defines WP_Ability class. 6 * 7 * @package WordPress 8 * @subpackage Abilities API 9 * @since 6.9.0 10 */ 11 12 declare( strict_types = 1 ); 13 14 /** 15 * Encapsulates the properties and methods related to a specific ability in the registry. 16 * 17 * @since 6.9.0 18 * 19 * @see WP_Abilities_Registry 20 */ 21 class WP_Ability { 22 23 /** 24 * The default value for the `show_in_rest` meta. 25 * 26 * @since 6.9.0 27 * @var bool 28 */ 29 protected const DEFAULT_SHOW_IN_REST = false; 30 31 /** 32 * The default value for the `public` meta. 33 * 34 * @since 7.1.0 35 * @var bool 36 */ 37 protected const DEFAULT_PUBLIC = false; 38 39 /** 40 * The default ability annotations. 41 * They are not guaranteed to provide a faithful description of ability behavior. 42 * 43 * @since 6.9.0 44 * @var array<string, bool|null> 45 */ 46 protected static $default_annotations = array( 47 // If true, the ability does not modify its environment. 48 'readonly' => null, 49 /* 50 * If true, the ability may perform destructive updates to its environment. 51 * If false, the ability performs only additive updates. 52 */ 53 'destructive' => null, 54 /* 55 * If true, calling the ability repeatedly with the same arguments will have no additional effect 56 * on its environment. 57 */ 58 'idempotent' => null, 59 ); 60 61 /** 62 * The name of the ability, with its namespace. 63 * Example: `my-plugin/my-ability`. 64 * 65 * @since 6.9.0 66 * @var string 67 */ 68 protected $name; 69 70 /** 71 * The human-readable ability label. 72 * 73 * @since 6.9.0 74 * @var string 75 */ 76 protected $label; 77 78 /** 79 * The detailed ability description. 80 * 81 * @since 6.9.0 82 * @var string 83 */ 84 protected $description; 85 86 /** 87 * The ability category. 88 * 89 * @since 6.9.0 90 * @var string 91 */ 92 protected $category; 93 94 /** 95 * The optional ability input schema. 96 * 97 * @since 6.9.0 98 * @var array<string, mixed> 99 */ 100 protected $input_schema = array(); 101 102 /** 103 * The optional ability output schema. 104 * 105 * @since 6.9.0 106 * @var array<string, mixed> 107 */ 108 protected $output_schema = array(); 109 110 /** 111 * The ability execute callback. 112 * 113 * @since 6.9.0 114 * @var callable(mixed): (mixed|WP_Error) 115 */ 116 protected $execute_callback; 117 118 /** 119 * The optional ability permission callback. 120 * 121 * @since 6.9.0 122 * @var callable(mixed): (bool|WP_Error) 123 */ 124 protected $permission_callback; 125 126 /** 127 * The optional ability metadata. 128 * 129 * @since 6.9.0 130 * @var array<string, mixed> 131 */ 132 protected $meta; 133 134 /** 135 * Constructor. 136 * 137 * Do not use this constructor directly. Instead, use the `wp_register_ability()` function. 138 * 139 * @access private 140 * 141 * @since 6.9.0 142 * 143 * @see wp_register_ability() 144 * 145 * @param string $name The name of the ability, with its namespace. 146 * @param array<string, mixed> $args { 147 * An associative array of arguments for the ability. 148 * 149 * @type string $label The human-readable label for the ability. 150 * @type string $description A detailed description of what the ability does. 151 * @type string $category The ability category slug this ability belongs to. 152 * @type callable $execute_callback A callback function to execute when the ability is invoked. 153 * Receives optional mixed input and returns mixed result or WP_Error. 154 * @type callable $permission_callback A callback function to check permissions before execution. 155 * Receives optional mixed input and returns bool or WP_Error. 156 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input. 157 * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output. 158 * @type array<string, mixed> $meta { 159 * Optional. Additional metadata for the ability. 160 * 161 * @type array<string, bool|null> $annotations { 162 * Optional. Semantic annotations describing the ability's behavioral characteristics. 163 * These annotations are hints for tooling and documentation. 164 * 165 * @type bool|null $readonly Optional. If true, the ability does not modify its environment. 166 * @type bool|null $destructive Optional. If true, the ability may perform destructive updates to its environment. 167 * If false, the ability performs only additive updates. 168 * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments 169 * will have no additional effect on its environment. 170 * } 171 * @type bool $public Optional. Whether the ability is meant to be available 172 * to clients such as the REST API, MCP, or AI agents. 173 * Seeds the default for per-channel flags like 174 * `$show_in_rest`. Defaults to false. 175 * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. 176 * Default is the value of `$public` when set, false otherwise. 177 * } 178 * } 179 */ 180 public function __construct( string $name, array $args ) { 181 $this->name = $name; 182 183 $properties = $this->prepare_properties( $args ); 184 185 foreach ( $properties as $property_name => $property_value ) { 186 if ( ! property_exists( $this, $property_name ) ) { 187 _doing_it_wrong( 188 __METHOD__, 189 sprintf( 190 /* translators: %s: Property name. */ 191 __( 'Property "%1$s" is not a valid property for ability "%2$s". Please check the %3$s class for allowed properties.' ), 192 '<code>' . esc_html( $property_name ) . '</code>', 193 '<code>' . esc_html( $this->name ) . '</code>', 194 '<code>' . __CLASS__ . '</code>' 195 ), 196 '6.9.0' 197 ); 198 continue; 199 } 200 201 $this->$property_name = $property_value; 202 } 203 } 204 205 /** 206 * Prepares and validates the properties used to instantiate the ability. 207 * 208 * Errors are thrown as exceptions instead of WP_Errors to allow for simpler handling and overloading. They are then 209 * caught and converted to a WP_Error by WP_Abilities_Registry::register(). 210 * 211 * @since 6.9.0 212 * 213 * @see WP_Abilities_Registry::register() 214 * 215 * @param array<string, mixed> $args { 216 * An associative array of arguments used to instantiate the ability class. 217 * 218 * @type string $label The human-readable label for the ability. 219 * @type string $description A detailed description of what the ability does. 220 * @type string $category The ability category slug this ability belongs to. 221 * @type callable $execute_callback A callback function to execute when the ability is invoked. 222 * Receives optional mixed input and returns mixed result or WP_Error. 223 * @type callable $permission_callback A callback function to check permissions before execution. 224 * Receives optional mixed input and returns bool or WP_Error. 225 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input. Required if ability accepts an input. 226 * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output. 227 * @type array<string, mixed> $meta { 228 * Optional. Additional metadata for the ability. 229 * 230 * @type array<string, bool|null> $annotations { 231 * Optional. Semantic annotations describing the ability's behavioral characteristics. 232 * These annotations are hints for tooling and documentation. 233 * 234 * @type bool|null $readonly Optional. If true, the ability does not modify its environment. 235 * @type bool|null $destructive Optional. If true, the ability may perform destructive updates to its environment. 236 * If false, the ability performs only additive updates. 237 * @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments 238 * will have no additional effect on its environment. 239 * } 240 * @type bool $public Optional. Whether the ability is meant to be available 241 * to clients such as the REST API, MCP, or AI agents. 242 * Seeds the default for per-channel flags like 243 * `$show_in_rest`. Defaults to false. 244 * @type bool $show_in_rest Optional. Whether to expose this ability in the REST API. 245 * Default is the value of `$public` when set, false otherwise. 246 * } 247 * } 248 * @return array<string, mixed> { 249 * An associative array of arguments with validated and prepared properties for the ability class. 250 * 251 * @type string $label The human-readable label for the ability. 252 * @type string $description A detailed description of what the ability does. 253 * @type string $category The ability category slug this ability belongs to. 254 * @type callable $execute_callback A callback function to execute when the ability is invoked. 255 * Receives optional mixed input and returns mixed result or WP_Error. 256 * @type callable $permission_callback A callback function to check permissions before execution. 257 * Receives optional mixed input and returns bool or WP_Error. 258 * @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input. 259 * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output. 260 * @type array<string, mixed> $meta { 261 * Additional metadata for the ability. 262 * 263 * @type array<string, bool|null> $annotations { 264 * Semantic annotations describing the ability's behavioral characteristics. 265 * These annotations are hints for tooling and documentation. 266 * 267 * @type bool|null $readonly If true, the ability does not modify its environment. 268 * @type bool|null $destructive If true, the ability may perform destructive updates to its environment. 269 * If false, the ability performs only additive updates. 270 * @type bool|null $idempotent If true, calling the ability repeatedly with the same arguments 271 * will have no additional effect on its environment. 272 * } 273 * @type bool $public Whether the ability is meant to be available to clients 274 * such as the REST API, MCP, or AI agents. Defaults to 275 * false. 276 * @type bool $show_in_rest Whether to expose this ability in the REST API. 277 * } 278 * } 279 * @throws InvalidArgumentException if an argument is invalid. 280 */ 281 protected function prepare_properties( array $args ): array { 282 // Required args must be present and of the correct type. 283 if ( empty( $args['label'] ) || ! is_string( $args['label'] ) ) { 284 throw new InvalidArgumentException( 285 __( 'The ability properties must contain a `label` string.' ) 286 ); 287 } 288 289 if ( empty( $args['description'] ) || ! is_string( $args['description'] ) ) { 290 throw new InvalidArgumentException( 291 __( 'The ability properties must contain a `description` string.' ) 292 ); 293 } 294 295 if ( empty( $args['category'] ) || ! is_string( $args['category'] ) ) { 296 throw new InvalidArgumentException( 297 __( 'The ability properties must contain a `category` string.' ) 298 ); 299 } 300 301 // If we are not overriding `ability_class` parameter during instantiation, then we need to validate the execute_callback. 302 if ( get_class( $this ) === self::class && ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) ) { 303 throw new InvalidArgumentException( 304 __( 'The ability properties must contain a valid `execute_callback` function.' ) 305 ); 306 } 307 308 // If we are not overriding `ability_class` parameter during instantiation, then we need to validate the permission_callback. 309 if ( get_class( $this ) === self::class && ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) ) { 310 throw new InvalidArgumentException( 311 __( 'The ability properties must provide a valid `permission_callback` function.' ) 312 ); 313 } 314 315 // Optional args only need to be of the correct type if they are present. 316 if ( isset( $args['input_schema'] ) && ! is_array( $args['input_schema'] ) ) { 317 throw new InvalidArgumentException( 318 __( 'The ability properties should provide a valid `input_schema` definition.' ) 319 ); 320 } 321 322 if ( isset( $args['output_schema'] ) && ! is_array( $args['output_schema'] ) ) { 323 throw new InvalidArgumentException( 324 __( 'The ability properties should provide a valid `output_schema` definition.' ) 325 ); 326 } 327 328 if ( isset( $args['meta'] ) && ! is_array( $args['meta'] ) ) { 329 throw new InvalidArgumentException( 330 __( 'The ability properties should provide a valid `meta` array.' ) 331 ); 332 } 333 334 if ( isset( $args['meta']['annotations'] ) && ! is_array( $args['meta']['annotations'] ) ) { 335 throw new InvalidArgumentException( 336 __( 'The ability meta should provide a valid `annotations` array.' ) 337 ); 338 } 339 340 if ( isset( $args['meta']['show_in_rest'] ) && ! is_bool( $args['meta']['show_in_rest'] ) ) { 341 throw new InvalidArgumentException( 342 __( 'The ability meta should provide a valid `show_in_rest` boolean.' ) 343 ); 344 } 345 346 if ( isset( $args['meta']['public'] ) && ! is_bool( $args['meta']['public'] ) ) { 347 throw new InvalidArgumentException( 348 __( 'The ability meta should provide a valid `public` boolean.' ) 349 ); 350 } 351 352 // Set defaults for optional meta. 353 $args['meta'] = wp_parse_args( 354 $args['meta'] ?? array(), 355 array( 356 'annotations' => static::$default_annotations, 357 ) 358 ); 359 360 $args['meta']['annotations'] = wp_parse_args( 361 $args['meta']['annotations'], 362 static::$default_annotations 363 ); 364 365 /* 366 * Resolve `show_in_rest` from most specific to least specific: an explicit 367 * `show_in_rest` value wins, then the high-level `public` flag seeds the 368 * default, then the built-in default applies. 369 */ 370 $args['meta']['show_in_rest'] = $args['meta']['show_in_rest'] ?? $args['meta']['public'] ?? self::DEFAULT_SHOW_IN_REST; 371 $args['meta']['public'] = $args['meta']['public'] ?? self::DEFAULT_PUBLIC; 372 373 return $args; 374 } 375 376 /** 377 * Retrieves the name of the ability, with its namespace. 378 * Example: `my-plugin/my-ability`. 379 * 380 * @since 6.9.0 381 * 382 * @return string The ability name, with its namespace. 383 */ 384 public function get_name(): string { 385 return $this->name; 386 } 387 388 /** 389 * Retrieves the human-readable label for the ability. 390 * 391 * @since 6.9.0 392 * 393 * @return string The human-readable ability label. 394 */ 395 public function get_label(): string { 396 return $this->label; 397 } 398 399 /** 400 * Retrieves the detailed description for the ability. 401 * 402 * @since 6.9.0 403 * 404 * @return string The detailed description for the ability. 405 */ 406 public function get_description(): string { 407 return $this->description; 408 } 409 410 /** 411 * Retrieves the ability category for the ability. 412 * 413 * @since 6.9.0 414 * 415 * @return string The ability category for the ability. 416 */ 417 public function get_category(): string { 418 return $this->category; 419 } 420 421 /** 422 * Retrieves the input schema for the ability. 423 * 424 * @since 6.9.0 425 * 426 * @return array<string, mixed> The input schema for the ability. 427 */ 428 public function get_input_schema(): array { 429 return $this->input_schema; 430 } 431 432 /** 433 * Retrieves the output schema for the ability. 434 * 435 * @since 6.9.0 436 * 437 * @return array<string, mixed> The output schema for the ability. 438 */ 439 public function get_output_schema(): array { 440 return $this->output_schema; 441 } 442 443 /** 444 * Retrieves the metadata for the ability. 445 * 446 * @since 6.9.0 447 * 448 * @return array<string, mixed> The metadata for the ability. 449 */ 450 public function get_meta(): array { 451 return $this->meta; 452 } 453 454 /** 455 * Retrieves a specific metadata item for the ability. 456 * 457 * @since 6.9.0 458 * 459 * @param string $key The metadata key to retrieve. 460 * @param mixed $default_value Optional. The default value to return if the metadata item is not found. Default `null`. 461 * @return mixed The value of the metadata item, or the default value if not found. 462 */ 463 public function get_meta_item( string $key, $default_value = null ) { 464 return array_key_exists( $key, $this->meta ) ? $this->meta[ $key ] : $default_value; 465 } 466 467 /** 468 * Normalizes the input for the ability, applying the default value from the input schema when needed. 469 * 470 * When no input is provided and the input schema is defined with a top-level `default` key, this method returns 471 * the value of that key. If the input schema does not define a `default`, or if the input schema is empty, 472 * this method returns null. If input is provided, it is returned as-is. 473 * 474 * The {@see 'wp_ability_normalize_input'} filter fires after the built-in default-value handling, 475 * allowing plugins to transform the result. 476 * 477 * @since 6.9.0 478 * @since 7.1.0 Added the `wp_ability_normalize_input` filter. 479 * 480 * @param mixed $input Optional. The raw input provided for the ability. Default `null`. 481 * @return mixed The normalized input, or a `WP_Error` if a filter returned one. 482 */ 483 public function normalize_input( $input = null ) { 484 if ( null === $input ) { 485 $input_schema = $this->get_input_schema(); 486 if ( array_key_exists( 'default', $input_schema ) ) { 487 $input = $input_schema['default']; 488 } 489 } 490 491 /** 492 * Filters the normalized input for an ability. 493 * 494 * Fires after `normalize_input()` has applied any default value declared in the input schema, 495 * giving plugins a chance to adjust the input before it is consumed downstream. Common uses 496 * include defaulting beyond what JSON Schema can express, prompt enrichment, and injecting 497 * caller metadata. 498 * 499 * Returning a `WP_Error` causes callers that propagate it (such as `execute()`) to halt 500 * before validation, permission checks, and the registered execute callback. 501 * 502 * @since 7.1.0 503 * 504 * @param mixed $input The normalized input data. 505 * @param string $ability_name The name of the ability. 506 * @param WP_Ability $ability The ability instance. 507 */ 508 return apply_filters( 'wp_ability_normalize_input', $input, $this->name, $this ); 509 } 510 511 /** 512 * Validates input data against the input schema. 513 * 514 * @since 6.9.0 515 * 516 * @param mixed $input Optional. The input data to validate. Default `null`. 517 * @return true|WP_Error Returns true if valid or the WP_Error object if validation fails. 518 */ 519 public function validate_input( $input = null ) { 520 $input_schema = $this->get_input_schema(); 521 if ( empty( $input_schema ) ) { 522 if ( null === $input ) { 523 return true; 524 } 525 526 return new WP_Error( 527 'ability_missing_input_schema', 528 sprintf( 529 /* translators: %s ability name. */ 530 __( 'Ability "%s" does not define an input schema required to validate the provided input.' ), 531 $this->name 532 ) 533 ); 534 } 535 536 $valid_input = rest_validate_value_from_schema( $input, $input_schema, 'input' ); 537 if ( is_wp_error( $valid_input ) ) { 538 $is_valid = new WP_Error( 539 'ability_invalid_input', 540 sprintf( 541 /* translators: %1$s ability name, %2$s error message. */ 542 __( 'Ability "%1$s" has invalid input. Reason: %2$s' ), 543 $this->name, 544 $valid_input->get_error_message() 545 ) 546 ); 547 } else { 548 $is_valid = true; 549 } 550 551 /** 552 * Filters the input validation result for an ability. 553 * 554 * Allows developers to add custom validation logic on top of the default 555 * JSON Schema validation. If default validation already failed, the filter 556 * receives the WP_Error object and can add additional error information or 557 * override it. If default validation passed, the filter can add additional 558 * validation checks and return a WP_Error if those checks fail. 559 * 560 * @since 7.1.0 561 * 562 * @param true|WP_Error $is_valid The validation result from default validation. 563 * @param mixed $input The input data being validated. 564 * @param string $ability_name The name of the ability. 565 */ 566 $validity = apply_filters( 'wp_ability_validate_input', $is_valid, $input, $this->name ); 567 if ( false === $validity ) { 568 return new WP_Error( 'ability_invalid_input', __( 'Invalid input.' ) ); 569 } 570 if ( is_wp_error( $validity ) && $validity->has_errors() ) { 571 return $validity; 572 } 573 return true; 574 } 575 576 /** 577 * Invokes a callable, ensuring the input is passed through only if the input schema is defined. 578 * 579 * @since 6.9.0 580 * 581 * @param callable $callback The callable to invoke. 582 * @param mixed $input Optional. The input data for the ability. Default `null`. 583 * @return mixed The result of the callable execution, or a `WP_Error` if the callback threw. 584 */ 585 protected function invoke_callback( callable $callback, $input = null ) { 586 $args = array(); 587 if ( ! empty( $this->get_input_schema() ) ) { 588 $args[] = $input; 589 } 590 591 try { 592 return $callback( ...$args ); 593 } catch ( Throwable $e ) { 594 return new WP_Error( 595 'ability_callback_exception', 596 sprintf( 597 /* translators: 1: Ability name, 2: Exception message. */ 598 __( 'Ability "%1$s" callback threw an exception: %2$s' ), 599 $this->name, 600 esc_html( $e->getMessage() ) 601 ) 602 ); 603 } 604 } 605 606 /** 607 * Checks whether the ability has the necessary permissions. 608 * 609 * Please note that input is not automatically validated against the input schema. 610 * Use `validate_input()` method to validate input before calling this method if needed. 611 * 612 * The {@see 'wp_ability_permission_result'} filter fires after the registered 613 * `permission_callback` returns, allowing plugins to override the result. 614 * 615 * @since 6.9.0 616 * @since 7.1.0 Added the `wp_ability_permission_result` filter. 617 * 618 * @see validate_input() 619 * 620 * @param mixed $input Optional. The valid input data for permission checking. Default `null`. 621 * @return bool|WP_Error Whether the ability has the necessary permission. 622 */ 623 public function check_permissions( $input = null ) { 624 if ( ! is_callable( $this->permission_callback ) ) { 625 return new WP_Error( 626 'ability_invalid_permission_callback', 627 /* translators: %s ability name. */ 628 sprintf( __( 'Ability "%s" does not have a valid permission callback.' ), $this->name ) 629 ); 630 } 631 632 $permission = $this->invoke_callback( $this->permission_callback, $input ); 633 634 /** 635 * Filters the result of an ability's permission check. 636 * 637 * Fires after the registered `permission_callback` returns. Plugins can use this to layer 638 * additional authorization rules on top of the ability's own permission logic — for example, 639 * multi-factor authorization gates or temporary permission elevation for trusted contexts. 640 * 641 * Filters can return `true` to grant, `false` to deny, or a `WP_Error` to deny with a specific 642 * error code and message. The filter receives whatever the `permission_callback` produced. 643 * Any other return value is coerced to `false`. 644 * 645 * @since 7.1.0 646 * 647 * @param bool|WP_Error $permission The permission result returned by `permission_callback`. 648 * @param string $ability_name The name of the ability. 649 * @param mixed $input The input data for the permission check. 650 * @param WP_Ability $ability The ability instance. 651 */ 652 $result = apply_filters( 'wp_ability_permission_result', $permission, $this->name, $input, $this ); 653 if ( ! is_bool( $result ) && ! is_wp_error( $result ) ) { 654 $result = false; 655 } 656 return $result; 657 } 658 659 /** 660 * Executes the ability callback. 661 * 662 * The {@see 'wp_ability_execute_result'} filter fires before this method returns, allowing 663 * plugins to transform the result produced by the registered `execute_callback`. 664 * 665 * @since 6.9.0 666 * @since 7.1.0 Added the `wp_ability_execute_result` filter. 667 * 668 * @param mixed $input Optional. The input data for the ability. Default `null`. 669 * @return mixed|WP_Error The result of the ability execution, or WP_Error on failure. 670 */ 671 protected function do_execute( $input = null ) { 672 if ( ! is_callable( $this->execute_callback ) ) { 673 $result = new WP_Error( 674 'ability_invalid_execute_callback', 675 /* translators: %s ability name. */ 676 sprintf( __( 'Ability "%s" does not have a valid execute callback.' ), $this->name ) 677 ); 678 } else { 679 $result = $this->invoke_callback( $this->execute_callback, $input ); 680 } 681 682 /** 683 * Filters the result returned by an ability's execute callback. 684 * 685 * Fires after the registered execute callback runs. Plugins can use this to transform the 686 * result — response formatting, stripping internal metadata, content safety filtering, 687 * response enrichment, or recovering from a failure by returning a successful value. 688 * 689 * The filter receives whatever the registered callback produced, including a `WP_Error` 690 * if execution failed. Filters may pass the `WP_Error` through unchanged, override it with 691 * a recovered result, or convert a successful result into a `WP_Error`. 692 * 693 * @since 7.1.0 694 * 695 * @param mixed $result The result returned by the registered `execute_callback`, 696 * or a `WP_Error` if execution failed. 697 * @param string $ability_name The name of the ability. 698 * @param mixed $input The normalized input data. 699 * @param WP_Ability $ability The ability instance. 700 */ 701 return apply_filters( 'wp_ability_execute_result', $result, $this->name, $input, $this ); 702 } 703 704 /** 705 * Validates output data against the output schema. 706 * 707 * @since 6.9.0 708 * 709 * @param mixed $output The output data to validate. 710 * @return true|WP_Error Returns true if valid, or a WP_Error object if validation fails. 711 */ 712 protected function validate_output( $output ) { 713 $output_schema = $this->get_output_schema(); 714 if ( empty( $output_schema ) ) { 715 $is_valid = true; 716 } else { 717 $valid_output = rest_validate_value_from_schema( $output, $output_schema, 'output' ); 718 if ( is_wp_error( $valid_output ) ) { 719 $is_valid = new WP_Error( 720 'ability_invalid_output', 721 sprintf( 722 /* translators: %1$s ability name, %2$s error message. */ 723 __( 'Ability "%1$s" has invalid output. Reason: %2$s' ), 724 $this->name, 725 $valid_output->get_error_message() 726 ) 727 ); 728 } else { 729 $is_valid = true; 730 } 731 } 732 733 /** 734 * Filters the output validation result for an ability. 735 * 736 * Allows developers to add custom validation logic on top of the default 737 * JSON Schema validation. If default validation already failed, the filter 738 * receives the WP_Error object and can add additional error information or 739 * override it. If default validation passed, the filter can add additional 740 * validation checks and return a WP_Error if those checks fail. 741 * 742 * @since 7.1.0 743 * 744 * @param true|WP_Error $is_valid The validation result from default validation. 745 * @param mixed $output The output data being validated. 746 * @param string $ability_name The name of the ability. 747 */ 748 $validity = apply_filters( 'wp_ability_validate_output', $is_valid, $output, $this->name ); 749 if ( false === $validity ) { 750 return new WP_Error( 'ability_invalid_output', __( 'Invalid output.' ) ); 751 } 752 if ( is_wp_error( $validity ) && $validity->has_errors() ) { 753 return $validity; 754 } 755 return true; 756 } 757 758 /** 759 * Executes the ability after input validation and running a permission check. 760 * Before returning the return value, it also validates the output. 761 * 762 * @since 6.9.0 763 * @since 7.1.0 Added the `wp_ability_invoked` action. 764 * @since 7.1.0 Added the `wp_pre_execute_ability` filter. 765 * 766 * @param mixed $input Optional. The input data for the ability. Default `null`. 767 * @return mixed|WP_Error The result of the ability execution, or WP_Error on failure. 768 */ 769 public function execute( $input = null ) { 770 /** 771 * Fires when an ability is invoked, before any processing takes place. 772 * 773 * This action fires for every call regardless of outcome (validation failure, 774 * permission denial, short-circuit, or successful execution), and before input 775 * normalization so the raw input is captured as-is. 776 * 777 * @since 7.1.0 778 * 779 * @param string $ability_name The name of the ability. 780 * @param mixed $input The raw input data for the ability, before normalization. 781 * @param WP_Ability $ability The ability instance. 782 */ 783 do_action( 'wp_ability_invoked', $this->name, $input, $this ); 784 785 /** 786 * Filters whether to short-circuit ability execution. 787 * 788 * Returning a value other than the received default bypasses the rest of `execute()` — 789 * input normalization, input validation, permission checks, the registered execute callback, 790 * output validation, and the surrounding actions — and the value is returned to the caller 791 * as-is. Useful for cached responses, rate limiting, maintenance mode, and test mocking. 792 * 793 * To continue with normal execution, return `$pre` unchanged. This preserves any value 794 * (including `null`, `false`, or arbitrary objects) as a valid short-circuit result. 795 * 796 * Because validation is bypassed, callers that short-circuit are responsible for the 797 * integrity of any value they consume from `$input`. 798 * 799 * @since 7.1.0 800 * 801 * @param mixed $pre The pre-computed result. Return this value unchanged to continue execution. 802 * Default `WP_Filter_Sentinel` instance unique to this invocation. 803 * @param string $ability_name The name of the ability. 804 * @param mixed $input The raw input passed to `execute()`. 805 * @param WP_Ability $ability The ability instance. 806 */ 807 $pre_execute_sentinel = new WP_Filter_Sentinel(); 808 $pre = apply_filters( 'wp_pre_execute_ability', $pre_execute_sentinel, $this->name, $input, $this ); 809 if ( $pre !== $pre_execute_sentinel ) { 810 return $pre; 811 } 812 813 $input = $this->normalize_input( $input ); 814 if ( is_wp_error( $input ) ) { 815 return $input; 816 } 817 818 $is_valid = $this->validate_input( $input ); 819 if ( is_wp_error( $is_valid ) ) { 820 return $is_valid; 821 } 822 823 $has_permissions = $this->check_permissions( $input ); 824 if ( true !== $has_permissions ) { 825 if ( is_wp_error( $has_permissions ) ) { 826 // Don't leak the permission check error to someone without the correct perms. 827 _doing_it_wrong( 828 __METHOD__, 829 esc_html( $has_permissions->get_error_message() ), 830 '6.9.0' 831 ); 832 } 833 834 return new WP_Error( 835 'ability_invalid_permissions', 836 /* translators: %s ability name. */ 837 sprintf( __( 'Ability "%s" does not have necessary permission.' ), $this->name ) 838 ); 839 } 840 841 /** 842 * Fires before an ability gets executed, after input validation and permissions check. 843 * 844 * @since 6.9.0 845 * @since 7.1.0 Added the `$ability` parameter. 846 * 847 * @param string $ability_name The name of the ability. 848 * @param mixed $input The input data for the ability. 849 * @param WP_Ability $ability The ability instance. 850 */ 851 do_action( 'wp_before_execute_ability', $this->name, $input, $this ); 852 853 $result = $this->do_execute( $input ); 854 if ( is_wp_error( $result ) ) { 855 return $result; 856 } 857 858 $is_valid = $this->validate_output( $result ); 859 if ( is_wp_error( $is_valid ) ) { 860 return $is_valid; 861 } 862 863 /** 864 * Fires immediately after an ability finished executing. 865 * 866 * @since 6.9.0 867 * @since 7.1.0 Added the `$ability` parameter. 868 * 869 * @param string $ability_name The name of the ability. 870 * @param mixed $input The input data for the ability. 871 * @param mixed $result The result of the ability execution. 872 * @param WP_Ability $ability The ability instance. 873 */ 874 do_action( 'wp_after_execute_ability', $this->name, $input, $result, $this ); 875 876 return $result; 877 } 878 879 /** 880 * Wakeup magic method. 881 * 882 * @since 6.9.0 883 * @throws LogicException If the ability object is unserialized. 884 * This is a security hardening measure to prevent unserialization of the ability. 885 */ 886 public function __wakeup(): void { 887 throw new LogicException( __CLASS__ . ' should never be unserialized.' ); 888 } 889 890 /** 891 * Sleep magic method. 892 * 893 * @since 6.9.0 894 * @throws LogicException If the ability object is serialized. 895 * This is a security hardening measure to prevent serialization of the ability. 896 */ 897 public function __sleep(): array { 898 throw new LogicException( __CLASS__ . ' should never be serialized.' ); 899 } 900 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sat Jul 25 08:20:20 2026 | Cross-referenced by PHPXref |