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