[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   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  }


Generated : Fri Apr 19 08:20:01 2024 Cross-referenced by PHPXref