[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

   1  <?php
   2  /**
   3   * REST API: WP_REST_Server class
   4   *
   5   * @package WordPress
   6   * @subpackage REST_API
   7   * @since 4.4.0
   8   */
   9  
  10  /**
  11   * Core class used to implement the WordPress REST API server.
  12   *
  13   * @since 4.4.0
  14   */
  15  class WP_REST_Server {
  16  
  17      /**
  18       * Alias for GET transport method.
  19       *
  20       * @since 4.4.0
  21       * @var string
  22       */
  23      const READABLE = 'GET';
  24  
  25      /**
  26       * Alias for POST transport method.
  27       *
  28       * @since 4.4.0
  29       * @var string
  30       */
  31      const CREATABLE = 'POST';
  32  
  33      /**
  34       * Alias for POST, PUT, PATCH transport methods together.
  35       *
  36       * @since 4.4.0
  37       * @var string
  38       */
  39      const EDITABLE = 'POST, PUT, PATCH';
  40  
  41      /**
  42       * Alias for DELETE transport method.
  43       *
  44       * @since 4.4.0
  45       * @var string
  46       */
  47      const DELETABLE = 'DELETE';
  48  
  49      /**
  50       * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
  51       *
  52       * @since 4.4.0
  53       * @var string
  54       */
  55      const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
  56  
  57      /**
  58       * Namespaces registered to the server.
  59       *
  60       * @since 4.4.0
  61       * @var array
  62       */
  63      protected $namespaces = array();
  64  
  65      /**
  66       * Endpoints registered to the server.
  67       *
  68       * @since 4.4.0
  69       * @var array
  70       */
  71      protected $endpoints = array();
  72  
  73      /**
  74       * Options defined for the routes.
  75       *
  76       * @since 4.4.0
  77       * @var array
  78       */
  79      protected $route_options = array();
  80  
  81      /**
  82       * Caches embedded requests.
  83       *
  84       * @since 5.4.0
  85       * @var array
  86       */
  87      protected $embed_cache = array();
  88  
  89      /**
  90       * Instantiates the REST server.
  91       *
  92       * @since 4.4.0
  93       */
  94  	public function __construct() {
  95          $this->endpoints = array(
  96              // Meta endpoints.
  97              '/'         => array(
  98                  'callback' => array( $this, 'get_index' ),
  99                  'methods'  => 'GET',
 100                  'args'     => array(
 101                      'context' => array(
 102                          'default' => 'view',
 103                      ),
 104                  ),
 105              ),
 106              '/batch/v1' => array(
 107                  'callback' => array( $this, 'serve_batch_request_v1' ),
 108                  'methods'  => 'POST',
 109                  'args'     => array(
 110                      'validation' => array(
 111                          'type'    => 'string',
 112                          'enum'    => array( 'require-all-validate', 'normal' ),
 113                          'default' => 'normal',
 114                      ),
 115                      'requests'   => array(
 116                          'required' => true,
 117                          'type'     => 'array',
 118                          'maxItems' => $this->get_max_batch_size(),
 119                          'items'    => array(
 120                              'type'       => 'object',
 121                              'properties' => array(
 122                                  'method'  => array(
 123                                      'type'    => 'string',
 124                                      'enum'    => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
 125                                      'default' => 'POST',
 126                                  ),
 127                                  'path'    => array(
 128                                      'type'     => 'string',
 129                                      'required' => true,
 130                                  ),
 131                                  'body'    => array(
 132                                      'type'                 => 'object',
 133                                      'properties'           => array(),
 134                                      'additionalProperties' => true,
 135                                  ),
 136                                  'headers' => array(
 137                                      'type'                 => 'object',
 138                                      'properties'           => array(),
 139                                      'additionalProperties' => array(
 140                                          'type'  => array( 'string', 'array' ),
 141                                          'items' => array(
 142                                              'type' => 'string',
 143                                          ),
 144                                      ),
 145                                  ),
 146                              ),
 147                          ),
 148                      ),
 149                  ),
 150              ),
 151          );
 152      }
 153  
 154  
 155      /**
 156       * Checks the authentication headers if supplied.
 157       *
 158       * @since 4.4.0
 159       *
 160       * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful
 161       *                       or no authentication provided
 162       */
 163  	public function check_authentication() {
 164          /**
 165           * Filters REST API authentication errors.
 166           *
 167           * This is used to pass a WP_Error from an authentication method back to
 168           * the API.
 169           *
 170           * Authentication methods should check first if they're being used, as
 171           * multiple authentication methods can be enabled on a site (cookies,
 172           * HTTP basic auth, OAuth). If the authentication method hooked in is
 173           * not actually being attempted, null should be returned to indicate
 174           * another authentication method should check instead. Similarly,
 175           * callbacks should ensure the value is `null` before checking for
 176           * errors.
 177           *
 178           * A WP_Error instance can be returned if an error occurs, and this should
 179           * match the format used by API methods internally (that is, the `status`
 180           * data should be used). A callback can return `true` to indicate that
 181           * the authentication method was used, and it succeeded.
 182           *
 183           * @since 4.4.0
 184           *
 185           * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication
 186           *                                   method wasn't used, true if authentication succeeded.
 187           */
 188          return apply_filters( 'rest_authentication_errors', null );
 189      }
 190  
 191      /**
 192       * Converts an error to a response object.
 193       *
 194       * This iterates over all error codes and messages to change it into a flat
 195       * array. This enables simpler client behaviour, as it is represented as a
 196       * list in JSON rather than an object/map.
 197       *
 198       * @since 4.4.0
 199       * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}.
 200       *
 201       * @param WP_Error $error WP_Error instance.
 202       * @return WP_REST_Response List of associative arrays with code and message keys.
 203       */
 204  	protected function error_to_response( $error ) {
 205          return rest_convert_error_to_response( $error );
 206      }
 207  
 208      /**
 209       * Retrieves an appropriate error representation in JSON.
 210       *
 211       * Note: This should only be used in WP_REST_Server::serve_request(), as it
 212       * cannot handle WP_Error internally. All callbacks and other internal methods
 213       * should instead return a WP_Error with the data set to an array that includes
 214       * a 'status' key, with the value being the HTTP status to send.
 215       *
 216       * @since 4.4.0
 217       *
 218       * @param string $code    WP_Error-style code.
 219       * @param string $message Human-readable message.
 220       * @param int    $status  Optional. HTTP status code to send. Default null.
 221       * @return string JSON representation of the error
 222       */
 223  	protected function json_error( $code, $message, $status = null ) {
 224          if ( $status ) {
 225              $this->set_status( $status );
 226          }
 227  
 228          $error = compact( 'code', 'message' );
 229  
 230          return wp_json_encode( $error );
 231      }
 232  
 233      /**
 234       * Handles serving a REST API request.
 235       *
 236       * Matches the current server URI to a route and runs the first matching
 237       * callback then outputs a JSON representation of the returned value.
 238       *
 239       * @since 4.4.0
 240       *
 241       * @see WP_REST_Server::dispatch()
 242       *
 243       * @global WP_User $current_user The currently authenticated user.
 244       *
 245       * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
 246       *                     Default null.
 247       * @return null|false Null if not served and a HEAD request, false otherwise.
 248       */
 249  	public function serve_request( $path = null ) {
 250          /* @var WP_User|null $current_user */
 251          global $current_user;
 252  
 253          if ( $current_user instanceof WP_User && ! $current_user->exists() ) {
 254              /*
 255               * If there is no current user authenticated via other means, clear
 256               * the cached lack of user, so that an authenticate check can set it
 257               * properly.
 258               *
 259               * This is done because for authentications such as Application
 260               * Passwords, we don't want it to be accepted unless the current HTTP
 261               * request is a REST API request, which can't always be identified early
 262               * enough in evaluation.
 263               */
 264              $current_user = null;
 265          }
 266  
 267          /**
 268           * Filters whether JSONP is enabled for the REST API.
 269           *
 270           * @since 4.4.0
 271           *
 272           * @param bool $jsonp_enabled Whether JSONP is enabled. Default true.
 273           */
 274          $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
 275  
 276          $jsonp_callback = false;
 277          if ( isset( $_GET['_jsonp'] ) ) {
 278              $jsonp_callback = $_GET['_jsonp'];
 279          }
 280  
 281          $content_type = ( $jsonp_callback && $jsonp_enabled ) ? 'application/javascript' : 'application/json';
 282          $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
 283          $this->send_header( 'X-Robots-Tag', 'noindex' );
 284  
 285          $api_root = get_rest_url();
 286          if ( ! empty( $api_root ) ) {
 287              $this->send_header( 'Link', '<' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"' );
 288          }
 289  
 290          /*
 291           * Mitigate possible JSONP Flash attacks.
 292           *
 293           * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
 294           */
 295          $this->send_header( 'X-Content-Type-Options', 'nosniff' );
 296          $expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );
 297  
 298          /**
 299           * Filters the list of response headers that are exposed to REST API CORS requests.
 300           *
 301           * @since 5.5.0
 302           *
 303           * @param string[] $expose_headers The list of response headers to expose.
 304           */
 305          $expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers );
 306  
 307          $this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );
 308  
 309          $allow_headers = array(
 310              'Authorization',
 311              'X-WP-Nonce',
 312              'Content-Disposition',
 313              'Content-MD5',
 314              'Content-Type',
 315          );
 316  
 317          /**
 318           * Filters the list of request headers that are allowed for REST API CORS requests.
 319           *
 320           * The allowed headers are passed to the browser to specify which
 321           * headers can be passed to the REST API. By default, we allow the
 322           * Content-* headers needed to upload files to the media endpoints.
 323           * As well as the Authorization and Nonce headers for allowing authentication.
 324           *
 325           * @since 5.5.0
 326           *
 327           * @param string[] $allow_headers The list of request headers to allow.
 328           */
 329          $allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers );
 330  
 331          $this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );
 332  
 333          /**
 334           * Filters whether to send nocache headers on a REST API request.
 335           *
 336           * @since 4.4.0
 337           *
 338           * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
 339           */
 340          $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
 341          if ( $send_no_cache_headers ) {
 342              foreach ( wp_get_nocache_headers() as $header => $header_value ) {
 343                  if ( empty( $header_value ) ) {
 344                      $this->remove_header( $header );
 345                  } else {
 346                      $this->send_header( $header, $header_value );
 347                  }
 348              }
 349          }
 350  
 351          /**
 352           * Filters whether the REST API is enabled.
 353           *
 354           * @since 4.4.0
 355           * @deprecated 4.7.0 Use the {@see 'rest_authentication_errors'} filter to
 356           *                   restrict access to the REST API.
 357           *
 358           * @param bool $rest_enabled Whether the REST API is enabled. Default true.
 359           */
 360          apply_filters_deprecated(
 361              'rest_enabled',
 362              array( true ),
 363              '4.7.0',
 364              'rest_authentication_errors',
 365              sprintf(
 366                  /* translators: %s: rest_authentication_errors */
 367                  __( 'The REST API can no longer be completely disabled, the %s filter can be used to restrict access to the API, instead.' ),
 368                  'rest_authentication_errors'
 369              )
 370          );
 371  
 372          if ( $jsonp_callback ) {
 373              if ( ! $jsonp_enabled ) {
 374                  echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
 375                  return false;
 376              }
 377  
 378              if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) {
 379                  echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 );
 380                  return false;
 381              }
 382          }
 383  
 384          if ( empty( $path ) ) {
 385              if ( isset( $_SERVER['PATH_INFO'] ) ) {
 386                  $path = $_SERVER['PATH_INFO'];
 387              } else {
 388                  $path = '/';
 389              }
 390          }
 391  
 392          $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
 393  
 394          $request->set_query_params( wp_unslash( $_GET ) );
 395          $request->set_body_params( wp_unslash( $_POST ) );
 396          $request->set_file_params( $_FILES );
 397          $request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
 398          $request->set_body( self::get_raw_data() );
 399  
 400          /*
 401           * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
 402           * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
 403           * header.
 404           */
 405          if ( isset( $_GET['_method'] ) ) {
 406              $request->set_method( $_GET['_method'] );
 407          } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
 408              $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
 409          }
 410  
 411          $result = $this->check_authentication();
 412  
 413          if ( ! is_wp_error( $result ) ) {
 414              $result = $this->dispatch( $request );
 415          }
 416  
 417          // Normalize to either WP_Error or WP_REST_Response...
 418          $result = rest_ensure_response( $result );
 419  
 420          // ...then convert WP_Error across.
 421          if ( is_wp_error( $result ) ) {
 422              $result = $this->error_to_response( $result );
 423          }
 424  
 425          /**
 426           * Filters the REST API response.
 427           *
 428           * Allows modification of the response before returning.
 429           *
 430           * @since 4.4.0
 431           * @since 4.5.0 Applied to embedded responses.
 432           *
 433           * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
 434           * @param WP_REST_Server   $server  Server instance.
 435           * @param WP_REST_Request  $request Request used to generate the response.
 436           */
 437          $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
 438  
 439          // Wrap the response in an envelope if asked for.
 440          if ( isset( $_GET['_envelope'] ) ) {
 441              $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) );
 442          }
 443  
 444          // Send extra data from response objects.
 445          $headers = $result->get_headers();
 446          $this->send_headers( $headers );
 447  
 448          $code = $result->get_status();
 449          $this->set_status( $code );
 450  
 451          /**
 452           * Filters whether the REST API request has already been served.
 453           *
 454           * Allow sending the request manually - by returning true, the API result
 455           * will not be sent to the client.
 456           *
 457           * @since 4.4.0
 458           *
 459           * @param bool             $served  Whether the request has already been served.
 460           *                                           Default false.
 461           * @param WP_HTTP_Response $result  Result to send to the client. Usually a `WP_REST_Response`.
 462           * @param WP_REST_Request  $request Request used to generate the response.
 463           * @param WP_REST_Server   $server  Server instance.
 464           */
 465          $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
 466  
 467          if ( ! $served ) {
 468              if ( 'HEAD' === $request->get_method() ) {
 469                  return null;
 470              }
 471  
 472              // Embed links inside the request.
 473              $embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
 474              $result = $this->response_to_data( $result, $embed );
 475  
 476              /**
 477               * Filters the REST API response.
 478               *
 479               * Allows modification of the response data after inserting
 480               * embedded data (if any) and before echoing the response data.
 481               *
 482               * @since 4.8.1
 483               *
 484               * @param array            $result  Response data to send to the client.
 485               * @param WP_REST_Server   $server  Server instance.
 486               * @param WP_REST_Request  $request Request used to generate the response.
 487               */
 488              $result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );
 489  
 490              // The 204 response shouldn't have a body.
 491              if ( 204 === $code || null === $result ) {
 492                  return null;
 493              }
 494  
 495              $result = wp_json_encode( $result );
 496  
 497              $json_error_message = $this->get_json_last_error();
 498  
 499              if ( $json_error_message ) {
 500                  $json_error_obj = new WP_Error(
 501                      'rest_encode_error',
 502                      $json_error_message,
 503                      array( 'status' => 500 )
 504                  );
 505  
 506                  $result = $this->error_to_response( $json_error_obj );
 507                  $result = wp_json_encode( $result->data );
 508              }
 509  
 510              if ( $jsonp_callback ) {
 511                  // Prepend '/**/' to mitigate possible JSONP Flash attacks.
 512                  // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
 513                  echo '/**/' . $jsonp_callback . '(' . $result . ')';
 514              } else {
 515                  echo $result;
 516              }
 517          }
 518  
 519          return null;
 520      }
 521  
 522      /**
 523       * Converts a response to data to send.
 524       *
 525       * @since 4.4.0
 526       * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
 527       *
 528       * @param WP_REST_Response $response Response object.
 529       * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
 530       * @return array {
 531       *     Data with sub-requests embedded.
 532       *
 533       *     @type array $_links    Links.
 534       *     @type array $_embedded Embedded objects.
 535       * }
 536       */
 537  	public function response_to_data( $response, $embed ) {
 538          $data  = $response->get_data();
 539          $links = self::get_compact_response_links( $response );
 540  
 541          if ( ! empty( $links ) ) {
 542              // Convert links to part of the data.
 543              $data['_links'] = $links;
 544          }
 545  
 546          if ( $embed ) {
 547              $this->embed_cache = array();
 548              // Determine if this is a numeric array.
 549              if ( wp_is_numeric_array( $data ) ) {
 550                  foreach ( $data as $key => $item ) {
 551                      $data[ $key ] = $this->embed_links( $item, $embed );
 552                  }
 553              } else {
 554                  $data = $this->embed_links( $data, $embed );
 555              }
 556              $this->embed_cache = array();
 557          }
 558  
 559          return $data;
 560      }
 561  
 562      /**
 563       * Retrieves links from a response.
 564       *
 565       * Extracts the links from a response into a structured hash, suitable for
 566       * direct output.
 567       *
 568       * @since 4.4.0
 569       *
 570       * @param WP_REST_Response $response Response to extract links from.
 571       * @return array Map of link relation to list of link hashes.
 572       */
 573  	public static function get_response_links( $response ) {
 574          $links = $response->get_links();
 575  
 576          if ( empty( $links ) ) {
 577              return array();
 578          }
 579  
 580          // Convert links to part of the data.
 581          $data = array();
 582          foreach ( $links as $rel => $items ) {
 583              $data[ $rel ] = array();
 584  
 585              foreach ( $items as $item ) {
 586                  $attributes         = $item['attributes'];
 587                  $attributes['href'] = $item['href'];
 588                  $data[ $rel ][]     = $attributes;
 589              }
 590          }
 591  
 592          return $data;
 593      }
 594  
 595      /**
 596       * Retrieves the CURIEs (compact URIs) used for relations.
 597       *
 598       * Extracts the links from a response into a structured hash, suitable for
 599       * direct output.
 600       *
 601       * @since 4.5.0
 602       *
 603       * @param WP_REST_Response $response Response to extract links from.
 604       * @return array Map of link relation to list of link hashes.
 605       */
 606  	public static function get_compact_response_links( $response ) {
 607          $links = self::get_response_links( $response );
 608  
 609          if ( empty( $links ) ) {
 610              return array();
 611          }
 612  
 613          $curies      = $response->get_curies();
 614          $used_curies = array();
 615  
 616          foreach ( $links as $rel => $items ) {
 617  
 618              // Convert $rel URIs to their compact versions if they exist.
 619              foreach ( $curies as $curie ) {
 620                  $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
 621                  if ( strpos( $rel, $href_prefix ) !== 0 ) {
 622                      continue;
 623                  }
 624  
 625                  // Relation now changes from '$uri' to '$curie:$relation'.
 626                  $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
 627                  preg_match( '!' . $rel_regex . '!', $rel, $matches );
 628                  if ( $matches ) {
 629                      $new_rel                       = $curie['name'] . ':' . $matches[1];
 630                      $used_curies[ $curie['name'] ] = $curie;
 631                      $links[ $new_rel ]             = $items;
 632                      unset( $links[ $rel ] );
 633                      break;
 634                  }
 635              }
 636          }
 637  
 638          // Push the curies onto the start of the links array.
 639          if ( $used_curies ) {
 640              $links['curies'] = array_values( $used_curies );
 641          }
 642  
 643          return $links;
 644      }
 645  
 646      /**
 647       * Embeds the links from the data into the request.
 648       *
 649       * @since 4.4.0
 650       * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
 651       *
 652       * @param array         $data  Data from the request.
 653       * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations.
 654       * @return array {
 655       *     Data with sub-requests embedded.
 656       *
 657       *     @type array $_links    Links.
 658       *     @type array $_embedded Embedded objects.
 659       * }
 660       */
 661  	protected function embed_links( $data, $embed = true ) {
 662          if ( empty( $data['_links'] ) ) {
 663              return $data;
 664          }
 665  
 666          $embedded = array();
 667  
 668          foreach ( $data['_links'] as $rel => $links ) {
 669              // If a list of relations was specified, and the link relation
 670              // is not in the list of allowed relations, don't process the link.
 671              if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
 672                  continue;
 673              }
 674  
 675              $embeds = array();
 676  
 677              foreach ( $links as $item ) {
 678                  // Determine if the link is embeddable.
 679                  if ( empty( $item['embeddable'] ) ) {
 680                      // Ensure we keep the same order.
 681                      $embeds[] = array();
 682                      continue;
 683                  }
 684  
 685                  if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) {
 686                      // Run through our internal routing and serve.
 687                      $request = WP_REST_Request::from_url( $item['href'] );
 688                      if ( ! $request ) {
 689                          $embeds[] = array();
 690                          continue;
 691                      }
 692  
 693                      // Embedded resources get passed context=embed.
 694                      if ( empty( $request['context'] ) ) {
 695                          $request['context'] = 'embed';
 696                      }
 697  
 698                      $response = $this->dispatch( $request );
 699  
 700                      /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
 701                      $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
 702  
 703                      $this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false );
 704                  }
 705  
 706                  $embeds[] = $this->embed_cache[ $item['href'] ];
 707              }
 708  
 709              // Determine if any real links were found.
 710              $has_links = count( array_filter( $embeds ) );
 711  
 712              if ( $has_links ) {
 713                  $embedded[ $rel ] = $embeds;
 714              }
 715          }
 716  
 717          if ( ! empty( $embedded ) ) {
 718              $data['_embedded'] = $embedded;
 719          }
 720  
 721          return $data;
 722      }
 723  
 724      /**
 725       * Wraps the response in an envelope.
 726       *
 727       * The enveloping technique is used to work around browser/client
 728       * compatibility issues. Essentially, it converts the full HTTP response to
 729       * data instead.
 730       *
 731       * @since 4.4.0
 732       *
 733       * @param WP_REST_Response $response Response object.
 734       * @param bool             $embed    Whether links should be embedded.
 735       * @return WP_REST_Response New response with wrapped data
 736       */
 737  	public function envelope_response( $response, $embed ) {
 738          $envelope = array(
 739              'body'    => $this->response_to_data( $response, $embed ),
 740              'status'  => $response->get_status(),
 741              'headers' => $response->get_headers(),
 742          );
 743  
 744          /**
 745           * Filters the enveloped form of a REST API response.
 746           *
 747           * @since 4.4.0
 748           *
 749           * @param array            $envelope {
 750           *     Envelope data.
 751           *
 752           *     @type array $body    Response data.
 753           *     @type int   $status  The 3-digit HTTP status code.
 754           *     @type array $headers Map of header name to header value.
 755           * }
 756           * @param WP_REST_Response $response Original response data.
 757           */
 758          $envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
 759  
 760          // Ensure it's still a response and return.
 761          return rest_ensure_response( $envelope );
 762      }
 763  
 764      /**
 765       * Registers a route to the server.
 766       *
 767       * @since 4.4.0
 768       *
 769       * @param string $namespace  Namespace.
 770       * @param string $route      The REST route.
 771       * @param array  $route_args Route arguments.
 772       * @param bool   $override   Optional. Whether the route should be overridden if it already exists.
 773       *                           Default false.
 774       */
 775  	public function register_route( $namespace, $route, $route_args, $override = false ) {
 776          if ( ! isset( $this->namespaces[ $namespace ] ) ) {
 777              $this->namespaces[ $namespace ] = array();
 778  
 779              $this->register_route(
 780                  $namespace,
 781                  '/' . $namespace,
 782                  array(
 783                      array(
 784                          'methods'  => self::READABLE,
 785                          'callback' => array( $this, 'get_namespace_index' ),
 786                          'args'     => array(
 787                              'namespace' => array(
 788                                  'default' => $namespace,
 789                              ),
 790                              'context'   => array(
 791                                  'default' => 'view',
 792                              ),
 793                          ),
 794                      ),
 795                  )
 796              );
 797          }
 798  
 799          // Associative to avoid double-registration.
 800          $this->namespaces[ $namespace ][ $route ] = true;
 801          $route_args['namespace']                  = $namespace;
 802  
 803          if ( $override || empty( $this->endpoints[ $route ] ) ) {
 804              $this->endpoints[ $route ] = $route_args;
 805          } else {
 806              $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
 807          }
 808      }
 809  
 810      /**
 811       * Retrieves the route map.
 812       *
 813       * The route map is an associative array with path regexes as the keys. The
 814       * value is an indexed array with the callback function/method as the first
 815       * item, and a bitmask of HTTP methods as the second item (see the class
 816       * constants).
 817       *
 818       * Each route can be mapped to more than one callback by using an array of
 819       * the indexed arrays. This allows mapping e.g. GET requests to one callback
 820       * and POST requests to another.
 821       *
 822       * Note that the path regexes (array keys) must have @ escaped, as this is
 823       * used as the delimiter with preg_match()
 824       *
 825       * @since 4.4.0
 826       * @since 5.4.0 Add $namespace parameter.
 827       *
 828       * @param string $namespace Optionally, only return routes in the given namespace.
 829       * @return array `'/path/regex' => array( $callback, $bitmask )` or
 830       *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
 831       */
 832  	public function get_routes( $namespace = '' ) {
 833          $endpoints = $this->endpoints;
 834  
 835          if ( $namespace ) {
 836              $endpoints = wp_list_filter( $endpoints, array( 'namespace' => $namespace ) );
 837          }
 838  
 839          /**
 840           * Filters the array of available REST API endpoints.
 841           *
 842           * @since 4.4.0
 843           *
 844           * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
 845           *                         to an array of callbacks for the endpoint. These take the format
 846           *                         `'/path/regex' => array( $callback, $bitmask )` or
 847           *                         `'/path/regex' => array( array( $callback, $bitmask ).
 848           */
 849          $endpoints = apply_filters( 'rest_endpoints', $endpoints );
 850  
 851          // Normalise the endpoints.
 852          $defaults = array(
 853              'methods'       => '',
 854              'accept_json'   => false,
 855              'accept_raw'    => false,
 856              'show_in_index' => true,
 857              'args'          => array(),
 858          );
 859  
 860          foreach ( $endpoints as $route => &$handlers ) {
 861  
 862              if ( isset( $handlers['callback'] ) ) {
 863                  // Single endpoint, add one deeper.
 864                  $handlers = array( $handlers );
 865              }
 866  
 867              if ( ! isset( $this->route_options[ $route ] ) ) {
 868                  $this->route_options[ $route ] = array();
 869              }
 870  
 871              foreach ( $handlers as $key => &$handler ) {
 872  
 873                  if ( ! is_numeric( $key ) ) {
 874                      // Route option, move it to the options.
 875                      $this->route_options[ $route ][ $key ] = $handler;
 876                      unset( $handlers[ $key ] );
 877                      continue;
 878                  }
 879  
 880                  $handler = wp_parse_args( $handler, $defaults );
 881  
 882                  // Allow comma-separated HTTP methods.
 883                  if ( is_string( $handler['methods'] ) ) {
 884                      $methods = explode( ',', $handler['methods'] );
 885                  } elseif ( is_array( $handler['methods'] ) ) {
 886                      $methods = $handler['methods'];
 887                  } else {
 888                      $methods = array();
 889                  }
 890  
 891                  $handler['methods'] = array();
 892  
 893                  foreach ( $methods as $method ) {
 894                      $method                        = strtoupper( trim( $method ) );
 895                      $handler['methods'][ $method ] = true;
 896                  }
 897              }
 898          }
 899  
 900          return $endpoints;
 901      }
 902  
 903      /**
 904       * Retrieves namespaces registered on the server.
 905       *
 906       * @since 4.4.0
 907       *
 908       * @return string[] List of registered namespaces.
 909       */
 910  	public function get_namespaces() {
 911          return array_keys( $this->namespaces );
 912      }
 913  
 914      /**
 915       * Retrieves specified options for a route.
 916       *
 917       * @since 4.4.0
 918       *
 919       * @param string $route Route pattern to fetch options for.
 920       * @return array|null Data as an associative array if found, or null if not found.
 921       */
 922  	public function get_route_options( $route ) {
 923          if ( ! isset( $this->route_options[ $route ] ) ) {
 924              return null;
 925          }
 926  
 927          return $this->route_options[ $route ];
 928      }
 929  
 930      /**
 931       * Matches the request to a callback and call it.
 932       *
 933       * @since 4.4.0
 934       *
 935       * @param WP_REST_Request $request Request to attempt dispatching.
 936       * @return WP_REST_Response Response returned by the callback.
 937       */
 938  	public function dispatch( $request ) {
 939          /**
 940           * Filters the pre-calculated result of a REST API dispatch request.
 941           *
 942           * Allow hijacking the request before dispatching by returning a non-empty. The returned value
 943           * will be used to serve the request instead.
 944           *
 945           * @since 4.4.0
 946           *
 947           * @param mixed           $result  Response to replace the requested version with. Can be anything
 948           *                                 a normal endpoint can return, or null to not hijack the request.
 949           * @param WP_REST_Server  $server  Server instance.
 950           * @param WP_REST_Request $request Request used to generate the response.
 951           */
 952          $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
 953  
 954          if ( ! empty( $result ) ) {
 955              return $result;
 956          }
 957  
 958          $error   = null;
 959          $matched = $this->match_request_to_handler( $request );
 960  
 961          if ( is_wp_error( $matched ) ) {
 962              return $this->error_to_response( $matched );
 963          }
 964  
 965          list( $route, $handler ) = $matched;
 966  
 967          if ( ! is_callable( $handler['callback'] ) ) {
 968              $error = new WP_Error(
 969                  'rest_invalid_handler',
 970                  __( 'The handler for the route is invalid.' ),
 971                  array( 'status' => 500 )
 972              );
 973          }
 974  
 975          if ( ! is_wp_error( $error ) ) {
 976              $check_required = $request->has_valid_params();
 977              if ( is_wp_error( $check_required ) ) {
 978                  $error = $check_required;
 979              } else {
 980                  $check_sanitized = $request->sanitize_params();
 981                  if ( is_wp_error( $check_sanitized ) ) {
 982                      $error = $check_sanitized;
 983                  }
 984              }
 985          }
 986  
 987          return $this->respond_to_request( $request, $route, $handler, $error );
 988      }
 989  
 990      /**
 991       * Matches a request object to its handler.
 992       *
 993       * @access private
 994       * @since 5.6.0
 995       *
 996       * @param WP_REST_Request $request The request object.
 997       * @return array|WP_Error The route and request handler on success or a WP_Error instance if no handler was found.
 998       */
 999  	protected function match_request_to_handler( $request ) {
1000          $method = $request->get_method();
1001          $path   = $request->get_route();
1002  
1003          $with_namespace = array();
1004  
1005          foreach ( $this->get_namespaces() as $namespace ) {
1006              if ( 0 === strpos( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
1007                  $with_namespace[] = $this->get_routes( $namespace );
1008              }
1009          }
1010  
1011          if ( $with_namespace ) {
1012              $routes = array_merge( ...$with_namespace );
1013          } else {
1014              $routes = $this->get_routes();
1015          }
1016  
1017          foreach ( $routes as $route => $handlers ) {
1018              $match = preg_match( '@^' . $route . '$@i', $path, $matches );
1019  
1020              if ( ! $match ) {
1021                  continue;
1022              }
1023  
1024              $args = array();
1025  
1026              foreach ( $matches as $param => $value ) {
1027                  if ( ! is_int( $param ) ) {
1028                      $args[ $param ] = $value;
1029                  }
1030              }
1031  
1032              foreach ( $handlers as $handler ) {
1033                  $callback = $handler['callback'];
1034                  $response = null;
1035  
1036                  // Fallback to GET method if no HEAD method is registered.
1037                  $checked_method = $method;
1038                  if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
1039                      $checked_method = 'GET';
1040                  }
1041                  if ( empty( $handler['methods'][ $checked_method ] ) ) {
1042                      continue;
1043                  }
1044  
1045                  if ( ! is_callable( $callback ) ) {
1046                      return array( $route, $handler );
1047                  }
1048  
1049                  $request->set_url_params( $args );
1050                  $request->set_attributes( $handler );
1051  
1052                  $defaults = array();
1053  
1054                  foreach ( $handler['args'] as $arg => $options ) {
1055                      if ( isset( $options['default'] ) ) {
1056                          $defaults[ $arg ] = $options['default'];
1057                      }
1058                  }
1059  
1060                  $request->set_default_params( $defaults );
1061  
1062                  return array( $route, $handler );
1063              }
1064          }
1065  
1066          return new WP_Error(
1067              'rest_no_route',
1068              __( 'No route was found matching the URL and request method.' ),
1069              array( 'status' => 404 )
1070          );
1071      }
1072  
1073      /**
1074       * Dispatches the request to the callback handler.
1075       *
1076       * @access private
1077       * @since 5.6.0
1078       *
1079       * @param WP_REST_Request $request  The request object.
1080       * @param array           $handler  The matched route handler.
1081       * @param string          $route    The matched route regex.
1082       * @param WP_Error|null   $response The current error object if any.
1083       *
1084       * @return WP_REST_Response
1085       */
1086  	protected function respond_to_request( $request, $route, $handler, $response ) {
1087          /**
1088           * Filters the response before executing any REST API callbacks.
1089           *
1090           * Allows plugins to perform additional validation after a
1091           * request is initialized and matched to a registered route,
1092           * but before it is executed.
1093           *
1094           * Note that this filter will not be called for requests that
1095           * fail to authenticate or match to a registered route.
1096           *
1097           * @since 4.7.0
1098           *
1099           * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
1100           *                                                                   Usually a WP_REST_Response or WP_Error.
1101           * @param array                                            $handler  Route handler used for the request.
1102           * @param WP_REST_Request                                  $request  Request used to generate the response.
1103           */
1104          $response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );
1105  
1106          // Check permission specified on the route.
1107          if ( ! is_wp_error( $response ) && ! empty( $handler['permission_callback'] ) ) {
1108              $permission = call_user_func( $handler['permission_callback'], $request );
1109  
1110              if ( is_wp_error( $permission ) ) {
1111                  $response = $permission;
1112              } elseif ( false === $permission || null === $permission ) {
1113                  $response = new WP_Error(
1114                      'rest_forbidden',
1115                      __( 'Sorry, you are not allowed to do that.' ),
1116                      array( 'status' => rest_authorization_required_code() )
1117                  );
1118              }
1119          }
1120  
1121          if ( ! is_wp_error( $response ) ) {
1122              /**
1123               * Filters the REST API dispatch request result.
1124               *
1125               * Allow plugins to override dispatching the request.
1126               *
1127               * @since 4.4.0
1128               * @since 4.5.0 Added `$route` and `$handler` parameters.
1129               *
1130               * @param mixed           $dispatch_result Dispatch result, will be used if not empty.
1131               * @param WP_REST_Request $request         Request used to generate the response.
1132               * @param string          $route           Route matched for the request.
1133               * @param array           $handler         Route handler used for the request.
1134               */
1135              $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler );
1136  
1137              // Allow plugins to halt the request via this filter.
1138              if ( null !== $dispatch_result ) {
1139                  $response = $dispatch_result;
1140              } else {
1141                  $response = call_user_func( $handler['callback'], $request );
1142              }
1143          }
1144  
1145          /**
1146           * Filters the response immediately after executing any REST API
1147           * callbacks.
1148           *
1149           * Allows plugins to perform any needed cleanup, for example,
1150           * to undo changes made during the {@see 'rest_request_before_callbacks'}
1151           * filter.
1152           *
1153           * Note that this filter will not be called for requests that
1154           * fail to authenticate or match to a registered route.
1155           *
1156           * Note that an endpoint's `permission_callback` can still be
1157           * called after this filter - see `rest_send_allow_header()`.
1158           *
1159           * @since 4.7.0
1160           *
1161           * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
1162           *                                                                   Usually a WP_REST_Response or WP_Error.
1163           * @param array                                            $handler  Route handler used for the request.
1164           * @param WP_REST_Request                                  $request  Request used to generate the response.
1165           */
1166          $response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );
1167  
1168          if ( is_wp_error( $response ) ) {
1169              $response = $this->error_to_response( $response );
1170          } else {
1171              $response = rest_ensure_response( $response );
1172          }
1173  
1174          $response->set_matched_route( $route );
1175          $response->set_matched_handler( $handler );
1176  
1177          return $response;
1178      }
1179  
1180      /**
1181       * Returns if an error occurred during most recent JSON encode/decode.
1182       *
1183       * Strings to be translated will be in format like
1184       * "Encoding error: Maximum stack depth exceeded".
1185       *
1186       * @since 4.4.0
1187       *
1188       * @return false|string Boolean false or string error message.
1189       */
1190  	protected function get_json_last_error() {
1191          $last_error_code = json_last_error();
1192  
1193          if ( JSON_ERROR_NONE === $last_error_code || empty( $last_error_code ) ) {
1194              return false;
1195          }
1196  
1197          return json_last_error_msg();
1198      }
1199  
1200      /**
1201       * Retrieves the site index.
1202       *
1203       * This endpoint describes the capabilities of the site.
1204       *
1205       * @since 4.4.0
1206       *
1207       * @param array $request {
1208       *     Request.
1209       *
1210       *     @type string $context Context.
1211       * }
1212       * @return WP_REST_Response The API root index data.
1213       */
1214  	public function get_index( $request ) {
1215          // General site data.
1216          $available = array(
1217              'name'            => get_option( 'blogname' ),
1218              'description'     => get_option( 'blogdescription' ),
1219              'url'             => get_option( 'siteurl' ),
1220              'home'            => home_url(),
1221              'gmt_offset'      => get_option( 'gmt_offset' ),
1222              'timezone_string' => get_option( 'timezone_string' ),
1223              'namespaces'      => array_keys( $this->namespaces ),
1224              'authentication'  => array(),
1225              'routes'          => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
1226          );
1227  
1228          $response = new WP_REST_Response( $available );
1229          $response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' );
1230          $this->add_active_theme_link_to_index( $response );
1231  
1232          /**
1233           * Filters the REST API root index data.
1234           *
1235           * This contains the data describing the API. This includes information
1236           * about supported authentication schemes, supported namespaces, routes
1237           * available on the API, and a small amount of data about the site.
1238           *
1239           * @since 4.4.0
1240           *
1241           * @param WP_REST_Response $response Response data.
1242           */
1243          return apply_filters( 'rest_index', $response );
1244      }
1245  
1246      /**
1247       * Adds a link to the active theme for users who have proper permissions.
1248       *
1249       * @since 5.7.0
1250       *
1251       * @param WP_REST_Response $response REST API response.
1252       */
1253  	protected function add_active_theme_link_to_index( WP_REST_Response $response ) {
1254          $should_add = current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' );
1255  
1256          if ( ! $should_add && current_user_can( 'edit_posts' ) ) {
1257              $should_add = true;
1258          }
1259  
1260          if ( ! $should_add ) {
1261              foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
1262                  if ( current_user_can( $post_type->cap->edit_posts ) ) {
1263                      $should_add = true;
1264                      break;
1265                  }
1266              }
1267          }
1268  
1269          if ( $should_add ) {
1270              $theme = wp_get_theme();
1271              $response->add_link( 'https://api.w.org/active-theme', rest_url( 'wp/v2/themes/' . $theme->get_stylesheet() ) );
1272          }
1273      }
1274  
1275      /**
1276       * Retrieves the index for a namespace.
1277       *
1278       * @since 4.4.0
1279       *
1280       * @param WP_REST_Request $request REST request instance.
1281       * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,
1282       *                                   WP_Error if the namespace isn't set.
1283       */
1284  	public function get_namespace_index( $request ) {
1285          $namespace = $request['namespace'];
1286  
1287          if ( ! isset( $this->namespaces[ $namespace ] ) ) {
1288              return new WP_Error(
1289                  'rest_invalid_namespace',
1290                  __( 'The specified namespace could not be found.' ),
1291                  array( 'status' => 404 )
1292              );
1293          }
1294  
1295          $routes    = $this->namespaces[ $namespace ];
1296          $endpoints = array_intersect_key( $this->get_routes(), $routes );
1297  
1298          $data     = array(
1299              'namespace' => $namespace,
1300              'routes'    => $this->get_data_for_routes( $endpoints, $request['context'] ),
1301          );
1302          $response = rest_ensure_response( $data );
1303  
1304          // Link to the root index.
1305          $response->add_link( 'up', rest_url( '/' ) );
1306  
1307          /**
1308           * Filters the REST API namespace index data.
1309           *
1310           * This typically is just the route data for the namespace, but you can
1311           * add any data you'd like here.
1312           *
1313           * @since 4.4.0
1314           *
1315           * @param WP_REST_Response $response Response data.
1316           * @param WP_REST_Request  $request  Request data. The namespace is passed as the 'namespace' parameter.
1317           */
1318          return apply_filters( 'rest_namespace_index', $response, $request );
1319      }
1320  
1321      /**
1322       * Retrieves the publicly-visible data for routes.
1323       *
1324       * @since 4.4.0
1325       *
1326       * @param array  $routes  Routes to get data for.
1327       * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
1328       * @return array[] Route data to expose in indexes, keyed by route.
1329       */
1330  	public function get_data_for_routes( $routes, $context = 'view' ) {
1331          $available = array();
1332  
1333          // Find the available routes.
1334          foreach ( $routes as $route => $callbacks ) {
1335              $data = $this->get_data_for_route( $route, $callbacks, $context );
1336              if ( empty( $data ) ) {
1337                  continue;
1338              }
1339  
1340              /**
1341               * Filters the REST API endpoint data.
1342               *
1343               * @since 4.4.0
1344               *
1345               * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
1346               */
1347              $available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
1348          }
1349  
1350          /**
1351           * Filters the publicly-visible data for REST API routes.
1352           *
1353           * This data is exposed on indexes and can be used by clients or
1354           * developers to investigate the site and find out how to use it. It
1355           * acts as a form of self-documentation.
1356           *
1357           * @since 4.4.0
1358           *
1359           * @param array[] $available Route data to expose in indexes, keyed by route.
1360           * @param array   $routes    Internal route data as an associative array.
1361           */
1362          return apply_filters( 'rest_route_data', $available, $routes );
1363      }
1364  
1365      /**
1366       * Retrieves publicly-visible data for the route.
1367       *
1368       * @since 4.4.0
1369       *
1370       * @param string $route     Route to get data for.
1371       * @param array  $callbacks Callbacks to convert to data.
1372       * @param string $context   Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'.
1373       * @return array|null Data for the route, or null if no publicly-visible data.
1374       */
1375  	public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
1376          $data = array(
1377              'namespace' => '',
1378              'methods'   => array(),
1379              'endpoints' => array(),
1380          );
1381  
1382          if ( isset( $this->route_options[ $route ] ) ) {
1383              $options = $this->route_options[ $route ];
1384  
1385              if ( isset( $options['namespace'] ) ) {
1386                  $data['namespace'] = $options['namespace'];
1387              }
1388  
1389              if ( isset( $options['schema'] ) && 'help' === $context ) {
1390                  $data['schema'] = call_user_func( $options['schema'] );
1391              }
1392          }
1393  
1394          $allowed_schema_keywords = array_flip( rest_get_allowed_schema_keywords() );
1395  
1396          $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
1397  
1398          foreach ( $callbacks as $callback ) {
1399              // Skip to the next route if any callback is hidden.
1400              if ( empty( $callback['show_in_index'] ) ) {
1401                  continue;
1402              }
1403  
1404              $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
1405              $endpoint_data   = array(
1406                  'methods' => array_keys( $callback['methods'] ),
1407              );
1408  
1409              if ( isset( $callback['args'] ) ) {
1410                  $endpoint_data['args'] = array();
1411  
1412                  foreach ( $callback['args'] as $key => $opts ) {
1413                      $arg_data             = array_intersect_key( $opts, $allowed_schema_keywords );
1414                      $arg_data['required'] = ! empty( $opts['required'] );
1415  
1416                      $endpoint_data['args'][ $key ] = $arg_data;
1417                  }
1418              }
1419  
1420              $data['endpoints'][] = $endpoint_data;
1421  
1422              // For non-variable routes, generate links.
1423              if ( strpos( $route, '{' ) === false ) {
1424                  $data['_links'] = array(
1425                      'self' => array(
1426                          array(
1427                              'href' => rest_url( $route ),
1428                          ),
1429                      ),
1430                  );
1431              }
1432          }
1433  
1434          if ( empty( $data['methods'] ) ) {
1435              // No methods supported, hide the route.
1436              return null;
1437          }
1438  
1439          return $data;
1440      }
1441  
1442      /**
1443       * Gets the maximum number of requests that can be included in a batch.
1444       *
1445       * @since 5.6.0
1446       *
1447       * @return int The maximum requests.
1448       */
1449  	protected function get_max_batch_size() {
1450          /**
1451           * Filters the maximum number of REST API requests that can be included in a batch.
1452           *
1453           * @since 5.6.0
1454           *
1455           * @param int $max_size The maximum size.
1456           */
1457          return apply_filters( 'rest_get_max_batch_size', 25 );
1458      }
1459  
1460      /**
1461       * Serves the batch/v1 request.
1462       *
1463       * @since 5.6.0
1464       *
1465       * @param WP_REST_Request $batch_request The batch request object.
1466       * @return WP_REST_Response The generated response object.
1467       */
1468  	public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
1469          $requests = array();
1470  
1471          foreach ( $batch_request['requests'] as $args ) {
1472              $parsed_url = wp_parse_url( $args['path'] );
1473  
1474              if ( false === $parsed_url ) {
1475                  $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.' ), array( 'status' => 400 ) );
1476  
1477                  continue;
1478              }
1479  
1480              $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] );
1481  
1482              if ( ! empty( $parsed_url['query'] ) ) {
1483                  $query_args = null; // Satisfy linter.
1484                  wp_parse_str( $parsed_url['query'], $query_args );
1485                  $single_request->set_query_params( $query_args );
1486              }
1487  
1488              if ( ! empty( $args['body'] ) ) {
1489                  $single_request->set_body_params( $args['body'] );
1490              }
1491  
1492              if ( ! empty( $args['headers'] ) ) {
1493                  $single_request->set_headers( $args['headers'] );
1494              }
1495  
1496              $requests[] = $single_request;
1497          }
1498  
1499          $matches    = array();
1500          $validation = array();
1501          $has_error  = false;
1502  
1503          foreach ( $requests as $single_request ) {
1504              $match     = $this->match_request_to_handler( $single_request );
1505              $matches[] = $match;
1506              $error     = null;
1507  
1508              if ( is_wp_error( $match ) ) {
1509                  $error = $match;
1510              }
1511  
1512              if ( ! $error ) {
1513                  list( $route, $handler ) = $match;
1514  
1515                  if ( isset( $handler['allow_batch'] ) ) {
1516                      $allow_batch = $handler['allow_batch'];
1517                  } else {
1518                      $route_options = $this->get_route_options( $route );
1519                      $allow_batch   = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false;
1520                  }
1521  
1522                  if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) {
1523                      $error = new WP_Error(
1524                          'rest_batch_not_allowed',
1525                          __( 'The requested route does not support batch requests.' ),
1526                          array( 'status' => 400 )
1527                      );
1528                  }
1529              }
1530  
1531              if ( ! $error ) {
1532                  $check_required = $single_request->has_valid_params();
1533                  if ( is_wp_error( $check_required ) ) {
1534                      $error = $check_required;
1535                  }
1536              }
1537  
1538              if ( ! $error ) {
1539                  $check_sanitized = $single_request->sanitize_params();
1540                  if ( is_wp_error( $check_sanitized ) ) {
1541                      $error = $check_sanitized;
1542                  }
1543              }
1544  
1545              if ( $error ) {
1546                  $has_error    = true;
1547                  $validation[] = $error;
1548              } else {
1549                  $validation[] = true;
1550              }
1551          }
1552  
1553          $responses = array();
1554  
1555          if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) {
1556              foreach ( $validation as $valid ) {
1557                  if ( is_wp_error( $valid ) ) {
1558                      $responses[] = $this->envelope_response( $this->error_to_response( $valid ), false )->get_data();
1559                  } else {
1560                      $responses[] = null;
1561                  }
1562              }
1563  
1564              return new WP_REST_Response(
1565                  array(
1566                      'failed'    => 'validation',
1567                      'responses' => $responses,
1568                  ),
1569                  WP_Http::MULTI_STATUS
1570              );
1571          }
1572  
1573          foreach ( $requests as $i => $single_request ) {
1574              $clean_request = clone $single_request;
1575              $clean_request->set_url_params( array() );
1576              $clean_request->set_attributes( array() );
1577              $clean_request->set_default_params( array() );
1578  
1579              /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
1580              $result = apply_filters( 'rest_pre_dispatch', null, $this, $clean_request );
1581  
1582              if ( empty( $result ) ) {
1583                  $match = $matches[ $i ];
1584                  $error = null;
1585  
1586                  if ( is_wp_error( $validation[ $i ] ) ) {
1587                      $error = $validation[ $i ];
1588                  }
1589  
1590                  if ( is_wp_error( $match ) ) {
1591                      $result = $this->error_to_response( $match );
1592                  } else {
1593                      list( $route, $handler ) = $match;
1594  
1595                      if ( ! $error && ! is_callable( $handler['callback'] ) ) {
1596                          $error = new WP_Error(
1597                              'rest_invalid_handler',
1598                              __( 'The handler for the route is invalid' ),
1599                              array( 'status' => 500 )
1600                          );
1601                      }
1602  
1603                      $result = $this->respond_to_request( $single_request, $route, $handler, $error );
1604                  }
1605              }
1606  
1607              /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
1608              $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $single_request );
1609  
1610              $responses[] = $this->envelope_response( $result, false )->get_data();
1611          }
1612  
1613          return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
1614      }
1615  
1616      /**
1617       * Sends an HTTP status code.
1618       *
1619       * @since 4.4.0
1620       *
1621       * @param int $code HTTP status.
1622       */
1623  	protected function set_status( $code ) {
1624          status_header( $code );
1625      }
1626  
1627      /**
1628       * Sends an HTTP header.
1629       *
1630       * @since 4.4.0
1631       *
1632       * @param string $key Header key.
1633       * @param string $value Header value.
1634       */
1635  	public function send_header( $key, $value ) {
1636          /*
1637           * Sanitize as per RFC2616 (Section 4.2):
1638           *
1639           * Any LWS that occurs between field-content MAY be replaced with a
1640           * single SP before interpreting the field value or forwarding the
1641           * message downstream.
1642           */
1643          $value = preg_replace( '/\s+/', ' ', $value );
1644          header( sprintf( '%s: %s', $key, $value ) );
1645      }
1646  
1647      /**
1648       * Sends multiple HTTP headers.
1649       *
1650       * @since 4.4.0
1651       *
1652       * @param array $headers Map of header name to header value.
1653       */
1654  	public function send_headers( $headers ) {
1655          foreach ( $headers as $key => $value ) {
1656              $this->send_header( $key, $value );
1657          }
1658      }
1659  
1660      /**
1661       * Removes an HTTP header from the current response.
1662       *
1663       * @since 4.8.0
1664       *
1665       * @param string $key Header key.
1666       */
1667  	public function remove_header( $key ) {
1668          header_remove( $key );
1669      }
1670  
1671      /**
1672       * Retrieves the raw request entity (body).
1673       *
1674       * @since 4.4.0
1675       *
1676       * @global string $HTTP_RAW_POST_DATA Raw post data.
1677       *
1678       * @return string Raw request data.
1679       */
1680  	public static function get_raw_data() {
1681          // phpcs:disable PHPCompatibility.Variables.RemovedPredefinedGlobalVariables.http_raw_post_dataDeprecatedRemoved
1682          global $HTTP_RAW_POST_DATA;
1683  
1684          // $HTTP_RAW_POST_DATA was deprecated in PHP 5.6 and removed in PHP 7.0.
1685          if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
1686              $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
1687          }
1688  
1689          return $HTTP_RAW_POST_DATA;
1690          // phpcs:enable
1691      }
1692  
1693      /**
1694       * Extracts headers from a PHP-style $_SERVER array.
1695       *
1696       * @since 4.4.0
1697       *
1698       * @param array $server Associative array similar to `$_SERVER`.
1699       * @return array Headers extracted from the input.
1700       */
1701  	public function get_headers( $server ) {
1702          $headers = array();
1703  
1704          // CONTENT_* headers are not prefixed with HTTP_.
1705          $additional = array(
1706              'CONTENT_LENGTH' => true,
1707              'CONTENT_MD5'    => true,
1708              'CONTENT_TYPE'   => true,
1709          );
1710  
1711          foreach ( $server as $key => $value ) {
1712              if ( strpos( $key, 'HTTP_' ) === 0 ) {
1713                  $headers[ substr( $key, 5 ) ] = $value;
1714              } elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) {
1715                  /*
1716                   * In some server configurations, the authorization header is passed in this alternate location.
1717                   * Since it would not be passed in in both places we do not check for both headers and resolve.
1718                   */
1719                  $headers['AUTHORIZATION'] = $value;
1720              } elseif ( isset( $additional[ $key ] ) ) {
1721                  $headers[ $key ] = $value;
1722              }
1723          }
1724  
1725          return $headers;
1726      }
1727  }


Generated : Fri May 14 08:20:01 2021 Cross-referenced by PHPXref