'The handler for the route is invalid' ), array( 'status' => 500 ) ); } if ( ! is_wp_error( $response ) ) { // Remove the redundant preg_match argument. unset( $args[0] ); $request->set_url_params( $args ); $request->set_attributes( $handler ); $defaults = array(); foreach ( $handler['args'] as $arg => $options ) { if ( isset( $options['default'] ) ) { $defaults[ $arg ] = $options['default']; } } $request->set_default_params( $defaults ); $check_required = $request->has_valid_params(); if ( is_wp_error( $check_required ) ) { $response = $check_required; } else { $check_sanitized = $request->sanitize_params(); if ( is_wp_error( $check_sanitized ) ) { $response = $check_sanitized; } } } /** * Filters the response before executing any REST API callbacks. * * Allows plugins to perform additional validation after a * request is initialized and matched to a registered route, * but before it is executed. * * Note that this filter will not be called for requests that * fail to authenticate or match to a registered route. * * @since 4.7.0 * * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request Request used to generate the response. */ $response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request ); if ( ! is_wp_error( $response ) ) { // Check permission specified on the route. if ( ! empty( $handler['permission_callback'] ) ) { $permission = call_user_func( $handler['permission_callback'], $request ); if ( is_wp_error( $permission ) ) { $response = $permission; } elseif ( false === $permission || null === $permission ) { $response = new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => rest_authorization_required_code() ) ); } } } if ( ! is_wp_error( $response ) ) { /** * Filters the REST dispatch request result. * * Allow plugins to override dispatching the request. * * @since 4.4.0 * @since 4.5.0 Added `$route` and `$handler` parameters. * * @param bool $dispatch_result Dispatch result, will be used if not empty. * @param WP_REST_Request $request Request used to generate the response. * @param string $route Route matched for the request. * @param array $handler Route handler used for the request. */ $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler ); // Allow plugins to halt the request via this filter. if ( null !== $dispatch_result ) { $response = $dispatch_result; } else { $response = call_user_func( $callback, $request ); } } /** * Filters the response immediately after executing any REST API * callbacks. * * Allows plugins to perform any needed cleanup, for example, * to undo changes made during the {@see 'rest_request_before_callbacks'} * filter. * * Note that this filter will not be called for requests that * fail to authenticate or match to a registered route. * * Note that an endpoint's `permission_callback` can still be * called after this filter - see `rest_send_allow_header()`. * * @since 4.7.0 * * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). * @param WP_REST_Request $request Request used to generate the response. */ $response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request ); if ( is_wp_error( $response ) ) { $response = $this->error_to_response( $response ); } else { $response = rest_ensure_response( $response ); } $response->set_matched_route( $route ); $response->set_matched_handler( $handler ); return $response; } } return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) ); } /** * Returns if an error occurred during most recent JSON encode/decode. * * Strings to be translated will be in format like * "Encoding error: Maximum stack depth exceeded". * * @since 4.4.0 * * @return bool|string Boolean false or string error message. */ protected function get_json_last_error() { // See https://core.trac.wordpress.org/ticket/27799. if ( ! function_exists( 'json_last_error' ) ) { return false; } $last_error_code = json_last_error(); if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) { return false; } return json_last_error_msg(); } /** * Retrieves the site index. * * This endpoint describes the capabilities of the site. * * @since 4.4.0 * * @param array $request { * Request. * * @type string $context Context. * } * @return array Index entity */ public function get_index( $request ) { // General site data. $available = array( 'name' => get_option( 'blogname' ), 'description' => get_option( 'blogdescription' ), 'url' => get_option( 'siteurl' ), 'home' => home_url(), 'gmt_offset' => get_option( 'gmt_offset' ), 'timezone_string' => get_option( 'timezone_string' ), 'namespaces' => array_keys( $this->namespaces ), 'authentication' => array(), 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ), ); $response = new WP_REST_Response( $available ); $response->add_link( 'help', 'http://v2.wp-api.org/' ); /** * Filters the API root index data. * * This contains the data describing the API. This includes information * about supported authentication schemes, supported namespaces, routes * available on the API, and a small amount of data about the site. * * @since 4.4.0 * * @param WP_REST_Response $response Response data. */ return apply_filters( 'rest_index', $response ); } /** * Retrieves the index for a namespace. * * @since 4.4.0 * * @param WP_REST_Request $request REST request instance. * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found, * WP_Error if the namespace isn't set. */ public function get_namespace_index( $request ) { $namespace = $request['namespace']; if ( ! isset( $this->namespaces[ $namespace ] ) ) { return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) ); } $routes = $this->namespaces[ $namespace ]; $endpoints = array_intersect_key( $this->get_routes(), $routes ); $data = array( 'namespace' => $namespace, 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ), ); $response = rest_ensure_response( $data ); // Link to the root index. $response->add_link( 'up', rest_url( '/' ) ); /** * Filters the namespace index data. * * This typically is just the route data for the namespace, but you can * add any data you'd like here. * * @since 4.4.0 * * @param WP_REST_Response $response Response data. * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. */ return apply_filters( 'rest_namespace_index', $response, $request ); } /** * Retrieves the publicly-visible data for routes. * * @since 4.4.0 * * @param array $routes Routes to get data for. * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'. * @return array Route data to expose in indexes. */ public function get_data_for_routes( $routes, $context = 'view' ) { $available = array(); // Find the available routes. foreach ( $routes as $route => $callbacks ) { $data = $this->get_data_for_route( $route, $callbacks, $context ); if ( empty( $data ) ) { continue; } /** * Filters the REST endpoint data. * * @since 4.4.0 * * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. */ $available[ $route ] = apply_filters( 'rest_endpoints_description', $data ); } /** * Filters the publicly-visible data for routes. * * This data is exposed on indexes and can be used by clients or * developers to investigate the site and find out how to use it. It * acts as a form of self-documentation. * * @since 4.4.0 * * @param array $available Map of route to route data. * @param array $routes Internal route data as an associative array. */ return apply_filters( 'rest_route_data', $available, $routes ); } /** * Retrieves publicly-visible data for the route. * * @since 4.4.0 * * @param string $route Route to get data for. * @param array $callbacks Callbacks to convert to data. * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'. * @return array|null Data for the route, or null if no publicly-visible data. */ public function get_data_for_route( $route, $callbacks, $context = 'view' ) { $data = array( 'namespace' => '', 'methods' => array(), 'endpoints' => array(), ); if ( isset( $this->route_options[ $route ] ) ) { $options = $this->route_options[ $route ]; if ( isset( $options['namespace'] ) ) { $data['namespace'] = $options['namespace']; } if ( isset( $options['schema'] ) && 'help' === $context ) { $data['schema'] = call_user_func( $options['schema'] ); } } $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route ); foreach ( $callbacks as $callback ) { // Skip to the next route if any callback is hidden. if ( empty( $callback['show_in_index'] ) ) { continue; } $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) ); $endpoint_data = array( 'methods' => array_keys( $callback['methods'] ), ); if ( isset( $callback['args'] ) ) { $endpoint_data['args'] = array(); foreach ( $callback['args'] as $key => $opts ) { $arg_data = array( 'required' => ! empty( $opts['required'] ), ); if ( isset( $opts['default'] ) ) { $arg_data['default'] = $opts['default']; } if ( isset( $opts['enum'] ) ) { $arg_data['enum'] = $opts['enum']; } if ( isset( $opts['description'] ) ) { $arg_data['description'] = $opts['description']; } if ( isset( $opts['type'] ) ) { $arg_data['type'] = $opts['type']; } if ( isset( $opts['items'] ) ) { $arg_data['items'] = $opts['items']; } $endpoint_data['args'][ $key ] = $arg_data; } } $data['endpoints'][] = $endpoint_data; // For non-variable routes, generate links. if ( strpos( $route, '{' ) === false ) { $data['_links'] = array( 'self' => rest_url( $route ), ); } } if ( empty( $data['methods'] ) ) { // No methods supported, hide the route. return null; } return $data; } /** * Sends an HTTP status code. * * @since 4.4.0 * * @param int $code HTTP status. */ protected function set_status( $code ) { status_header( $code ); } /** * Sends an HTTP header. * * @since 4.4.0 * * @param string $key Header key. * @param string $value Header value. */ public function send_header( $key, $value ) { /* * Sanitize as per RFC2616 (Section 4.2): * * Any LWS that occurs between field-content MAY be replaced with a * single SP before interpreting the field value or forwarding the * message downstream. */ $value = preg_replace( '/\s+/', ' ', $value ); header( sprintf( '%s: %s', $key, $value ) ); } /** * Sends multiple HTTP headers. * * @since 4.4.0 * * @param array $headers Map of header name to header value. */ public function send_headers( $headers ) { foreach ( $headers as $key => $value ) { $this->send_header( $key, $value ); } } /** * Removes an HTTP header from the current response. * * @since 4.8.0 * * @param string $key Header key. */ public function remove_header( $key ) { if ( function_exists( 'header_remove' ) ) { // In PHP 5.3+ there is a way to remove an already set header. header_remove( $key ); } else { // In PHP 5.2, send an empty header, but only as a last resort to // override a header already sent. foreach ( headers_list() as $header ) { if ( 0 === stripos( $header, "$key:" ) ) { $this->send_header( $key, '' ); break; } } } } /** * Retrieves the raw request entity (body). * * @since 4.4.0 * * @global string $HTTP_RAW_POST_DATA Raw post data. * * @return string Raw request data. */ public static function get_raw_data() { global $HTTP_RAW_POST_DATA; /* * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default, * but we can do it ourself. */ if ( ! isset( $HTTP_RAW_POST_DATA ) ) { $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); } return $HTTP_RAW_POST_DATA; } /** * Extracts headers from a PHP-style $_SERVER array. * * @since 4.4.0 * * @param array $server Associative array similar to `$_SERVER`. * @return array Headers extracted from the input. */ public function get_headers( $server ) { $headers = array(); // CONTENT_* headers are not prefixed with HTTP_. $additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true ); foreach ( $server as $key => $value ) { if ( strpos( $key, 'HTTP_' ) === 0 ) { $headers[ substr( $key, 5 ) ] = $value; } elseif ( isset( $additional[ $key ] ) ) { $headers[ $key ] = $value; } } return $headers; } }