[ Index ]

PHP Cross Reference of WordPress

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


Generated: Wed Aug 12 01:00:03 2020 Cross-referenced by PHPXref 0.7.1