wp/wp-includes/rest-api/class-wp-rest-server.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    75 	 *
    75 	 *
    76 	 * @since 4.4.0
    76 	 * @since 4.4.0
    77 	 * @var array
    77 	 * @var array
    78 	 */
    78 	 */
    79 	protected $route_options = array();
    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();
    80 
    88 
    81 	/**
    89 	/**
    82 	 * Instantiates the REST server.
    90 	 * Instantiates the REST server.
    83 	 *
    91 	 *
    84 	 * @since 4.4.0
    92 	 * @since 4.4.0
   127 		 * data should be used). A callback can return `true` to indicate that
   135 		 * data should be used). A callback can return `true` to indicate that
   128 		 * the authentication method was used, and it succeeded.
   136 		 * the authentication method was used, and it succeeded.
   129 		 *
   137 		 *
   130 		 * @since 4.4.0
   138 		 * @since 4.4.0
   131 		 *
   139 		 *
   132 		 * @param WP_Error|null|bool WP_Error if authentication error, null if authentication
   140 		 * @param WP_Error|null|true $errors WP_Error if authentication error, null if authentication
   133 		 *                              method wasn't used, true if authentication succeeded.
   141 		 *                                   method wasn't used, true if authentication succeeded.
   134 		 */
   142 		 */
   135 		return apply_filters( 'rest_authentication_errors', null );
   143 		return apply_filters( 'rest_authentication_errors', null );
   136 	}
   144 	}
   137 
   145 
   138 	/**
   146 	/**
   215 	 *
   223 	 *
   216 	 * @see WP_REST_Server::dispatch()
   224 	 * @see WP_REST_Server::dispatch()
   217 	 *
   225 	 *
   218 	 * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
   226 	 * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
   219 	 *                     Default null.
   227 	 *                     Default null.
   220 	 * @return false|null Null if not served and a HEAD request, false otherwise.
   228 	 * @return null|false Null if not served and a HEAD request, false otherwise.
   221 	 */
   229 	 */
   222 	public function serve_request( $path = null ) {
   230 	public function serve_request( $path = null ) {
   223 		$content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
   231 		$content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json';
   224 		$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
   232 		$this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
   225 		$this->send_header( 'X-Robots-Tag', 'noindex' );
   233 		$this->send_header( 'X-Robots-Tag', 'noindex' );
   233 		 * Mitigate possible JSONP Flash attacks.
   241 		 * Mitigate possible JSONP Flash attacks.
   234 		 *
   242 		 *
   235 		 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   243 		 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   236 		 */
   244 		 */
   237 		$this->send_header( 'X-Content-Type-Options', 'nosniff' );
   245 		$this->send_header( 'X-Content-Type-Options', 'nosniff' );
   238 		$this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' );
   246 		$expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );
   239 		$this->send_header( 'Access-Control-Allow-Headers', 'Authorization, Content-Type' );
   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 ) );
   240 
   282 
   241 		/**
   283 		/**
   242 		 * Send nocache headers on authenticated requests.
   284 		 * Send nocache headers on authenticated requests.
   243 		 *
   285 		 *
   244 		 * @since 4.4.0
   286 		 * @since 4.4.0
   258 
   300 
   259 		/**
   301 		/**
   260 		 * Filters whether the REST API is enabled.
   302 		 * Filters whether the REST API is enabled.
   261 		 *
   303 		 *
   262 		 * @since 4.4.0
   304 		 * @since 4.4.0
   263 		 * @deprecated 4.7.0 Use the rest_authentication_errors filter to restrict access to the API
   305 		 * @deprecated 4.7.0 Use the {@see 'rest_authentication_errors'} filter to
       
   306 		 *                   restrict access to the API.
   264 		 *
   307 		 *
   265 		 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
   308 		 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
   266 		 */
   309 		 */
   267 		apply_filters_deprecated(
   310 		apply_filters_deprecated(
   268 			'rest_enabled',
   311 			'rest_enabled',
   269 			array( true ),
   312 			array( true ),
   270 			'4.7.0',
   313 			'4.7.0',
   271 			'rest_authentication_errors',
   314 			'rest_authentication_errors',
   272 			__( 'The REST API can no longer be completely disabled, the rest_authentication_errors filter can be used to restrict access to the API, instead.' )
   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 			)
   273 		);
   320 		);
   274 
   321 
   275 		/**
   322 		/**
   276 		 * Filters whether jsonp is enabled.
   323 		 * Filters whether jsonp is enabled.
   277 		 *
   324 		 *
   308 
   355 
   309 		$request->set_query_params( wp_unslash( $_GET ) );
   356 		$request->set_query_params( wp_unslash( $_GET ) );
   310 		$request->set_body_params( wp_unslash( $_POST ) );
   357 		$request->set_body_params( wp_unslash( $_POST ) );
   311 		$request->set_file_params( $_FILES );
   358 		$request->set_file_params( $_FILES );
   312 		$request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
   359 		$request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
   313 		$request->set_body( $this->get_raw_data() );
   360 		$request->set_body( self::get_raw_data() );
   314 
   361 
   315 		/*
   362 		/*
   316 		 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
   363 		 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
   317 		 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
   364 		 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
   318 		 * header.
   365 		 * header.
   383 			if ( 'HEAD' === $request->get_method() ) {
   430 			if ( 'HEAD' === $request->get_method() ) {
   384 				return null;
   431 				return null;
   385 			}
   432 			}
   386 
   433 
   387 			// Embed links inside the request.
   434 			// Embed links inside the request.
   388 			$result = $this->response_to_data( $result, isset( $_GET['_embed'] ) );
   435 			$embed  = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
       
   436 			$result = $this->response_to_data( $result, $embed );
   389 
   437 
   390 			/**
   438 			/**
   391 			 * Filters the API response.
   439 			 * Filters the API response.
   392 			 *
   440 			 *
   393 			 * Allows modification of the response data after inserting
   441 			 * Allows modification of the response data after inserting
   399 			 * @param WP_REST_Server   $this    Server instance.
   447 			 * @param WP_REST_Server   $this    Server instance.
   400 			 * @param WP_REST_Request  $request Request used to generate the response.
   448 			 * @param WP_REST_Request  $request Request used to generate the response.
   401 			 */
   449 			 */
   402 			$result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );
   450 			$result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );
   403 
   451 
       
   452 			// The 204 response shouldn't have a body.
       
   453 			if ( 204 === $code || null === $result ) {
       
   454 				return null;
       
   455 			}
       
   456 
   404 			$result = wp_json_encode( $result );
   457 			$result = wp_json_encode( $result );
   405 
   458 
   406 			$json_error_message = $this->get_json_last_error();
   459 			$json_error_message = $this->get_json_last_error();
       
   460 
   407 			if ( $json_error_message ) {
   461 			if ( $json_error_message ) {
   408 				$json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) );
   462 				$json_error_obj = new WP_Error(
   409 				$result         = $this->error_to_response( $json_error_obj );
   463 					'rest_encode_error',
   410 				$result         = wp_json_encode( $result->data[0] );
   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] );
   411 			}
   470 			}
   412 
   471 
   413 			if ( $jsonp_callback ) {
   472 			if ( $jsonp_callback ) {
   414 				// Prepend '/**/' to mitigate possible JSONP Flash attacks.
   473 				// Prepend '/**/' to mitigate possible JSONP Flash attacks.
   415 				// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   474 				// https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
   416 				echo '/**/' . $jsonp_callback . '(' . $result . ')';
   475 				echo '/**/' . $jsonp_callback . '(' . $result . ')';
   417 			} else {
   476 			} else {
   418 				echo $result;
   477 				echo $result;
   419 			}
   478 			}
   420 		}
   479 		}
       
   480 
   421 		return null;
   481 		return null;
   422 	}
   482 	}
   423 
   483 
   424 	/**
   484 	/**
   425 	 * Converts a response to data to send.
   485 	 * Converts a response to data to send.
   426 	 *
   486 	 *
   427 	 * @since 4.4.0
   487 	 * @since 4.4.0
       
   488 	 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
   428 	 *
   489 	 *
   429 	 * @param WP_REST_Response $response Response object.
   490 	 * @param WP_REST_Response $response Response object.
   430 	 * @param bool             $embed    Whether links should be embedded.
   491 	 * @param bool|string[]    $embed    Whether to embed all links, a filtered list of link relations, or no links.
   431 	 * @return array {
   492 	 * @return array {
   432 	 *     Data with sub-requests embedded.
   493 	 *     Data with sub-requests embedded.
   433 	 *
   494 	 *
   434 	 *     @type array [$_links]    Links.
   495 	 *     @type array $_links    Links.
   435 	 *     @type array [$_embedded] Embeddeds.
   496 	 *     @type array $_embedded Embeddeds.
   436 	 * }
   497 	 * }
   437 	 */
   498 	 */
   438 	public function response_to_data( $response, $embed ) {
   499 	public function response_to_data( $response, $embed ) {
   439 		$data  = $response->get_data();
   500 		$data  = $response->get_data();
   440 		$links = $this->get_compact_response_links( $response );
   501 		$links = self::get_compact_response_links( $response );
   441 
   502 
   442 		if ( ! empty( $links ) ) {
   503 		if ( ! empty( $links ) ) {
   443 			// Convert links to part of the data.
   504 			// Convert links to part of the data.
   444 			$data['_links'] = $links;
   505 			$data['_links'] = $links;
   445 		}
   506 		}
       
   507 
   446 		if ( $embed ) {
   508 		if ( $embed ) {
       
   509 			$this->embed_cache = array();
   447 			// Determine if this is a numeric array.
   510 			// Determine if this is a numeric array.
   448 			if ( wp_is_numeric_array( $data ) ) {
   511 			if ( wp_is_numeric_array( $data ) ) {
   449 				$data = array_map( array( $this, 'embed_links' ), $data );
   512 				foreach ( $data as $key => $item ) {
       
   513 					$data[ $key ] = $this->embed_links( $item, $embed );
       
   514 				}
   450 			} else {
   515 			} else {
   451 				$data = $this->embed_links( $data );
   516 				$data = $this->embed_links( $data, $embed );
   452 			}
   517 			}
       
   518 			$this->embed_cache = array();
   453 		}
   519 		}
   454 
   520 
   455 		return $data;
   521 		return $data;
   456 	}
   522 	}
   457 
   523 
   466 	 * @param WP_REST_Response $response Response to extract links from.
   532 	 * @param WP_REST_Response $response Response to extract links from.
   467 	 * @return array Map of link relation to list of link hashes.
   533 	 * @return array Map of link relation to list of link hashes.
   468 	 */
   534 	 */
   469 	public static function get_response_links( $response ) {
   535 	public static function get_response_links( $response ) {
   470 		$links = $response->get_links();
   536 		$links = $response->get_links();
       
   537 
   471 		if ( empty( $links ) ) {
   538 		if ( empty( $links ) ) {
   472 			return array();
   539 			return array();
   473 		}
   540 		}
   474 
   541 
   475 		// Convert links to part of the data.
   542 		// Convert links to part of the data.
   540 
   607 
   541 	/**
   608 	/**
   542 	 * Embeds the links from the data into the request.
   609 	 * Embeds the links from the data into the request.
   543 	 *
   610 	 *
   544 	 * @since 4.4.0
   611 	 * @since 4.4.0
   545 	 *
   612 	 * @since 5.4.0 The $embed parameter can now contain a list of link relations to include.
   546 	 * @param array $data Data from the request.
   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.
   547 	 * @return array {
   616 	 * @return array {
   548 	 *     Data with sub-requests embedded.
   617 	 *     Data with sub-requests embedded.
   549 	 *
   618 	 *
   550 	 *     @type array [$_links]    Links.
   619 	 *     @type array $_links    Links.
   551 	 *     @type array [$_embedded] Embeddeds.
   620 	 *     @type array $_embedded Embeddeds.
   552 	 * }
   621 	 * }
   553 	 */
   622 	 */
   554 	protected function embed_links( $data ) {
   623 	protected function embed_links( $data, $embed = true ) {
   555 		if ( empty( $data['_links'] ) ) {
   624 		if ( empty( $data['_links'] ) ) {
   556 			return $data;
   625 			return $data;
   557 		}
   626 		}
   558 
   627 
   559 		$embedded = array();
   628 		$embedded = array();
   560 
   629 
   561 		foreach ( $data['_links'] as $rel => $links ) {
   630 		foreach ( $data['_links'] as $rel => $links ) {
   562 			// Ignore links to self, for obvious reasons.
   631 			// If a list of relations was specified, and the link relation
   563 			if ( 'self' === $rel ) {
   632 			// is not in the list of allowed relations, don't process the link.
       
   633 			if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
   564 				continue;
   634 				continue;
   565 			}
   635 			}
   566 
   636 
   567 			$embeds = array();
   637 			$embeds = array();
   568 
   638 
   572 					// Ensure we keep the same order.
   642 					// Ensure we keep the same order.
   573 					$embeds[] = array();
   643 					$embeds[] = array();
   574 					continue;
   644 					continue;
   575 				}
   645 				}
   576 
   646 
   577 				// Run through our internal routing and serve.
   647 				if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) {
   578 				$request = WP_REST_Request::from_url( $item['href'] );
   648 					// Run through our internal routing and serve.
   579 				if ( ! $request ) {
   649 					$request = WP_REST_Request::from_url( $item['href'] );
   580 					$embeds[] = array();
   650 					if ( ! $request ) {
   581 					continue;
   651 						$embeds[] = array();
   582 				}
   652 						continue;
   583 
   653 					}
   584 				// Embedded resources get passed context=embed.
   654 
   585 				if ( empty( $request['context'] ) ) {
   655 					// Embedded resources get passed context=embed.
   586 					$request['context'] = 'embed';
   656 					if ( empty( $request['context'] ) ) {
   587 				}
   657 						$request['context'] = 'embed';
   588 
   658 					}
   589 				$response = $this->dispatch( $request );
   659 
   590 
   660 					$response = $this->dispatch( $request );
   591 				/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
   661 
   592 				$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
   662 					/** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
   593 
   663 					$response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
   594 				$embeds[] = $this->response_to_data( $response, false );
   664 
       
   665 					$this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false );
       
   666 				}
       
   667 
       
   668 				$embeds[] = $this->embed_cache[ $item['href'] ];
   595 			}
   669 			}
   596 
   670 
   597 			// Determine if any real links were found.
   671 			// Determine if any real links were found.
   598 			$has_links = count( array_filter( $embeds ) );
   672 			$has_links = count( array_filter( $embeds ) );
   599 
   673 
   703 	 *
   777 	 *
   704 	 * Note that the path regexes (array keys) must have @ escaped, as this is
   778 	 * Note that the path regexes (array keys) must have @ escaped, as this is
   705 	 * used as the delimiter with preg_match()
   779 	 * used as the delimiter with preg_match()
   706 	 *
   780 	 *
   707 	 * @since 4.4.0
   781 	 * @since 4.4.0
   708 	 *
   782 	 * @since 5.4.0 Add $namespace parameter.
       
   783 	 *
       
   784 	 * @param string $namespace Optionally, only return routes in the given namespace.
   709 	 * @return array `'/path/regex' => array( $callback, $bitmask )` or
   785 	 * @return array `'/path/regex' => array( $callback, $bitmask )` or
   710 	 *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
   786 	 *               `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
   711 	 */
   787 	 */
   712 	public function get_routes() {
   788 	public function get_routes( $namespace = '' ) {
       
   789 		$endpoints = $this->endpoints;
       
   790 
       
   791 		if ( $namespace ) {
       
   792 			$endpoints = wp_list_filter( $endpoints, array( 'namespace' => $namespace ) );
       
   793 		}
   713 
   794 
   714 		/**
   795 		/**
   715 		 * Filters the array of available endpoints.
   796 		 * Filters the array of available endpoints.
   716 		 *
   797 		 *
   717 		 * @since 4.4.0
   798 		 * @since 4.4.0
   719 		 * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
   800 		 * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
   720 		 *                         to an array of callbacks for the endpoint. These take the format
   801 		 *                         to an array of callbacks for the endpoint. These take the format
   721 		 *                         `'/path/regex' => array( $callback, $bitmask )` or
   802 		 *                         `'/path/regex' => array( $callback, $bitmask )` or
   722 		 *                         `'/path/regex' => array( array( $callback, $bitmask ).
   803 		 *                         `'/path/regex' => array( array( $callback, $bitmask ).
   723 		 */
   804 		 */
   724 		$endpoints = apply_filters( 'rest_endpoints', $this->endpoints );
   805 		$endpoints = apply_filters( 'rest_endpoints', $endpoints );
   725 
   806 
   726 		// Normalise the endpoints.
   807 		// Normalise the endpoints.
   727 		$defaults = array(
   808 		$defaults = array(
   728 			'methods'       => '',
   809 			'methods'       => '',
   729 			'accept_json'   => false,
   810 			'accept_json'   => false,
   778 	/**
   859 	/**
   779 	 * Retrieves namespaces registered on the server.
   860 	 * Retrieves namespaces registered on the server.
   780 	 *
   861 	 *
   781 	 * @since 4.4.0
   862 	 * @since 4.4.0
   782 	 *
   863 	 *
   783 	 * @return array List of registered namespaces.
   864 	 * @return string[] List of registered namespaces.
   784 	 */
   865 	 */
   785 	public function get_namespaces() {
   866 	public function get_namespaces() {
   786 		return array_keys( $this->namespaces );
   867 		return array_keys( $this->namespaces );
   787 	}
   868 	}
   788 
   869 
   831 		}
   912 		}
   832 
   913 
   833 		$method = $request->get_method();
   914 		$method = $request->get_method();
   834 		$path   = $request->get_route();
   915 		$path   = $request->get_route();
   835 
   916 
   836 		foreach ( $this->get_routes() as $route => $handlers ) {
   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 ) {
   837 			$match = preg_match( '@^' . $route . '$@i', $path, $matches );
   932 			$match = preg_match( '@^' . $route . '$@i', $path, $matches );
   838 
   933 
   839 			if ( ! $match ) {
   934 			if ( ! $match ) {
   840 				continue;
   935 				continue;
   841 			}
   936 			}
   842 
   937 
   843 			$args = array();
   938 			$args = array();
       
   939 
   844 			foreach ( $matches as $param => $value ) {
   940 			foreach ( $matches as $param => $value ) {
   845 				if ( ! is_int( $param ) ) {
   941 				if ( ! is_int( $param ) ) {
   846 					$args[ $param ] = $value;
   942 					$args[ $param ] = $value;
   847 				}
   943 				}
   848 			}
   944 			}
   859 				if ( empty( $handler['methods'][ $checked_method ] ) ) {
   955 				if ( empty( $handler['methods'][ $checked_method ] ) ) {
   860 					continue;
   956 					continue;
   861 				}
   957 				}
   862 
   958 
   863 				if ( ! is_callable( $callback ) ) {
   959 				if ( ! is_callable( $callback ) ) {
   864 					$response = new WP_Error( 'rest_invalid_handler', __( 'The handler for the route is invalid' ), array( 'status' => 500 ) );
   960 					$response = new WP_Error(
       
   961 						'rest_invalid_handler',
       
   962 						__( 'The handler for the route is invalid' ),
       
   963 						array( 'status' => 500 )
       
   964 					);
   865 				}
   965 				}
   866 
   966 
   867 				if ( ! is_wp_error( $response ) ) {
   967 				if ( ! is_wp_error( $response ) ) {
   868 					// Remove the redundant preg_match argument.
   968 					// Remove the redundant preg_match argument.
   869 					unset( $args[0] );
   969 					unset( $args[0] );
   902 				 * Note that this filter will not be called for requests that
  1002 				 * Note that this filter will not be called for requests that
   903 				 * fail to authenticate or match to a registered route.
  1003 				 * fail to authenticate or match to a registered route.
   904 				 *
  1004 				 *
   905 				 * @since 4.7.0
  1005 				 * @since 4.7.0
   906 				 *
  1006 				 *
   907 				 * @param WP_HTTP_Response|WP_Error $response Result to send to the client. Usually a WP_REST_Response or WP_Error.
  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.
   908 				 * @param array                     $handler  Route handler used for the request.
  1008 				 * @param array                                            $handler  Route handler used for the request.
   909 				 * @param WP_REST_Request           $request  Request used to generate the response.
  1009 				 * @param WP_REST_Request                                  $request  Request used to generate the response.
   910 				 */
  1010 				 */
   911 				$response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );
  1011 				$response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );
   912 
  1012 
   913 				if ( ! is_wp_error( $response ) ) {
  1013 				if ( ! is_wp_error( $response ) ) {
   914 					// Check permission specified on the route.
  1014 					// Check permission specified on the route.
   916 						$permission = call_user_func( $handler['permission_callback'], $request );
  1016 						$permission = call_user_func( $handler['permission_callback'], $request );
   917 
  1017 
   918 						if ( is_wp_error( $permission ) ) {
  1018 						if ( is_wp_error( $permission ) ) {
   919 							$response = $permission;
  1019 							$response = $permission;
   920 						} elseif ( false === $permission || null === $permission ) {
  1020 						} elseif ( false === $permission || null === $permission ) {
   921 							$response = new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => rest_authorization_required_code() ) );
  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 							);
   922 						}
  1026 						}
   923 					}
  1027 					}
   924 				}
  1028 				}
   925 
  1029 
   926 				if ( ! is_wp_error( $response ) ) {
  1030 				if ( ! is_wp_error( $response ) ) {
   961 				 * Note that an endpoint's `permission_callback` can still be
  1065 				 * Note that an endpoint's `permission_callback` can still be
   962 				 * called after this filter - see `rest_send_allow_header()`.
  1066 				 * called after this filter - see `rest_send_allow_header()`.
   963 				 *
  1067 				 *
   964 				 * @since 4.7.0
  1068 				 * @since 4.7.0
   965 				 *
  1069 				 *
   966 				 * @param WP_HTTP_Response|WP_Error $response Result to send to the client. Usually a WP_REST_Response or WP_Error.
  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.
   967 				 * @param array                     $handler  Route handler used for the request.
  1071 				 * @param array                                            $handler  Route handler used for the request.
   968 				 * @param WP_REST_Request           $request  Request used to generate the response.
  1072 				 * @param WP_REST_Request                                  $request  Request used to generate the response.
   969 				 */
  1073 				 */
   970 				$response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );
  1074 				$response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );
   971 
  1075 
   972 				if ( is_wp_error( $response ) ) {
  1076 				if ( is_wp_error( $response ) ) {
   973 					$response = $this->error_to_response( $response );
  1077 					$response = $this->error_to_response( $response );
   980 
  1084 
   981 				return $response;
  1085 				return $response;
   982 			}
  1086 			}
   983 		}
  1087 		}
   984 
  1088 
   985 		return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) );
  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 		);
   986 	}
  1096 	}
   987 
  1097 
   988 	/**
  1098 	/**
   989 	 * Returns if an error occurred during most recent JSON encode/decode.
  1099 	 * Returns if an error occurred during most recent JSON encode/decode.
   990 	 *
  1100 	 *
   994 	 * @since 4.4.0
  1104 	 * @since 4.4.0
   995 	 *
  1105 	 *
   996 	 * @return bool|string Boolean false or string error message.
  1106 	 * @return bool|string Boolean false or string error message.
   997 	 */
  1107 	 */
   998 	protected function get_json_last_error() {
  1108 	protected function get_json_last_error() {
   999 		// See https://core.trac.wordpress.org/ticket/27799.
       
  1000 		if ( ! function_exists( 'json_last_error' ) ) {
       
  1001 			return false;
       
  1002 		}
       
  1003 
       
  1004 		$last_error_code = json_last_error();
  1109 		$last_error_code = json_last_error();
  1005 
  1110 
  1006 		if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) {
  1111 		if ( JSON_ERROR_NONE === $last_error_code || empty( $last_error_code ) ) {
  1007 			return false;
  1112 			return false;
  1008 		}
  1113 		}
  1009 
  1114 
  1010 		return json_last_error_msg();
  1115 		return json_last_error_msg();
  1011 	}
  1116 	}
  1020 	 * @param array $request {
  1125 	 * @param array $request {
  1021 	 *     Request.
  1126 	 *     Request.
  1022 	 *
  1127 	 *
  1023 	 *     @type string $context Context.
  1128 	 *     @type string $context Context.
  1024 	 * }
  1129 	 * }
  1025 	 * @return array Index entity
  1130 	 * @return WP_REST_Response The API root index data.
  1026 	 */
  1131 	 */
  1027 	public function get_index( $request ) {
  1132 	public function get_index( $request ) {
  1028 		// General site data.
  1133 		// General site data.
  1029 		$available = array(
  1134 		$available = array(
  1030 			'name'            => get_option( 'blogname' ),
  1135 			'name'            => get_option( 'blogname' ),
  1067 	 */
  1172 	 */
  1068 	public function get_namespace_index( $request ) {
  1173 	public function get_namespace_index( $request ) {
  1069 		$namespace = $request['namespace'];
  1174 		$namespace = $request['namespace'];
  1070 
  1175 
  1071 		if ( ! isset( $this->namespaces[ $namespace ] ) ) {
  1176 		if ( ! isset( $this->namespaces[ $namespace ] ) ) {
  1072 			return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) );
  1177 			return new WP_Error(
       
  1178 				'rest_invalid_namespace',
       
  1179 				__( 'The specified namespace could not be found.' ),
       
  1180 				array( 'status' => 404 )
       
  1181 			);
  1073 		}
  1182 		}
  1074 
  1183 
  1075 		$routes    = $this->namespaces[ $namespace ];
  1184 		$routes    = $this->namespaces[ $namespace ];
  1076 		$endpoints = array_intersect_key( $this->get_routes(), $routes );
  1185 		$endpoints = array_intersect_key( $this->get_routes(), $routes );
  1077 
  1186 
  1103 	 *
  1212 	 *
  1104 	 * @since 4.4.0
  1213 	 * @since 4.4.0
  1105 	 *
  1214 	 *
  1106 	 * @param array  $routes  Routes to get data for.
  1215 	 * @param array  $routes  Routes to get data for.
  1107 	 * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
  1216 	 * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
  1108 	 * @return array Route data to expose in indexes.
  1217 	 * @return array[] Route data to expose in indexes, keyed by route.
  1109 	 */
  1218 	 */
  1110 	public function get_data_for_routes( $routes, $context = 'view' ) {
  1219 	public function get_data_for_routes( $routes, $context = 'view' ) {
  1111 		$available = array();
  1220 		$available = array();
  1112 
  1221 
  1113 		// Find the available routes.
  1222 		// Find the available routes.
  1134 		 * developers to investigate the site and find out how to use it. It
  1243 		 * developers to investigate the site and find out how to use it. It
  1135 		 * acts as a form of self-documentation.
  1244 		 * acts as a form of self-documentation.
  1136 		 *
  1245 		 *
  1137 		 * @since 4.4.0
  1246 		 * @since 4.4.0
  1138 		 *
  1247 		 *
  1139 		 * @param array $available Map of route to route data.
  1248 		 * @param array[] $available Route data to expose in indexes, keyed by route.
  1140 		 * @param array $routes    Internal route data as an associative array.
  1249 		 * @param array   $routes    Internal route data as an associative array.
  1141 		 */
  1250 		 */
  1142 		return apply_filters( 'rest_route_data', $available, $routes );
  1251 		return apply_filters( 'rest_route_data', $available, $routes );
  1143 	}
  1252 	}
  1144 
  1253 
  1145 	/**
  1254 	/**
  1184 				'methods' => array_keys( $callback['methods'] ),
  1293 				'methods' => array_keys( $callback['methods'] ),
  1185 			);
  1294 			);
  1186 
  1295 
  1187 			if ( isset( $callback['args'] ) ) {
  1296 			if ( isset( $callback['args'] ) ) {
  1188 				$endpoint_data['args'] = array();
  1297 				$endpoint_data['args'] = array();
       
  1298 
  1189 				foreach ( $callback['args'] as $key => $opts ) {
  1299 				foreach ( $callback['args'] as $key => $opts ) {
  1190 					$arg_data = array(
  1300 					$arg_data = array(
  1191 						'required' => ! empty( $opts['required'] ),
  1301 						'required' => ! empty( $opts['required'] ),
  1192 					);
  1302 					);
  1193 					if ( isset( $opts['default'] ) ) {
  1303 					if ( isset( $opts['default'] ) ) {
  1212 			$data['endpoints'][] = $endpoint_data;
  1322 			$data['endpoints'][] = $endpoint_data;
  1213 
  1323 
  1214 			// For non-variable routes, generate links.
  1324 			// For non-variable routes, generate links.
  1215 			if ( strpos( $route, '{' ) === false ) {
  1325 			if ( strpos( $route, '{' ) === false ) {
  1216 				$data['_links'] = array(
  1326 				$data['_links'] = array(
  1217 					'self' => rest_url( $route ),
  1327 					'self' => array(
       
  1328 						array(
       
  1329 							'href' => rest_url( $route ),
       
  1330 						),
       
  1331 					),
  1218 				);
  1332 				);
  1219 			}
  1333 			}
  1220 		}
  1334 		}
  1221 
  1335 
  1222 		if ( empty( $data['methods'] ) ) {
  1336 		if ( empty( $data['methods'] ) ) {
  1277 	 * @since 4.8.0
  1391 	 * @since 4.8.0
  1278 	 *
  1392 	 *
  1279 	 * @param string $key Header key.
  1393 	 * @param string $key Header key.
  1280 	 */
  1394 	 */
  1281 	public function remove_header( $key ) {
  1395 	public function remove_header( $key ) {
  1282 		if ( function_exists( 'header_remove' ) ) {
  1396 		header_remove( $key );
  1283 			// In PHP 5.3+ there is a way to remove an already set header.
       
  1284 			header_remove( $key );
       
  1285 		} else {
       
  1286 			// In PHP 5.2, send an empty header, but only as a last resort to
       
  1287 			// override a header already sent.
       
  1288 			foreach ( headers_list() as $header ) {
       
  1289 				if ( 0 === stripos( $header, "$key:" ) ) {
       
  1290 					$this->send_header( $key, '' );
       
  1291 					break;
       
  1292 				}
       
  1293 			}
       
  1294 		}
       
  1295 	}
  1397 	}
  1296 
  1398 
  1297 	/**
  1399 	/**
  1298 	 * Retrieves the raw request entity (body).
  1400 	 * Retrieves the raw request entity (body).
  1299 	 *
  1401 	 *
  1302 	 * @global string $HTTP_RAW_POST_DATA Raw post data.
  1404 	 * @global string $HTTP_RAW_POST_DATA Raw post data.
  1303 	 *
  1405 	 *
  1304 	 * @return string Raw request data.
  1406 	 * @return string Raw request data.
  1305 	 */
  1407 	 */
  1306 	public static function get_raw_data() {
  1408 	public static function get_raw_data() {
       
  1409 		// phpcs:disable PHPCompatibility.Variables.RemovedPredefinedGlobalVariables.http_raw_post_dataDeprecatedRemoved
  1307 		global $HTTP_RAW_POST_DATA;
  1410 		global $HTTP_RAW_POST_DATA;
  1308 
  1411 
  1309 		/*
  1412 		// $HTTP_RAW_POST_DATA was deprecated in PHP 5.6 and removed in PHP 7.0.
  1310 		 * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,
       
  1311 		 * but we can do it ourself.
       
  1312 		 */
       
  1313 		if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
  1413 		if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
  1314 			$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
  1414 			$HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
  1315 		}
  1415 		}
  1316 
  1416 
  1317 		return $HTTP_RAW_POST_DATA;
  1417 		return $HTTP_RAW_POST_DATA;
       
  1418 		// phpcs:enable
  1318 	}
  1419 	}
  1319 
  1420 
  1320 	/**
  1421 	/**
  1321 	 * Extracts headers from a PHP-style $_SERVER array.
  1422 	 * Extracts headers from a PHP-style $_SERVER array.
  1322 	 *
  1423 	 *
  1336 		);
  1437 		);
  1337 
  1438 
  1338 		foreach ( $server as $key => $value ) {
  1439 		foreach ( $server as $key => $value ) {
  1339 			if ( strpos( $key, 'HTTP_' ) === 0 ) {
  1440 			if ( strpos( $key, 'HTTP_' ) === 0 ) {
  1340 				$headers[ substr( $key, 5 ) ] = $value;
  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;
  1341 			} elseif ( isset( $additional[ $key ] ) ) {
  1448 			} elseif ( isset( $additional[ $key ] ) ) {
  1342 				$headers[ $key ] = $value;
  1449 				$headers[ $key ] = $value;
  1343 			}
  1450 			}
  1344 		}
  1451 		}
  1345 
  1452