[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-abilities-v1-list-controller.php (source)

   1  <?php
   2  /**
   3   * REST API list controller for Abilities API.
   4   *
   5   * @package WordPress
   6   * @subpackage Abilities_API
   7   * @since 6.9.0
   8   */
   9  
  10  declare( strict_types = 1 );
  11  
  12  /**
  13   * Core controller used to access abilities via the REST API.
  14   *
  15   * @since 6.9.0
  16   *
  17   * @see WP_REST_Controller
  18   */
  19  class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
  20  
  21      /**
  22       * REST API namespace.
  23       *
  24       * @since 6.9.0
  25       * @var string
  26       */
  27      protected $namespace = 'wp-abilities/v1';
  28  
  29      /**
  30       * REST API base route.
  31       *
  32       * @since 6.9.0
  33       * @var string
  34       */
  35      protected $rest_base = 'abilities';
  36  
  37      /**
  38       * Registers the routes for abilities.
  39       *
  40       * @since 6.9.0
  41       *
  42       * @see register_rest_route()
  43       */
  44  	public function register_routes(): void {
  45          register_rest_route(
  46              $this->namespace,
  47              '/' . $this->rest_base,
  48              array(
  49                  array(
  50                      'methods'             => WP_REST_Server::READABLE,
  51                      'callback'            => array( $this, 'get_items' ),
  52                      'permission_callback' => array( $this, 'get_items_permissions_check' ),
  53                      'args'                => $this->get_collection_params(),
  54                  ),
  55                  'schema' => array( $this, 'get_public_item_schema' ),
  56              )
  57          );
  58  
  59          register_rest_route(
  60              $this->namespace,
  61              '/' . $this->rest_base . '/(?P<name>[a-zA-Z0-9\-\/]+)',
  62              array(
  63                  'args'   => array(
  64                      'name' => array(
  65                          'description' => __( 'Unique identifier for the ability.' ),
  66                          'type'        => 'string',
  67                          'pattern'     => '^[a-zA-Z0-9\-\/]+$',
  68                      ),
  69                  ),
  70                  array(
  71                      'methods'             => WP_REST_Server::READABLE,
  72                      'callback'            => array( $this, 'get_item' ),
  73                      'permission_callback' => array( $this, 'get_item_permissions_check' ),
  74                  ),
  75                  'schema' => array( $this, 'get_public_item_schema' ),
  76              )
  77          );
  78      }
  79  
  80      /**
  81       * Retrieves all abilities.
  82       *
  83       * @since 6.9.0
  84       *
  85       * @param WP_REST_Request $request Full details about the request.
  86       * @return WP_REST_Response Response object on success.
  87       */
  88  	public function get_items( $request ) {
  89          $query_args = array(
  90              'meta' => array( 'show_in_rest' => true ),
  91          );
  92  
  93          if ( ! empty( $request['category'] ) ) {
  94              $query_args['category'] = $request['category'];
  95          }
  96  
  97          if ( ! empty( $request['namespace'] ) ) {
  98              $query_args['namespace'] = $request['namespace'];
  99          }
 100  
 101          if ( ! empty( $request['meta'] ) ) {
 102              // Merge caller meta first so the forced show_in_rest filter wins. This keeps a caller from using meta to reveal abilities hidden from REST.
 103              $query_args['meta'] = array_merge( $request['meta'], $query_args['meta'] );
 104          }
 105  
 106          if ( ! empty( $request['has_required_capabilities'] ) ) {
 107              $query_args['has_required_capabilities'] = true;
 108          }
 109  
 110          $abilities = wp_get_abilities( $query_args );
 111  
 112          $page     = $request['page'];
 113          $per_page = $request['per_page'];
 114          $offset   = ( $page - 1 ) * $per_page;
 115  
 116          $total_abilities = count( $abilities );
 117          $max_pages       = (int) ceil( $total_abilities / $per_page );
 118  
 119          if ( $request->get_method() === 'HEAD' ) {
 120              $response = new WP_REST_Response( array() );
 121          } else {
 122              $abilities = array_slice( $abilities, $offset, $per_page );
 123  
 124              $data = array();
 125              foreach ( $abilities as $ability ) {
 126                  $item   = $this->prepare_item_for_response( $ability, $request );
 127                  $data[] = $this->prepare_response_for_collection( $item );
 128              }
 129  
 130              $response = rest_ensure_response( $data );
 131          }
 132  
 133          $response->header( 'X-WP-Total', (string) $total_abilities );
 134          $response->header( 'X-WP-TotalPages', (string) $max_pages );
 135  
 136          $query_params = $request->get_query_params();
 137          $base         = add_query_arg( urlencode_deep( $query_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
 138  
 139          if ( $page > 1 ) {
 140              $prev_page = $page - 1;
 141              $prev_link = add_query_arg( 'page', $prev_page, $base );
 142              $response->link_header( 'prev', $prev_link );
 143          }
 144  
 145          if ( $page < $max_pages ) {
 146              $next_page = $page + 1;
 147              $next_link = add_query_arg( 'page', $next_page, $base );
 148              $response->link_header( 'next', $next_link );
 149          }
 150  
 151          return $response;
 152      }
 153  
 154      /**
 155       * Retrieves a specific ability.
 156       *
 157       * @since 6.9.0
 158       *
 159       * @param WP_REST_Request $request Full details about the request.
 160       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 161       */
 162  	public function get_item( $request ) {
 163          $ability = wp_has_ability( $request['name'] ) ? wp_get_ability( $request['name'] ) : null;
 164          if ( ! $ability || ! $ability->get_meta_item( 'show_in_rest' ) ) {
 165              return new WP_Error(
 166                  'rest_ability_not_found',
 167                  __( 'Ability not found.' ),
 168                  array( 'status' => 404 )
 169              );
 170          }
 171  
 172          $data = $this->prepare_item_for_response( $ability, $request );
 173          return rest_ensure_response( $data );
 174      }
 175  
 176      /**
 177       * Checks if a given request has access to read ability items.
 178       *
 179       * @since 6.9.0
 180       *
 181       * @param WP_REST_Request $request Full details about the request.
 182       * @return bool True if the request has read access.
 183       */
 184  	public function get_items_permissions_check( $request ) {
 185          return current_user_can( 'read' );
 186      }
 187  
 188      /**
 189       * Checks if a given request has access to read an ability item.
 190       *
 191       * @since 6.9.0
 192       *
 193       * @param WP_REST_Request $request Full details about the request.
 194       * @return bool True if the request has read access.
 195       */
 196  	public function get_item_permissions_check( $request ) {
 197          return current_user_can( 'read' );
 198      }
 199  
 200      /**
 201       * Prepares an ability for response.
 202       *
 203       * @since 6.9.0
 204       *
 205       * @param WP_Ability      $ability The ability object.
 206       * @param WP_REST_Request $request Request object.
 207       * @return WP_REST_Response Response object.
 208       */
 209  	public function prepare_item_for_response( $ability, $request ) {
 210          $data = array(
 211              'name'                  => $ability->get_name(),
 212              'label'                 => $ability->get_label(),
 213              'description'           => $ability->get_description(),
 214              'category'              => $ability->get_category(),
 215              'required_capabilities' => $ability->get_required_capabilities(),
 216              'input_schema'          => wp_prepare_json_schema_for_client( $ability->get_input_schema() ),
 217              'output_schema'         => wp_prepare_json_schema_for_client( $ability->get_output_schema() ),
 218              'meta'                  => $ability->get_meta(),
 219          );
 220  
 221          $context = $request['context'] ?? 'view';
 222          $data    = $this->add_additional_fields_to_object( $data, $request );
 223          $data    = $this->filter_response_by_context( $data, $context );
 224  
 225          $response = rest_ensure_response( $data );
 226  
 227          $fields = $this->get_fields_for_response( $request );
 228          if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
 229              $links = array(
 230                  'self'       => array(
 231                      'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $ability->get_name() ) ),
 232                  ),
 233                  'collection' => array(
 234                      'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
 235                  ),
 236              );
 237  
 238              $links['wp:action-run'] = array(
 239                  'href' => rest_url( sprintf( '%s/%s/%s/run', $this->namespace, $this->rest_base, $ability->get_name() ) ),
 240              );
 241  
 242              $response->add_links( $links );
 243          }
 244  
 245          return $response;
 246      }
 247  
 248      /**
 249       * Retrieves the ability's schema, conforming to JSON Schema.
 250       *
 251       * @since 6.9.0
 252       * @since 7.1.0 Added the `meta.public` property.
 253       * @since 7.2.0 Added the `required_capabilities` property.
 254       *
 255       * @return array<string, mixed> Item schema data.
 256       */
 257  	public function get_item_schema(): array {
 258          $schema = array(
 259              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 260              'title'      => 'ability',
 261              'type'       => 'object',
 262              'properties' => array(
 263                  'name'                  => array(
 264                      'description' => __( 'Unique identifier for the ability.' ),
 265                      'type'        => 'string',
 266                      'context'     => array( 'view', 'edit', 'embed' ),
 267                      'readonly'    => true,
 268                  ),
 269                  'label'                 => array(
 270                      'description' => __( 'Display label for the ability.' ),
 271                      'type'        => 'string',
 272                      'context'     => array( 'view', 'edit', 'embed' ),
 273                      'readonly'    => true,
 274                  ),
 275                  'description'           => array(
 276                      'description' => __( 'Description of the ability.' ),
 277                      'type'        => 'string',
 278                      'context'     => array( 'view', 'edit' ),
 279                      'readonly'    => true,
 280                  ),
 281                  'category'              => array(
 282                      'description' => __( 'Ability category this ability belongs to.' ),
 283                      'type'        => 'string',
 284                      'context'     => array( 'view', 'edit', 'embed' ),
 285                      'readonly'    => true,
 286                  ),
 287                  'required_capabilities' => array(
 288                      'description' => __( 'Capabilities the current user must have to run the ability.' ),
 289                      'type'        => 'array',
 290                      'items'       => array(
 291                          'type' => 'string',
 292                      ),
 293                      'context'     => array( 'view', 'edit' ),
 294                      'readonly'    => true,
 295                  ),
 296                  'input_schema'          => array(
 297                      'description' => __( 'JSON Schema for the ability input.' ),
 298                      'type'        => 'object',
 299                      'context'     => array( 'view', 'edit' ),
 300                      'readonly'    => true,
 301                  ),
 302                  'output_schema'         => array(
 303                      'description' => __( 'JSON Schema for the ability output.' ),
 304                      'type'        => 'object',
 305                      'context'     => array( 'view', 'edit' ),
 306                      'readonly'    => true,
 307                  ),
 308                  'meta'                  => array(
 309                      'description' => __( 'Meta information about the ability.' ),
 310                      'type'        => 'object',
 311                      'properties'  => array(
 312                          'annotations' => array(
 313                              'description'          => __( 'Behavioral annotations for the ability.' ),
 314                              'type'                 => 'object',
 315                              'properties'           => array(
 316                                  'readonly'    => array(
 317                                      'description' => __( 'Whether the ability does not modify its environment.' ),
 318                                      'type'        => array( 'boolean', 'null' ),
 319                                  ),
 320                                  'destructive' => array(
 321                                      'description' => __( 'Whether the ability may perform destructive updates to its environment.' ),
 322                                      'type'        => array( 'boolean', 'null' ),
 323                                  ),
 324                                  'idempotent'  => array(
 325                                      'description' => __( 'Whether repeated calls with the same arguments have no additional effect.' ),
 326                                      'type'        => array( 'boolean', 'null' ),
 327                                  ),
 328                              ),
 329                              'additionalProperties' => true,
 330                          ),
 331                          'public'      => array(
 332                              'description' => __( 'Whether the ability is meant to be available to clients such as the REST API, MCP, or AI agents. Defaults to false, but individual channel settings such as show_in_rest can override it.' ),
 333                              'type'        => 'boolean',
 334                          ),
 335                      ),
 336                      'context'     => array( 'view', 'edit' ),
 337                      'readonly'    => true,
 338                  ),
 339              ),
 340          );
 341  
 342          return $this->add_additional_fields_schema( $schema );
 343      }
 344  
 345      /**
 346       * Retrieves the query params for collections.
 347       *
 348       * @since 6.9.0
 349       * @since 7.1.0 Added the `namespace` and `meta` parameters and the `rest_abilities_collection_params` filter.
 350       * @since 7.2.0 Added the `has_required_capabilities` parameter.
 351       *
 352       * @return array<string, mixed> Collection parameters.
 353       */
 354  	public function get_collection_params(): array {
 355          $query_params = array(
 356              'context'                   => $this->get_context_param( array( 'default' => 'view' ) ),
 357              'page'                      => array(
 358                  'description' => __( 'Current page of the collection.' ),
 359                  'type'        => 'integer',
 360                  'default'     => 1,
 361                  'minimum'     => 1,
 362              ),
 363              'per_page'                  => array(
 364                  'description' => __( 'Maximum number of items to be returned in result set.' ),
 365                  'type'        => 'integer',
 366                  'default'     => 50,
 367                  'minimum'     => 1,
 368                  'maximum'     => 100,
 369              ),
 370              'category'                  => array(
 371                  'description'       => __( 'Limit results to abilities in specific ability category.' ),
 372                  'type'              => 'string',
 373                  'sanitize_callback' => 'sanitize_key',
 374                  'validate_callback' => 'rest_validate_request_arg',
 375              ),
 376              'namespace'                 => array(
 377                  'description'       => __( 'Limit results to abilities in a specific namespace.' ),
 378                  'type'              => 'string',
 379                  'sanitize_callback' => 'sanitize_key',
 380                  'validate_callback' => 'rest_validate_request_arg',
 381              ),
 382              'meta'                      => array(
 383                  'description'          => __( 'Limit results to abilities matching all of the given meta fields.' ),
 384                  'type'                 => 'object',
 385                  'properties'           => array(
 386                      // show_in_rest is omitted on purpose. It is forced on and cannot be filtered by a caller.
 387                      'annotations' => array(
 388                          'description'          => __( 'Limit results to abilities matching the given behavioral annotations.' ),
 389                          'type'                 => 'object',
 390                          'properties'           => array(
 391                              'readonly'    => array(
 392                                  'description' => __( 'Whether the ability does not modify its environment.' ),
 393                                  'type'        => array( 'boolean', 'null' ),
 394                              ),
 395                              'destructive' => array(
 396                                  'description' => __( 'Whether the ability may perform destructive updates to its environment.' ),
 397                                  'type'        => array( 'boolean', 'null' ),
 398                              ),
 399                              'idempotent'  => array(
 400                                  'description' => __( 'Whether repeated calls with the same arguments have no additional effect.' ),
 401                                  'type'        => array( 'boolean', 'null' ),
 402                              ),
 403                          ),
 404                          'additionalProperties' => true,
 405                      ),
 406                  ),
 407                  'additionalProperties' => true,
 408              ),
 409              'has_required_capabilities' => array(
 410                  'description' => __( 'Limit results to abilities whose required capabilities the current user has. Abilities without required capabilities are always included. Permission callbacks don\'t run, so an included ability can still deny execution.' ),
 411                  'type'        => 'boolean',
 412                  'default'     => false,
 413              ),
 414          );
 415  
 416          /**
 417           * Filters REST API collection parameters for the abilities controller.
 418           *
 419           * Use this to declare the schema type of a custom meta key. A declared
 420           * type lets REST coerce a query-string value, for example "true" to a
 421           * boolean, before the meta filter matches it.
 422           *
 423           * @since 7.1.0
 424           *
 425           * @param array $query_params JSON Schema-formatted collection parameters.
 426           */
 427          return apply_filters( 'rest_abilities_collection_params', $query_params );
 428      }
 429  }


Generated : Tue Nov 3 08:20:36 2026 Cross-referenced by PHPXref