[ 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       * @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'
  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 fiter.
 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          foreach ( $data as $key => $value ) {
 300              if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
 301                  continue;
 302              }
 303  
 304              if ( ! in_array( $context, $schema['properties'][ $key ]['context'], true ) ) {
 305                  unset( $data[ $key ] );
 306                  continue;
 307              }
 308  
 309              if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
 310                  foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
 311                      if ( empty( $details['context'] ) ) {
 312                          continue;
 313                      }
 314  
 315                      if ( ! in_array( $context, $details['context'], true ) ) {
 316                          if ( isset( $data[ $key ][ $attribute ] ) ) {
 317                              unset( $data[ $key ][ $attribute ] );
 318                          }
 319                      }
 320                  }
 321              }
 322          }
 323  
 324          return $data;
 325      }
 326  
 327      /**
 328       * Retrieves the item's schema, conforming to JSON Schema.
 329       *
 330       * @since 4.7.0
 331       *
 332       * @return array Item schema data.
 333       */
 334  	public function get_item_schema() {
 335          return $this->add_additional_fields_schema( array() );
 336      }
 337  
 338      /**
 339       * Retrieves the item's schema for display / public consumption purposes.
 340       *
 341       * @since 4.7.0
 342       *
 343       * @return array Public item schema data.
 344       */
 345  	public function get_public_item_schema() {
 346  
 347          $schema = $this->get_item_schema();
 348  
 349          if ( ! empty( $schema['properties'] ) ) {
 350              foreach ( $schema['properties'] as &$property ) {
 351                  unset( $property['arg_options'] );
 352              }
 353          }
 354  
 355          return $schema;
 356      }
 357  
 358      /**
 359       * Retrieves the query params for the collections.
 360       *
 361       * @since 4.7.0
 362       *
 363       * @return array Query parameters for the collection.
 364       */
 365  	public function get_collection_params() {
 366          return array(
 367              'context'  => $this->get_context_param(),
 368              'page'     => array(
 369                  'description'       => __( 'Current page of the collection.' ),
 370                  'type'              => 'integer',
 371                  'default'           => 1,
 372                  'sanitize_callback' => 'absint',
 373                  'validate_callback' => 'rest_validate_request_arg',
 374                  'minimum'           => 1,
 375              ),
 376              'per_page' => array(
 377                  'description'       => __( 'Maximum number of items to be returned in result set.' ),
 378                  'type'              => 'integer',
 379                  'default'           => 10,
 380                  'minimum'           => 1,
 381                  'maximum'           => 100,
 382                  'sanitize_callback' => 'absint',
 383                  'validate_callback' => 'rest_validate_request_arg',
 384              ),
 385              'search'   => array(
 386                  'description'       => __( 'Limit results to those matching a string.' ),
 387                  'type'              => 'string',
 388                  'sanitize_callback' => 'sanitize_text_field',
 389                  'validate_callback' => 'rest_validate_request_arg',
 390              ),
 391          );
 392      }
 393  
 394      /**
 395       * Retrieves the magical context param.
 396       *
 397       * Ensures consistent descriptions between endpoints, and populates enum from schema.
 398       *
 399       * @since 4.7.0
 400       *
 401       * @param array $args Optional. Additional arguments for context parameter. Default empty array.
 402       * @return array Context parameter details.
 403       */
 404  	public function get_context_param( $args = array() ) {
 405          $param_details = array(
 406              'description'       => __( 'Scope under which the request is made; determines fields present in response.' ),
 407              'type'              => 'string',
 408              'sanitize_callback' => 'sanitize_key',
 409              'validate_callback' => 'rest_validate_request_arg',
 410          );
 411  
 412          $schema = $this->get_item_schema();
 413  
 414          if ( empty( $schema['properties'] ) ) {
 415              return array_merge( $param_details, $args );
 416          }
 417  
 418          $contexts = array();
 419  
 420          foreach ( $schema['properties'] as $attributes ) {
 421              if ( ! empty( $attributes['context'] ) ) {
 422                  $contexts = array_merge( $contexts, $attributes['context'] );
 423              }
 424          }
 425  
 426          if ( ! empty( $contexts ) ) {
 427              $param_details['enum'] = array_unique( $contexts );
 428              rsort( $param_details['enum'] );
 429          }
 430  
 431          return array_merge( $param_details, $args );
 432      }
 433  
 434      /**
 435       * Adds the values from additional fields to a data object.
 436       *
 437       * @since 4.7.0
 438       *
 439       * @param array           $prepared Prepared response array.
 440       * @param WP_REST_Request $request  Full details about the request.
 441       * @return array Modified data object with additional fields.
 442       */
 443  	protected function add_additional_fields_to_object( $prepared, $request ) {
 444  
 445          $additional_fields = $this->get_additional_fields();
 446  
 447          $requested_fields = $this->get_fields_for_response( $request );
 448  
 449          foreach ( $additional_fields as $field_name => $field_options ) {
 450              if ( ! $field_options['get_callback'] ) {
 451                  continue;
 452              }
 453  
 454              if ( ! rest_is_field_included( $field_name, $requested_fields ) ) {
 455                  continue;
 456              }
 457  
 458              $prepared[ $field_name ] = call_user_func( $field_options['get_callback'], $prepared, $field_name, $request, $this->get_object_type() );
 459          }
 460  
 461          return $prepared;
 462      }
 463  
 464      /**
 465       * Updates the values of additional fields added to a data object.
 466       *
 467       * @since 4.7.0
 468       *
 469       * @param object          $object  Data model like WP_Term or WP_Post.
 470       * @param WP_REST_Request $request Full details about the request.
 471       * @return bool|WP_Error True on success, WP_Error object if a field cannot be updated.
 472       */
 473  	protected function update_additional_fields_for_object( $object, $request ) {
 474          $additional_fields = $this->get_additional_fields();
 475  
 476          foreach ( $additional_fields as $field_name => $field_options ) {
 477              if ( ! $field_options['update_callback'] ) {
 478                  continue;
 479              }
 480  
 481              // Don't run the update callbacks if the data wasn't passed in the request.
 482              if ( ! isset( $request[ $field_name ] ) ) {
 483                  continue;
 484              }
 485  
 486              $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
 487  
 488              if ( is_wp_error( $result ) ) {
 489                  return $result;
 490              }
 491          }
 492  
 493          return true;
 494      }
 495  
 496      /**
 497       * Adds the schema from additional fields to a schema array.
 498       *
 499       * The type of object is inferred from the passed schema.
 500       *
 501       * @since 4.7.0
 502       *
 503       * @param array $schema Schema array.
 504       * @return array Modified Schema array.
 505       */
 506  	protected function add_additional_fields_schema( $schema ) {
 507          if ( empty( $schema['title'] ) ) {
 508              return $schema;
 509          }
 510  
 511          // Can't use $this->get_object_type otherwise we cause an inf loop.
 512          $object_type = $schema['title'];
 513  
 514          $additional_fields = $this->get_additional_fields( $object_type );
 515  
 516          foreach ( $additional_fields as $field_name => $field_options ) {
 517              if ( ! $field_options['schema'] ) {
 518                  continue;
 519              }
 520  
 521              $schema['properties'][ $field_name ] = $field_options['schema'];
 522          }
 523  
 524          return $schema;
 525      }
 526  
 527      /**
 528       * Retrieves all of the registered additional fields for a given object-type.
 529       *
 530       * @since 4.7.0
 531       *
 532       * @param string $object_type Optional. The object type.
 533       * @return array Registered additional fields (if any), empty array if none or if the object type could
 534       *               not be inferred.
 535       */
 536  	protected function get_additional_fields( $object_type = null ) {
 537  
 538          if ( ! $object_type ) {
 539              $object_type = $this->get_object_type();
 540          }
 541  
 542          if ( ! $object_type ) {
 543              return array();
 544          }
 545  
 546          global $wp_rest_additional_fields;
 547  
 548          if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
 549              return array();
 550          }
 551  
 552          return $wp_rest_additional_fields[ $object_type ];
 553      }
 554  
 555      /**
 556       * Retrieves the object type this controller is responsible for managing.
 557       *
 558       * @since 4.7.0
 559       *
 560       * @return string Object type for the controller.
 561       */
 562  	protected function get_object_type() {
 563          $schema = $this->get_item_schema();
 564  
 565          if ( ! $schema || ! isset( $schema['title'] ) ) {
 566              return null;
 567          }
 568  
 569          return $schema['title'];
 570      }
 571  
 572      /**
 573       * Gets an array of fields to be included on the response.
 574       *
 575       * Included fields are based on item schema and `_fields=` request argument.
 576       *
 577       * @since 4.9.6
 578       *
 579       * @param WP_REST_Request $request Full details about the request.
 580       * @return array Fields to be included in the response.
 581       */
 582  	public function get_fields_for_response( $request ) {
 583          $schema     = $this->get_item_schema();
 584          $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
 585  
 586          $additional_fields = $this->get_additional_fields();
 587  
 588          foreach ( $additional_fields as $field_name => $field_options ) {
 589              // For back-compat, include any field with an empty schema
 590              // because it won't be present in $this->get_item_schema().
 591              if ( is_null( $field_options['schema'] ) ) {
 592                  $properties[ $field_name ] = $field_options;
 593              }
 594          }
 595  
 596          // Exclude fields that specify a different context than the request context.
 597          $context = $request['context'];
 598          if ( $context ) {
 599              foreach ( $properties as $name => $options ) {
 600                  if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
 601                      unset( $properties[ $name ] );
 602                  }
 603              }
 604          }
 605  
 606          $fields = array_keys( $properties );
 607  
 608          if ( ! isset( $request['_fields'] ) ) {
 609              return $fields;
 610          }
 611          $requested_fields = wp_parse_list( $request['_fields'] );
 612          if ( 0 === count( $requested_fields ) ) {
 613              return $fields;
 614          }
 615          // Trim off outside whitespace from the comma delimited list.
 616          $requested_fields = array_map( 'trim', $requested_fields );
 617          // Always persist 'id', because it can be needed for add_additional_fields_to_object().
 618          if ( in_array( 'id', $fields, true ) ) {
 619              $requested_fields[] = 'id';
 620          }
 621          // Return the list of all requested fields which appear in the schema.
 622          return array_reduce(
 623              $requested_fields,
 624              function( $response_fields, $field ) use ( $fields ) {
 625                  if ( in_array( $field, $fields, true ) ) {
 626                      $response_fields[] = $field;
 627                      return $response_fields;
 628                  }
 629                  // Check for nested fields if $field is not a direct match.
 630                  $nested_fields = explode( '.', $field );
 631                  // A nested field is included so long as its top-level property
 632                  // is present in the schema.
 633                  if ( in_array( $nested_fields[0], $fields, true ) ) {
 634                      $response_fields[] = $field;
 635                  }
 636                  return $response_fields;
 637              },
 638              array()
 639          );
 640      }
 641  
 642      /**
 643       * Retrieves an array of endpoint arguments from the item schema for the controller.
 644       *
 645       * @since 4.7.0
 646       *
 647       * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are
 648       *                       checked for required values and may fall-back to a given default, this is not done
 649       *                       on `EDITABLE` requests. Default WP_REST_Server::CREATABLE.
 650       * @return array Endpoint arguments.
 651       */
 652  	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
 653  
 654          $schema            = $this->get_item_schema();
 655          $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
 656          $endpoint_args     = array();
 657  
 658          foreach ( $schema_properties as $field_id => $params ) {
 659  
 660              // Arguments specified as `readonly` are not allowed to be set.
 661              if ( ! empty( $params['readonly'] ) ) {
 662                  continue;
 663              }
 664  
 665              $endpoint_args[ $field_id ] = array(
 666                  'validate_callback' => 'rest_validate_request_arg',
 667                  'sanitize_callback' => 'rest_sanitize_request_arg',
 668              );
 669  
 670              if ( isset( $params['description'] ) ) {
 671                  $endpoint_args[ $field_id ]['description'] = $params['description'];
 672              }
 673  
 674              if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
 675                  $endpoint_args[ $field_id ]['default'] = $params['default'];
 676              }
 677  
 678              if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
 679                  $endpoint_args[ $field_id ]['required'] = true;
 680              }
 681  
 682              foreach ( array( 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties' ) as $schema_prop ) {
 683                  if ( isset( $params[ $schema_prop ] ) ) {
 684                      $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
 685                  }
 686              }
 687  
 688              // Merge in any options provided by the schema property.
 689              if ( isset( $params['arg_options'] ) ) {
 690  
 691                  // Only use required / default from arg_options on CREATABLE endpoints.
 692                  if ( WP_REST_Server::CREATABLE !== $method ) {
 693                      $params['arg_options'] = array_diff_key(
 694                          $params['arg_options'],
 695                          array(
 696                              'required' => '',
 697                              'default'  => '',
 698                          )
 699                      );
 700                  }
 701  
 702                  $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
 703              }
 704          }
 705  
 706          return $endpoint_args;
 707      }
 708  
 709      /**
 710       * Sanitizes the slug value.
 711       *
 712       * @since 4.7.0
 713       *
 714       * @internal We can't use sanitize_title() directly, as the second
 715       * parameter is the fallback title, which would end up being set to the
 716       * request object.
 717       *
 718       * @see https://github.com/WP-API/WP-API/issues/1585
 719       *
 720       * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
 721       *
 722       * @param string $slug Slug value passed in request.
 723       * @return string Sanitized value for the slug.
 724       */
 725  	public function sanitize_slug( $slug ) {
 726          return sanitize_title( $slug );
 727      }
 728  }


Generated: Mon Apr 6 01:00:03 2020 Cross-referenced by PHPXref 0.7.1