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


Generated: Tue Jul 14 01:00:03 2020 Cross-referenced by PHPXref 0.7.1