| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Nov 3 08:20:36 2026 | Cross-referenced by PHPXref |