[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 4.7.0 8 */ 9 10 /** 11 * Core base controller for managing and interacting with REST API items. 12 * 13 * @since 4.7.0 14 */ 15 abstract class WP_REST_Controller { 16 17 /** 18 * The namespace of this controller's route. 19 * 20 * @since 4.7.0 21 * @var string 22 */ 23 protected $namespace; 24 25 /** 26 * The base of this controller's route. 27 * 28 * @since 4.7.0 29 * @var string 30 */ 31 protected $rest_base; 32 33 /** 34 * Cached results of get_item_schema. 35 * 36 * @since 5.3.0 37 * @var array 38 */ 39 protected $schema; 40 41 /** 42 * Registers the routes for the objects of the controller. 43 * 44 * @since 4.7.0 45 * 46 * @see register_rest_route() 47 */ 48 public function register_routes() { 49 _doing_it_wrong( 50 'WP_REST_Controller::register_routes', 51 /* translators: %s: register_routes() */ 52 sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ), 53 '4.7.0' 54 ); 55 } 56 57 /** 58 * Checks if a given request has access to get items. 59 * 60 * @since 4.7.0 61 * 62 * @param WP_REST_Request $request Full details about the request. 63 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 64 */ 65 public function get_items_permissions_check( $request ) { 66 return new WP_Error( 67 'invalid-method', 68 /* translators: %s: Method name. */ 69 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 70 array( 'status' => 405 ) 71 ); 72 } 73 74 /** 75 * Retrieves a collection of items. 76 * 77 * @since 4.7.0 78 * 79 * @param WP_REST_Request $request Full details about the request. 80 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 81 */ 82 public function get_items( $request ) { 83 return new WP_Error( 84 'invalid-method', 85 /* translators: %s: Method name. */ 86 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 87 array( 'status' => 405 ) 88 ); 89 } 90 91 /** 92 * Checks if a given request has access to get a specific item. 93 * 94 * @since 4.7.0 95 * 96 * @param WP_REST_Request $request Full details about the request. 97 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. 98 */ 99 public function get_item_permissions_check( $request ) { 100 return new WP_Error( 101 'invalid-method', 102 /* translators: %s: Method name. */ 103 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 104 array( 'status' => 405 ) 105 ); 106 } 107 108 /** 109 * Retrieves one item from the collection. 110 * 111 * @since 4.7.0 112 * 113 * @param WP_REST_Request $request Full details about the request. 114 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 115 */ 116 public function get_item( $request ) { 117 return new WP_Error( 118 'invalid-method', 119 /* translators: %s: Method name. */ 120 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 121 array( 'status' => 405 ) 122 ); 123 } 124 125 /** 126 * Checks if a given request has access to create items. 127 * 128 * @since 4.7.0 129 * 130 * @param WP_REST_Request $request Full details about the request. 131 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. 132 */ 133 public function create_item_permissions_check( $request ) { 134 return new WP_Error( 135 'invalid-method', 136 /* translators: %s: Method name. */ 137 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 138 array( 'status' => 405 ) 139 ); 140 } 141 142 /** 143 * Creates one item from the collection. 144 * 145 * @since 4.7.0 146 * 147 * @param WP_REST_Request $request Full details about the request. 148 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 149 */ 150 public function create_item( $request ) { 151 return new WP_Error( 152 'invalid-method', 153 /* translators: %s: Method name. */ 154 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 155 array( 'status' => 405 ) 156 ); 157 } 158 159 /** 160 * Checks if a given request has access to update a specific item. 161 * 162 * @since 4.7.0 163 * 164 * @param WP_REST_Request $request Full details about the request. 165 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. 166 */ 167 public function update_item_permissions_check( $request ) { 168 return new WP_Error( 169 'invalid-method', 170 /* translators: %s: Method name. */ 171 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 172 array( 'status' => 405 ) 173 ); 174 } 175 176 /** 177 * Updates one item from the collection. 178 * 179 * @since 4.7.0 180 * 181 * @param WP_REST_Request $request Full details about the request. 182 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 183 */ 184 public function update_item( $request ) { 185 return new WP_Error( 186 'invalid-method', 187 /* translators: %s: Method name. */ 188 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 189 array( 'status' => 405 ) 190 ); 191 } 192 193 /** 194 * Checks if a given request has access to delete a specific item. 195 * 196 * @since 4.7.0 197 * 198 * @param WP_REST_Request $request Full details about the request. 199 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. 200 */ 201 public function delete_item_permissions_check( $request ) { 202 return new WP_Error( 203 'invalid-method', 204 /* translators: %s: Method name. */ 205 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 206 array( 'status' => 405 ) 207 ); 208 } 209 210 /** 211 * Deletes one item from the collection. 212 * 213 * @since 4.7.0 214 * 215 * @param WP_REST_Request $request Full details about the request. 216 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 217 */ 218 public function delete_item( $request ) { 219 return new WP_Error( 220 'invalid-method', 221 /* translators: %s: Method name. */ 222 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 223 array( 'status' => 405 ) 224 ); 225 } 226 227 /** 228 * Prepares one item for create or update operation. 229 * 230 * @since 4.7.0 231 * 232 * @param WP_REST_Request $request Request object. 233 * @return object|WP_Error The prepared item, or WP_Error object on failure. 234 */ 235 protected function prepare_item_for_database( $request ) { 236 return new WP_Error( 237 'invalid-method', 238 /* translators: %s: Method name. */ 239 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 240 array( 'status' => 405 ) 241 ); 242 } 243 244 /** 245 * Prepares the item for the REST response. 246 * 247 * @since 4.7.0 248 * 249 * @param mixed $item WordPress representation of the item. 250 * @param WP_REST_Request $request Request object. 251 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 252 */ 253 public function prepare_item_for_response( $item, $request ) { 254 return new WP_Error( 255 'invalid-method', 256 /* translators: %s: Method name. */ 257 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), 258 array( 'status' => 405 ) 259 ); 260 } 261 262 /** 263 * Prepares a response for insertion into a collection. 264 * 265 * @since 4.7.0 266 * 267 * @param WP_REST_Response $response Response object. 268 * @return array|mixed Response data, ready for insertion into collection data. 269 */ 270 public function prepare_response_for_collection( $response ) { 271 if ( ! ( $response instanceof WP_REST_Response ) ) { 272 return $response; 273 } 274 275 $data = (array) $response->get_data(); 276 $server = rest_get_server(); 277 $links = $server::get_compact_response_links( $response ); 278 279 if ( ! empty( $links ) ) { 280 $data['_links'] = $links; 281 } 282 283 return $data; 284 } 285 286 /** 287 * Filters a response based on the context defined in the schema. 288 * 289 * @since 4.7.0 290 * 291 * @param array $data Response data to filter. 292 * @param string $context Context defined in the schema. 293 * @return array Filtered response. 294 */ 295 public function filter_response_by_context( $data, $context ) { 296 297 $schema = $this->get_item_schema(); 298 299 return rest_filter_response_by_context( $data, $schema, $context ); 300 } 301 302 /** 303 * Retrieves the item's schema, conforming to JSON Schema. 304 * 305 * @since 4.7.0 306 * 307 * @return array Item schema data. 308 */ 309 public function get_item_schema() { 310 return $this->add_additional_fields_schema( array() ); 311 } 312 313 /** 314 * Retrieves the item's schema for display / public consumption purposes. 315 * 316 * @since 4.7.0 317 * 318 * @return array Public item schema data. 319 */ 320 public function get_public_item_schema() { 321 322 $schema = $this->get_item_schema(); 323 324 if ( ! empty( $schema['properties'] ) ) { 325 foreach ( $schema['properties'] as &$property ) { 326 unset( $property['arg_options'] ); 327 } 328 } 329 330 return $schema; 331 } 332 333 /** 334 * Retrieves the query params for the collections. 335 * 336 * @since 4.7.0 337 * 338 * @return array Query parameters for the collection. 339 */ 340 public function get_collection_params() { 341 return array( 342 'context' => $this->get_context_param(), 343 'page' => array( 344 'description' => __( 'Current page of the collection.' ), 345 'type' => 'integer', 346 'default' => 1, 347 'sanitize_callback' => 'absint', 348 'validate_callback' => 'rest_validate_request_arg', 349 'minimum' => 1, 350 ), 351 'per_page' => array( 352 'description' => __( 'Maximum number of items to be returned in result set.' ), 353 'type' => 'integer', 354 'default' => 10, 355 'minimum' => 1, 356 'maximum' => 100, 357 'sanitize_callback' => 'absint', 358 'validate_callback' => 'rest_validate_request_arg', 359 ), 360 'search' => array( 361 'description' => __( 'Limit results to those matching a string.' ), 362 'type' => 'string', 363 'sanitize_callback' => 'sanitize_text_field', 364 'validate_callback' => 'rest_validate_request_arg', 365 ), 366 ); 367 } 368 369 /** 370 * Retrieves the magical context param. 371 * 372 * Ensures consistent descriptions between endpoints, and populates enum from schema. 373 * 374 * @since 4.7.0 375 * 376 * @param array $args Optional. Additional arguments for context parameter. Default empty array. 377 * @return array Context parameter details. 378 */ 379 public function get_context_param( $args = array() ) { 380 $param_details = array( 381 'description' => __( 'Scope under which the request is made; determines fields present in response.' ), 382 'type' => 'string', 383 'sanitize_callback' => 'sanitize_key', 384 'validate_callback' => 'rest_validate_request_arg', 385 ); 386 387 $schema = $this->get_item_schema(); 388 389 if ( empty( $schema['properties'] ) ) { 390 return array_merge( $param_details, $args ); 391 } 392 393 $contexts = array(); 394 395 foreach ( $schema['properties'] as $attributes ) { 396 if ( ! empty( $attributes['context'] ) ) { 397 $contexts = array_merge( $contexts, $attributes['context'] ); 398 } 399 } 400 401 if ( ! empty( $contexts ) ) { 402 $param_details['enum'] = array_unique( $contexts ); 403 rsort( $param_details['enum'] ); 404 } 405 406 return array_merge( $param_details, $args ); 407 } 408 409 /** 410 * Adds the values from additional fields to a data object. 411 * 412 * @since 4.7.0 413 * 414 * @param array $prepared Prepared response array. 415 * @param WP_REST_Request $request Full details about the request. 416 * @return array Modified data object with additional fields. 417 */ 418 protected function add_additional_fields_to_object( $prepared, $request ) { 419 420 $additional_fields = $this->get_additional_fields(); 421 422 $requested_fields = $this->get_fields_for_response( $request ); 423 424 foreach ( $additional_fields as $field_name => $field_options ) { 425 if ( ! $field_options['get_callback'] ) { 426 continue; 427 } 428 429 if ( ! rest_is_field_included( $field_name, $requested_fields ) ) { 430 continue; 431 } 432 433 $prepared[ $field_name ] = call_user_func( $field_options['get_callback'], $prepared, $field_name, $request, $this->get_object_type() ); 434 } 435 436 return $prepared; 437 } 438 439 /** 440 * Updates the values of additional fields added to a data object. 441 * 442 * @since 4.7.0 443 * 444 * @param object $object Data model like WP_Term or WP_Post. 445 * @param WP_REST_Request $request Full details about the request. 446 * @return true|WP_Error True on success, WP_Error object if a field cannot be updated. 447 */ 448 protected function update_additional_fields_for_object( $object, $request ) { 449 $additional_fields = $this->get_additional_fields(); 450 451 foreach ( $additional_fields as $field_name => $field_options ) { 452 if ( ! $field_options['update_callback'] ) { 453 continue; 454 } 455 456 // Don't run the update callbacks if the data wasn't passed in the request. 457 if ( ! isset( $request[ $field_name ] ) ) { 458 continue; 459 } 460 461 $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() ); 462 463 if ( is_wp_error( $result ) ) { 464 return $result; 465 } 466 } 467 468 return true; 469 } 470 471 /** 472 * Adds the schema from additional fields to a schema array. 473 * 474 * The type of object is inferred from the passed schema. 475 * 476 * @since 4.7.0 477 * 478 * @param array $schema Schema array. 479 * @return array Modified Schema array. 480 */ 481 protected function add_additional_fields_schema( $schema ) { 482 if ( empty( $schema['title'] ) ) { 483 return $schema; 484 } 485 486 // Can't use $this->get_object_type otherwise we cause an inf loop. 487 $object_type = $schema['title']; 488 489 $additional_fields = $this->get_additional_fields( $object_type ); 490 491 foreach ( $additional_fields as $field_name => $field_options ) { 492 if ( ! $field_options['schema'] ) { 493 continue; 494 } 495 496 $schema['properties'][ $field_name ] = $field_options['schema']; 497 } 498 499 return $schema; 500 } 501 502 /** 503 * Retrieves all of the registered additional fields for a given object-type. 504 * 505 * @since 4.7.0 506 * 507 * @global array $wp_rest_additional_fields Holds registered fields, organized by object type. 508 * 509 * @param string $object_type Optional. The object type. 510 * @return array Registered additional fields (if any), empty array if none or if the object type 511 * could not be inferred. 512 */ 513 protected function get_additional_fields( $object_type = null ) { 514 global $wp_rest_additional_fields; 515 516 if ( ! $object_type ) { 517 $object_type = $this->get_object_type(); 518 } 519 520 if ( ! $object_type ) { 521 return array(); 522 } 523 524 if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) { 525 return array(); 526 } 527 528 return $wp_rest_additional_fields[ $object_type ]; 529 } 530 531 /** 532 * Retrieves the object type this controller is responsible for managing. 533 * 534 * @since 4.7.0 535 * 536 * @return string Object type for the controller. 537 */ 538 protected function get_object_type() { 539 $schema = $this->get_item_schema(); 540 541 if ( ! $schema || ! isset( $schema['title'] ) ) { 542 return null; 543 } 544 545 return $schema['title']; 546 } 547 548 /** 549 * Gets an array of fields to be included on the response. 550 * 551 * Included fields are based on item schema and `_fields=` request argument. 552 * 553 * @since 4.9.6 554 * 555 * @param WP_REST_Request $request Full details about the request. 556 * @return string[] Fields to be included in the response. 557 */ 558 public function get_fields_for_response( $request ) { 559 $schema = $this->get_item_schema(); 560 $properties = isset( $schema['properties'] ) ? $schema['properties'] : array(); 561 562 $additional_fields = $this->get_additional_fields(); 563 564 foreach ( $additional_fields as $field_name => $field_options ) { 565 // For back-compat, include any field with an empty schema 566 // because it won't be present in $this->get_item_schema(). 567 if ( is_null( $field_options['schema'] ) ) { 568 $properties[ $field_name ] = $field_options; 569 } 570 } 571 572 // Exclude fields that specify a different context than the request context. 573 $context = $request['context']; 574 if ( $context ) { 575 foreach ( $properties as $name => $options ) { 576 if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) { 577 unset( $properties[ $name ] ); 578 } 579 } 580 } 581 582 $fields = array_keys( $properties ); 583 584 if ( ! isset( $request['_fields'] ) ) { 585 return $fields; 586 } 587 $requested_fields = wp_parse_list( $request['_fields'] ); 588 if ( 0 === count( $requested_fields ) ) { 589 return $fields; 590 } 591 // Trim off outside whitespace from the comma delimited list. 592 $requested_fields = array_map( 'trim', $requested_fields ); 593 // Always persist 'id', because it can be needed for add_additional_fields_to_object(). 594 if ( in_array( 'id', $fields, true ) ) { 595 $requested_fields[] = 'id'; 596 } 597 // Return the list of all requested fields which appear in the schema. 598 return array_reduce( 599 $requested_fields, 600 static function( $response_fields, $field ) use ( $fields ) { 601 if ( in_array( $field, $fields, true ) ) { 602 $response_fields[] = $field; 603 return $response_fields; 604 } 605 // Check for nested fields if $field is not a direct match. 606 $nested_fields = explode( '.', $field ); 607 // A nested field is included so long as its top-level property 608 // is present in the schema. 609 if ( in_array( $nested_fields[0], $fields, true ) ) { 610 $response_fields[] = $field; 611 } 612 return $response_fields; 613 }, 614 array() 615 ); 616 } 617 618 /** 619 * Retrieves an array of endpoint arguments from the item schema for the controller. 620 * 621 * @since 4.7.0 622 * 623 * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are 624 * checked for required values and may fall-back to a given default, this is not done 625 * on `EDITABLE` requests. Default WP_REST_Server::CREATABLE. 626 * @return array Endpoint arguments. 627 */ 628 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { 629 return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method ); 630 } 631 632 /** 633 * Sanitizes the slug value. 634 * 635 * @since 4.7.0 636 * 637 * @internal We can't use sanitize_title() directly, as the second 638 * parameter is the fallback title, which would end up being set to the 639 * request object. 640 * 641 * @see https://github.com/WP-API/WP-API/issues/1585 642 * 643 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659 644 * 645 * @param string $slug Slug value passed in request. 646 * @return string Sanitized value for the slug. 647 */ 648 public function sanitize_slug( $slug ) { 649 return sanitize_title( $slug ); 650 } 651 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |