[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/abilities-api/ -> class-wp-ability.php (source)

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


Generated : Sun Jun 14 08:20:09 2026 Cross-referenced by PHPXref