[ Index ]

PHP Cross Reference of WordPress

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


Generated: Tue Sep 17 01:00:03 2019 Cross-referenced by PHPXref 0.7.1