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