WP_REST_Server

Core class used to implement the WordPress REST API server.

Defined (1)

The class is defined in the following location(s).

/wp-includes/rest-api/class-wp-rest-server.php  
  1. class WP_REST_Server { 
  2.  
  3. /** 
  4. * Alias for GET transport method. 
  5. * @since 4.4.0 
  6. * @var string 
  7. */ 
  8. const READABLE = 'GET'; 
  9.  
  10. /** 
  11. * Alias for POST transport method. 
  12. * @since 4.4.0 
  13. * @var string 
  14. */ 
  15. const CREATABLE = 'POST'; 
  16.  
  17. /** 
  18. * Alias for POST, PUT, PATCH transport methods together. 
  19. * @since 4.4.0 
  20. * @var string 
  21. */ 
  22. const EDITABLE = 'POST, PUT, PATCH'; 
  23.  
  24. /** 
  25. * Alias for DELETE transport method. 
  26. * @since 4.4.0 
  27. * @var string 
  28. */ 
  29. const DELETABLE = 'DELETE'; 
  30.  
  31. /** 
  32. * Alias for GET, POST, PUT, PATCH & DELETE transport methods together. 
  33. * @since 4.4.0 
  34. * @var string 
  35. */ 
  36. const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE'; 
  37.  
  38. /** 
  39. * Namespaces registered to the server. 
  40. * @since 4.4.0 
  41. * @access protected 
  42. * @var array 
  43. */ 
  44. protected $namespaces = array(); 
  45.  
  46. /** 
  47. * Endpoints registered to the server. 
  48. * @since 4.4.0 
  49. * @access protected 
  50. * @var array 
  51. */ 
  52. protected $endpoints = array(); 
  53.  
  54. /** 
  55. * Options defined for the routes. 
  56. * @since 4.4.0 
  57. * @access protected 
  58. * @var array 
  59. */ 
  60. protected $route_options = array(); 
  61.  
  62. /** 
  63. * Instantiates the REST server. 
  64. * @since 4.4.0 
  65. * @access public 
  66. */ 
  67. public function __construct() { 
  68. $this->endpoints = array( 
  69. // Meta endpoints. 
  70. '/' => array( 
  71. 'callback' => array( $this, 'get_index' ),  
  72. 'methods' => 'GET',  
  73. 'args' => array( 
  74. 'context' => array( 
  75. 'default' => 'view',  
  76. ),  
  77. ),  
  78. ),  
  79. ); 
  80.  
  81.  
  82. /** 
  83. * Checks the authentication headers if supplied. 
  84. * @since 4.4.0 
  85. * @access public 
  86. * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful 
  87. * or no authentication provided 
  88. */ 
  89. public function check_authentication() { 
  90. /** 
  91. * Filters REST authentication errors. 
  92. * This is used to pass a WP_Error from an authentication method back to 
  93. * the API. 
  94. * Authentication methods should check first if they're being used, as 
  95. * multiple authentication methods can be enabled on a site (cookies,  
  96. * HTTP basic auth, OAuth). If the authentication method hooked in is 
  97. * not actually being attempted, null should be returned to indicate 
  98. * another authentication method should check instead. Similarly,  
  99. * callbacks should ensure the value is `null` before checking for 
  100. * errors. 
  101. * A WP_Error instance can be returned if an error occurs, and this should 
  102. * match the format used by API methods internally (that is, the `status` 
  103. * data should be used). A callback can return `true` to indicate that 
  104. * the authentication method was used, and it succeeded. 
  105. * @since 4.4.0 
  106. * @param WP_Error|null|bool WP_Error if authentication error, null if authentication 
  107. * method wasn't used, true if authentication succeeded. 
  108. */ 
  109. return apply_filters( 'rest_authentication_errors', null ); 
  110.  
  111. /** 
  112. * Converts an error to a response object. 
  113. * This iterates over all error codes and messages to change it into a flat 
  114. * array. This enables simpler client behaviour, as it is represented as a 
  115. * list in JSON rather than an object/map. 
  116. * @since 4.4.0 
  117. * @access protected 
  118. * @param WP_Error $error WP_Error instance. 
  119. * @return WP_REST_Response List of associative arrays with code and message keys. 
  120. */ 
  121. protected function error_to_response( $error ) { 
  122. $error_data = $error->get_error_data(); 
  123.  
  124. if ( is_array( $error_data ) && isset( $error_data['status'] ) ) { 
  125. $status = $error_data['status']; 
  126. } else { 
  127. $status = 500; 
  128.  
  129. $errors = array(); 
  130.  
  131. foreach ( (array) $error->errors as $code => $messages ) { 
  132. foreach ( (array) $messages as $message ) { 
  133. $errors[] = array( 'code' => $code, 'message' => $message, 'data' => $error->get_error_data( $code ) ); 
  134.  
  135. $data = $errors[0]; 
  136. if ( count( $errors ) > 1 ) { 
  137. // Remove the primary error. 
  138. array_shift( $errors ); 
  139. $data['additional_errors'] = $errors; 
  140.  
  141. $response = new WP_REST_Response( $data, $status ); 
  142.  
  143. return $response; 
  144.  
  145. /** 
  146. * Retrieves an appropriate error representation in JSON. 
  147. * Note: This should only be used in WP_REST_Server::serve_request(), as it 
  148. * cannot handle WP_Error internally. All callbacks and other internal methods 
  149. * should instead return a WP_Error with the data set to an array that includes 
  150. * a 'status' key, with the value being the HTTP status to send. 
  151. * @since 4.4.0 
  152. * @access protected 
  153. * @param string $code WP_Error-style code. 
  154. * @param string $message Human-readable message. 
  155. * @param int $status Optional. HTTP status code to send. Default null. 
  156. * @return string JSON representation of the error 
  157. */ 
  158. protected function json_error( $code, $message, $status = null ) { 
  159. if ( $status ) { 
  160. $this->set_status( $status ); 
  161.  
  162. $error = compact( 'code', 'message' ); 
  163.  
  164. return wp_json_encode( $error ); 
  165.  
  166. /** 
  167. * Handles serving an API request. 
  168. * Matches the current server URI to a route and runs the first matching 
  169. * callback then outputs a JSON representation of the returned value. 
  170. * @since 4.4.0 
  171. * @access public 
  172. * @see WP_REST_Server::dispatch() 
  173. * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used. 
  174. * Default null. 
  175. * @return false|null Null if not served and a HEAD request, false otherwise. 
  176. */ 
  177. public function serve_request( $path = null ) { 
  178. $content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json'; 
  179. $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) ); 
  180. $this->send_header( 'X-Robots-Tag', 'noindex' ); 
  181.  
  182. $api_root = get_rest_url(); 
  183. if ( ! empty( $api_root ) ) { 
  184. $this->send_header( 'Link', '<' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"' ); 
  185.  
  186. /** 
  187. * Mitigate possible JSONP Flash attacks. 
  188. * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ 
  189. */ 
  190. $this->send_header( 'X-Content-Type-Options', 'nosniff' ); 
  191. $this->send_header( 'Access-Control-Expose-Headers', 'X-WP-Total, X-WP-TotalPages' ); 
  192. $this->send_header( 'Access-Control-Allow-Headers', 'Authorization, Content-Type' ); 
  193.  
  194. /** 
  195. * Send nocache headers on authenticated requests. 
  196. * @since 4.4.0 
  197. * @param bool $rest_send_nocache_headers Whether to send no-cache headers. 
  198. */ 
  199. $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() ); 
  200. if ( $send_no_cache_headers ) { 
  201. foreach ( wp_get_nocache_headers() as $header => $header_value ) { 
  202. $this->send_header( $header, $header_value ); 
  203.  
  204. /** 
  205. * Filters whether the REST API is enabled. 
  206. * @since 4.4.0 
  207. * @deprecated 4.7.0 Use the rest_authentication_errors filter to restrict access to the API 
  208. * @param bool $rest_enabled Whether the REST API is enabled. Default true. 
  209. */ 
  210. apply_filters_deprecated( 'rest_enabled', array( true ), '4.7.0', 'rest_authentication_errors', __( 'The REST API can no longer be completely disabled, the rest_authentication_errors can be used to restrict access to the API, instead.' ) ); 
  211.  
  212. /** 
  213. * Filters whether jsonp is enabled. 
  214. * @since 4.4.0 
  215. * @param bool $jsonp_enabled Whether jsonp is enabled. Default true. 
  216. */ 
  217. $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true ); 
  218.  
  219. $jsonp_callback = null; 
  220.  
  221. if ( isset( $_GET['_jsonp'] ) ) { 
  222. if ( ! $jsonp_enabled ) { 
  223. echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 ); 
  224. return false; 
  225.  
  226. $jsonp_callback = $_GET['_jsonp']; 
  227. if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) { 
  228. echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 ); 
  229. return false; 
  230.  
  231. if ( empty( $path ) ) { 
  232. if ( isset( $_SERVER['PATH_INFO'] ) ) { 
  233. $path = $_SERVER['PATH_INFO']; 
  234. } else { 
  235. $path = '/'; 
  236.  
  237. $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path ); 
  238.  
  239. $request->set_query_params( wp_unslash( $_GET ) ); 
  240. $request->set_body_params( wp_unslash( $_POST ) ); 
  241. $request->set_file_params( $_FILES ); 
  242. $request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) ); 
  243. $request->set_body( $this->get_raw_data() ); 
  244.  
  245. /** 
  246. * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check 
  247. * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE 
  248. * header. 
  249. */ 
  250. if ( isset( $_GET['_method'] ) ) { 
  251. $request->set_method( $_GET['_method'] ); 
  252. } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { 
  253. $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ); 
  254.  
  255. $result = $this->check_authentication(); 
  256.  
  257. if ( ! is_wp_error( $result ) ) { 
  258. $result = $this->dispatch( $request ); 
  259.  
  260. // Normalize to either WP_Error or WP_REST_Response... 
  261. $result = rest_ensure_response( $result ); 
  262.  
  263. // ...then convert WP_Error across. 
  264. if ( is_wp_error( $result ) ) { 
  265. $result = $this->error_to_response( $result ); 
  266.  
  267. /** 
  268. * Filters the API response. 
  269. * Allows modification of the response before returning. 
  270. * @since 4.4.0 
  271. * @since 4.5.0 Applied to embedded responses. 
  272. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. 
  273. * @param WP_REST_Server $this Server instance. 
  274. * @param WP_REST_Request $request Request used to generate the response. 
  275. */ 
  276. $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request ); 
  277.  
  278. // Wrap the response in an envelope if asked for. 
  279. if ( isset( $_GET['_envelope'] ) ) { 
  280. $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) ); 
  281.  
  282. // Send extra data from response objects. 
  283. $headers = $result->get_headers(); 
  284. $this->send_headers( $headers ); 
  285.  
  286. $code = $result->get_status(); 
  287. $this->set_status( $code ); 
  288.  
  289. /** 
  290. * Filters whether the request has already been served. 
  291. * Allow sending the request manually - by returning true, the API result 
  292. * will not be sent to the client. 
  293. * @since 4.4.0 
  294. * @param bool $served Whether the request has already been served. 
  295. * Default false. 
  296. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. 
  297. * @param WP_REST_Request $request Request used to generate the response. 
  298. * @param WP_REST_Server $this Server instance. 
  299. */ 
  300. $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this ); 
  301.  
  302. if ( ! $served ) { 
  303. if ( 'HEAD' === $request->get_method() ) { 
  304. return null; 
  305.  
  306. // Embed links inside the request. 
  307. $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) ); 
  308.  
  309. $result = wp_json_encode( $result ); 
  310.  
  311. $json_error_message = $this->get_json_last_error(); 
  312. if ( $json_error_message ) { 
  313. $json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) ); 
  314. $result = $this->error_to_response( $json_error_obj ); 
  315. $result = wp_json_encode( $result->data[0] ); 
  316.  
  317. if ( $jsonp_callback ) { 
  318. // Prepend '/**/' to mitigate possible JSONP Flash attacks. 
  319. // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ 
  320. echo '/**/' . $jsonp_callback . '(' . $result . ')'; 
  321. } else { 
  322. echo $result; 
  323. return null; 
  324.  
  325. /** 
  326. * Converts a response to data to send. 
  327. * @since 4.4.0 
  328. * @access public 
  329. * @param WP_REST_Response $response Response object. 
  330. * @param bool $embed Whether links should be embedded. 
  331. * @return array { 
  332. * Data with sub-requests embedded. 
  333. * @type array [$_links] Links. 
  334. * @type array [$_embedded] Embeddeds. 
  335. * } 
  336. */ 
  337. public function response_to_data( $response, $embed ) { 
  338. $data = $response->get_data(); 
  339. $links = $this->get_compact_response_links( $response ); 
  340.  
  341. if ( ! empty( $links ) ) { 
  342. // Convert links to part of the data. 
  343. $data['_links'] = $links; 
  344. if ( $embed ) { 
  345. // Determine if this is a numeric array. 
  346. if ( wp_is_numeric_array( $data ) ) { 
  347. $data = array_map( array( $this, 'embed_links' ), $data ); 
  348. } else { 
  349. $data = $this->embed_links( $data ); 
  350.  
  351. return $data; 
  352.  
  353. /** 
  354. * Retrieves links from a response. 
  355. * Extracts the links from a response into a structured hash, suitable for 
  356. * direct output. 
  357. * @since 4.4.0 
  358. * @access public 
  359. * @static 
  360. * @param WP_REST_Response $response Response to extract links from. 
  361. * @return array Map of link relation to list of link hashes. 
  362. */ 
  363. public static function get_response_links( $response ) { 
  364. $links = $response->get_links(); 
  365. if ( empty( $links ) ) { 
  366. return array(); 
  367.  
  368. // Convert links to part of the data. 
  369. $data = array(); 
  370. foreach ( $links as $rel => $items ) { 
  371. $data[ $rel ] = array(); 
  372.  
  373. foreach ( $items as $item ) { 
  374. $attributes = $item['attributes']; 
  375. $attributes['href'] = $item['href']; 
  376. $data[ $rel ][] = $attributes; 
  377.  
  378. return $data; 
  379.  
  380. /** 
  381. * Retrieves the CURIEs (compact URIs) used for relations. 
  382. * Extracts the links from a response into a structured hash, suitable for 
  383. * direct output. 
  384. * @since 4.5.0 
  385. * @access public 
  386. * @static 
  387. * @param WP_REST_Response $response Response to extract links from. 
  388. * @return array Map of link relation to list of link hashes. 
  389. */ 
  390. public static function get_compact_response_links( $response ) { 
  391. $links = self::get_response_links( $response ); 
  392.  
  393. if ( empty( $links ) ) { 
  394. return array(); 
  395.  
  396. $curies = $response->get_curies(); 
  397. $used_curies = array(); 
  398.  
  399. foreach ( $links as $rel => $items ) { 
  400.  
  401. // Convert $rel URIs to their compact versions if they exist. 
  402. foreach ( $curies as $curie ) { 
  403. $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) ); 
  404. if ( strpos( $rel, $href_prefix ) !== 0 ) { 
  405. continue; 
  406.  
  407. // Relation now changes from '$uri' to '$curie:$relation'. 
  408. $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) ); 
  409. preg_match( '!' . $rel_regex . '!', $rel, $matches ); 
  410. if ( $matches ) { 
  411. $new_rel = $curie['name'] . ':' . $matches[1]; 
  412. $used_curies[ $curie['name'] ] = $curie; 
  413. $links[ $new_rel ] = $items; 
  414. unset( $links[ $rel ] ); 
  415. break; 
  416.  
  417. // Push the curies onto the start of the links array. 
  418. if ( $used_curies ) { 
  419. $links['curies'] = array_values( $used_curies ); 
  420.  
  421. return $links; 
  422.  
  423. /** 
  424. * Embeds the links from the data into the request. 
  425. * @since 4.4.0 
  426. * @access protected 
  427. * @param array $data Data from the request. 
  428. * @return array { 
  429. * Data with sub-requests embedded. 
  430. * @type array [$_links] Links. 
  431. * @type array [$_embedded] Embeddeds. 
  432. * } 
  433. */ 
  434. protected function embed_links( $data ) { 
  435. if ( empty( $data['_links'] ) ) { 
  436. return $data; 
  437.  
  438. $embedded = array(); 
  439.  
  440. foreach ( $data['_links'] as $rel => $links ) { 
  441. // Ignore links to self, for obvious reasons. 
  442. if ( 'self' === $rel ) { 
  443. continue; 
  444.  
  445. $embeds = array(); 
  446.  
  447. foreach ( $links as $item ) { 
  448. // Determine if the link is embeddable. 
  449. if ( empty( $item['embeddable'] ) ) { 
  450. // Ensure we keep the same order. 
  451. $embeds[] = array(); 
  452. continue; 
  453.  
  454. // Run through our internal routing and serve. 
  455. $request = WP_REST_Request::from_url( $item['href'] ); 
  456. if ( ! $request ) { 
  457. $embeds[] = array(); 
  458. continue; 
  459.  
  460. // Embedded resources get passed context=embed. 
  461. if ( empty( $request['context'] ) ) { 
  462. $request['context'] = 'embed'; 
  463.  
  464. $response = $this->dispatch( $request ); 
  465.  
  466. /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ 
  467. $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); 
  468.  
  469. $embeds[] = $this->response_to_data( $response, false ); 
  470.  
  471. // Determine if any real links were found. 
  472. $has_links = count( array_filter( $embeds ) ); 
  473.  
  474. if ( $has_links ) { 
  475. $embedded[ $rel ] = $embeds; 
  476.  
  477. if ( ! empty( $embedded ) ) { 
  478. $data['_embedded'] = $embedded; 
  479.  
  480. return $data; 
  481.  
  482. /** 
  483. * Wraps the response in an envelope. 
  484. * The enveloping technique is used to work around browser/client 
  485. * compatibility issues. Essentially, it converts the full HTTP response to 
  486. * data instead. 
  487. * @since 4.4.0 
  488. * @access public 
  489. * @param WP_REST_Response $response Response object. 
  490. * @param bool $embed Whether links should be embedded. 
  491. * @return WP_REST_Response New response with wrapped data 
  492. */ 
  493. public function envelope_response( $response, $embed ) { 
  494. $envelope = array( 
  495. 'body' => $this->response_to_data( $response, $embed ),  
  496. 'status' => $response->get_status(),  
  497. 'headers' => $response->get_headers(),  
  498. ); 
  499.  
  500. /** 
  501. * Filters the enveloped form of a response. 
  502. * @since 4.4.0 
  503. * @param array $envelope Envelope data. 
  504. * @param WP_REST_Response $response Original response data. 
  505. */ 
  506. $envelope = apply_filters( 'rest_envelope_response', $envelope, $response ); 
  507.  
  508. // Ensure it's still a response and return. 
  509. return rest_ensure_response( $envelope ); 
  510.  
  511. /** 
  512. * Registers a route to the server. 
  513. * @since 4.4.0 
  514. * @access public 
  515. * @param string $namespace Namespace. 
  516. * @param string $route The REST route. 
  517. * @param array $route_args Route arguments. 
  518. * @param bool $override Optional. Whether the route should be overridden if it already exists. 
  519. * Default false. 
  520. */ 
  521. public function register_route( $namespace, $route, $route_args, $override = false ) { 
  522. if ( ! isset( $this->namespaces[ $namespace ] ) ) { 
  523. $this->namespaces[ $namespace ] = array(); 
  524.  
  525. $this->register_route( $namespace, '/' . $namespace, array( 
  526. array( 
  527. 'methods' => self::READABLE,  
  528. 'callback' => array( $this, 'get_namespace_index' ),  
  529. 'args' => array( 
  530. 'namespace' => array( 
  531. 'default' => $namespace,  
  532. ),  
  533. 'context' => array( 
  534. 'default' => 'view',  
  535. ),  
  536. ),  
  537. ),  
  538. ) ); 
  539.  
  540. // Associative to avoid double-registration. 
  541. $this->namespaces[ $namespace ][ $route ] = true; 
  542. $route_args['namespace'] = $namespace; 
  543.  
  544. if ( $override || empty( $this->endpoints[ $route ] ) ) { 
  545. $this->endpoints[ $route ] = $route_args; 
  546. } else { 
  547. $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args ); 
  548.  
  549. /** 
  550. * Retrieves the route map. 
  551. * The route map is an associative array with path regexes as the keys. The 
  552. * value is an indexed array with the callback function/method as the first 
  553. * item, and a bitmask of HTTP methods as the second item (see the class 
  554. * constants). 
  555. * Each route can be mapped to more than one callback by using an array of 
  556. * the indexed arrays. This allows mapping e.g. GET requests to one callback 
  557. * and POST requests to another. 
  558. * Note that the path regexes (array keys) must have @ escaped, as this is 
  559. * used as the delimiter with preg_match() 
  560. * @since 4.4.0 
  561. * @access public 
  562. * @return array `'/path/regex' => array( $callback, $bitmask )` or 
  563. * `'/path/regex' => array( array( $callback, $bitmask ), ...)`. 
  564. */ 
  565. public function get_routes() { 
  566.  
  567. /** 
  568. * Filters the array of available endpoints. 
  569. * @since 4.4.0 
  570. * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped 
  571. * to an array of callbacks for the endpoint. These take the format 
  572. * `'/path/regex' => array( $callback, $bitmask )` or 
  573. * `'/path/regex' => array( array( $callback, $bitmask ). 
  574. */ 
  575. $endpoints = apply_filters( 'rest_endpoints', $this->endpoints ); 
  576.  
  577. // Normalise the endpoints. 
  578. $defaults = array( 
  579. 'methods' => '',  
  580. 'accept_json' => false,  
  581. 'accept_raw' => false,  
  582. 'show_in_index' => true,  
  583. 'args' => array(),  
  584. ); 
  585.  
  586. foreach ( $endpoints as $route => &$handlers ) { 
  587.  
  588. if ( isset( $handlers['callback'] ) ) { 
  589. // Single endpoint, add one deeper. 
  590. $handlers = array( $handlers ); 
  591.  
  592. if ( ! isset( $this->route_options[ $route ] ) ) { 
  593. $this->route_options[ $route ] = array(); 
  594.  
  595. foreach ( $handlers as $key => &$handler ) { 
  596.  
  597. if ( ! is_numeric( $key ) ) { 
  598. // Route option, move it to the options. 
  599. $this->route_options[ $route ][ $key ] = $handler; 
  600. unset( $handlers[ $key ] ); 
  601. continue; 
  602.  
  603. $handler = wp_parse_args( $handler, $defaults ); 
  604.  
  605. // Allow comma-separated HTTP methods. 
  606. if ( is_string( $handler['methods'] ) ) { 
  607. $methods = explode( ', ', $handler['methods'] ); 
  608. } elseif ( is_array( $handler['methods'] ) ) { 
  609. $methods = $handler['methods']; 
  610. } else { 
  611. $methods = array(); 
  612.  
  613. $handler['methods'] = array(); 
  614.  
  615. foreach ( $methods as $method ) { 
  616. $method = strtoupper( trim( $method ) ); 
  617. $handler['methods'][ $method ] = true; 
  618.  
  619. return $endpoints; 
  620.  
  621. /** 
  622. * Retrieves namespaces registered on the server. 
  623. * @since 4.4.0 
  624. * @access public 
  625. * @return array List of registered namespaces. 
  626. */ 
  627. public function get_namespaces() { 
  628. return array_keys( $this->namespaces ); 
  629.  
  630. /** 
  631. * Retrieves specified options for a route. 
  632. * @since 4.4.0 
  633. * @access public 
  634. * @param string $route Route pattern to fetch options for. 
  635. * @return array|null Data as an associative array if found, or null if not found. 
  636. */ 
  637. public function get_route_options( $route ) { 
  638. if ( ! isset( $this->route_options[ $route ] ) ) { 
  639. return null; 
  640.  
  641. return $this->route_options[ $route ]; 
  642.  
  643. /** 
  644. * Matches the request to a callback and call it. 
  645. * @since 4.4.0 
  646. * @access public 
  647. * @param WP_REST_Request $request Request to attempt dispatching. 
  648. * @return WP_REST_Response Response returned by the callback. 
  649. */ 
  650. public function dispatch( $request ) { 
  651. /** 
  652. * Filters the pre-calculated result of a REST dispatch request. 
  653. * Allow hijacking the request before dispatching by returning a non-empty. The returned value 
  654. * will be used to serve the request instead. 
  655. * @since 4.4.0 
  656. * @param mixed $result Response to replace the requested version with. Can be anything 
  657. * a normal endpoint can return, or null to not hijack the request. 
  658. * @param WP_REST_Server $this Server instance. 
  659. * @param WP_REST_Request $request Request used to generate the response. 
  660. */ 
  661. $result = apply_filters( 'rest_pre_dispatch', null, $this, $request ); 
  662.  
  663. if ( ! empty( $result ) ) { 
  664. return $result; 
  665.  
  666. $method = $request->get_method(); 
  667. $path = $request->get_route(); 
  668.  
  669. foreach ( $this->get_routes() as $route => $handlers ) { 
  670. $match = preg_match( '@^' . $route . '$@i', $path, $args ); 
  671.  
  672. if ( ! $match ) { 
  673. continue; 
  674.  
  675. foreach ( $handlers as $handler ) { 
  676. $callback = $handler['callback']; 
  677. $response = null; 
  678.  
  679. // Fallback to GET method if no HEAD method is registered. 
  680. $checked_method = $method; 
  681. if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) { 
  682. $checked_method = 'GET'; 
  683. if ( empty( $handler['methods'][ $checked_method ] ) ) { 
  684. continue; 
  685.  
  686. if ( ! is_callable( $callback ) ) { 
  687. $response = new WP_Error( 'rest_invalid_handler', __( 'The handler for the route is invalid' ), array( 'status' => 500 ) ); 
  688.  
  689. if ( ! is_wp_error( $response ) ) { 
  690. // Remove the redundant preg_match argument. 
  691. unset( $args[0] ); 
  692.  
  693. $request->set_url_params( $args ); 
  694. $request->set_attributes( $handler ); 
  695.  
  696. $defaults = array(); 
  697.  
  698. foreach ( $handler['args'] as $arg => $options ) { 
  699. if ( isset( $options['default'] ) ) { 
  700. $defaults[ $arg ] = $options['default']; 
  701.  
  702. $request->set_default_params( $defaults ); 
  703.  
  704. $check_required = $request->has_valid_params(); 
  705. if ( is_wp_error( $check_required ) ) { 
  706. $response = $check_required; 
  707. } else { 
  708. $check_sanitized = $request->sanitize_params(); 
  709. if ( is_wp_error( $check_sanitized ) ) { 
  710. $response = $check_sanitized; 
  711.  
  712. /** 
  713. * Filters the response before executing any REST API callbacks. 
  714. * Allows plugins to perform additional validation after a 
  715. * request is initialized and matched to a registered route,  
  716. * but before it is executed. 
  717. * Note that this filter will not be called for requests that 
  718. * fail to authenticate or match to a registered route. 
  719. * @since 4.7.0 
  720. * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response. 
  721. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). 
  722. * @param WP_REST_Request $request Request used to generate the response. 
  723. */ 
  724. $response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request ); 
  725.  
  726. if ( ! is_wp_error( $response ) ) { 
  727. // Check permission specified on the route. 
  728. if ( ! empty( $handler['permission_callback'] ) ) { 
  729. $permission = call_user_func( $handler['permission_callback'], $request ); 
  730.  
  731. if ( is_wp_error( $permission ) ) { 
  732. $response = $permission; 
  733. } elseif ( false === $permission || null === $permission ) { 
  734. $response = new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => 403 ) ); 
  735.  
  736. if ( ! is_wp_error( $response ) ) { 
  737. /** 
  738. * Filters the REST dispatch request result. 
  739. * Allow plugins to override dispatching the request. 
  740. * @since 4.4.0 
  741. * @since 4.5.0 Added `$route` and `$handler` parameters. 
  742. * @param bool $dispatch_result Dispatch result, will be used if not empty. 
  743. * @param WP_REST_Request $request Request used to generate the response. 
  744. * @param string $route Route matched for the request. 
  745. * @param array $handler Route handler used for the request. 
  746. */ 
  747. $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler ); 
  748.  
  749. // Allow plugins to halt the request via this filter. 
  750. if ( null !== $dispatch_result ) { 
  751. $response = $dispatch_result; 
  752. } else { 
  753. $response = call_user_func( $callback, $request ); 
  754.  
  755. /** 
  756. * Filters the response immediately after executing any REST API 
  757. * callbacks. 
  758. * Allows plugins to perform any needed cleanup, for example,  
  759. * to undo changes made during the {@see 'rest_request_before_callbacks'} 
  760. * filter. 
  761. * Note that this filter will not be called for requests that 
  762. * fail to authenticate or match to a registered route. 
  763. * Note that an endpoint's `permission_callback` can still be 
  764. * called after this filter - see `rest_send_allow_header()`. 
  765. * @since 4.7.0 
  766. * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response. 
  767. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). 
  768. * @param WP_REST_Request $request Request used to generate the response. 
  769. */ 
  770. $response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request ); 
  771.  
  772. if ( is_wp_error( $response ) ) { 
  773. $response = $this->error_to_response( $response ); 
  774. } else { 
  775. $response = rest_ensure_response( $response ); 
  776.  
  777. $response->set_matched_route( $route ); 
  778. $response->set_matched_handler( $handler ); 
  779.  
  780. return $response; 
  781.  
  782. return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) ); 
  783.  
  784. /** 
  785. * Returns if an error occurred during most recent JSON encode/decode. 
  786. * Strings to be translated will be in format like 
  787. * "Encoding error: Maximum stack depth exceeded". 
  788. * @since 4.4.0 
  789. * @access protected 
  790. * @return bool|string Boolean false or string error message. 
  791. */ 
  792. protected function get_json_last_error() { 
  793. // See https://core.trac.wordpress.org/ticket/27799. 
  794. if ( ! function_exists( 'json_last_error' ) ) { 
  795. return false; 
  796.  
  797. $last_error_code = json_last_error(); 
  798.  
  799. if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) { 
  800. return false; 
  801.  
  802. return json_last_error_msg(); 
  803.  
  804. /** 
  805. * Retrieves the site index. 
  806. * This endpoint describes the capabilities of the site. 
  807. * @since 4.4.0 
  808. * @access public 
  809. * @param array $request { 
  810. * Request. 
  811. * @type string $context Context. 
  812. * } 
  813. * @return array Index entity 
  814. */ 
  815. public function get_index( $request ) { 
  816. // General site data. 
  817. $available = array( 
  818. 'name' => get_option( 'blogname' ),  
  819. 'description' => get_option( 'blogdescription' ),  
  820. 'url' => get_option( 'siteurl' ),  
  821. 'home' => home_url(),  
  822. 'gmt_offset' => get_option( 'gmt_offset' ),  
  823. 'timezone_string' => get_option( 'timezone_string' ),  
  824. 'namespaces' => array_keys( $this->namespaces ),  
  825. 'authentication' => array(),  
  826. 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),  
  827. ); 
  828.  
  829. $response = new WP_REST_Response( $available ); 
  830.  
  831. $response->add_link( 'help', 'http://v2.wp-api.org/' ); 
  832.  
  833. /** 
  834. * Filters the API root index data. 
  835. * This contains the data describing the API. This includes information 
  836. * about supported authentication schemes, supported namespaces, routes 
  837. * available on the API, and a small amount of data about the site. 
  838. * @since 4.4.0 
  839. * @param WP_REST_Response $response Response data. 
  840. */ 
  841. return apply_filters( 'rest_index', $response ); 
  842.  
  843. /** 
  844. * Retrieves the index for a namespace. 
  845. * @since 4.4.0 
  846. * @access public 
  847. * @param WP_REST_Request $request REST request instance. 
  848. * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,  
  849. * WP_Error if the namespace isn't set. 
  850. */ 
  851. public function get_namespace_index( $request ) { 
  852. $namespace = $request['namespace']; 
  853.  
  854. if ( ! isset( $this->namespaces[ $namespace ] ) ) { 
  855. return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) ); 
  856.  
  857. $routes = $this->namespaces[ $namespace ]; 
  858. $endpoints = array_intersect_key( $this->get_routes(), $routes ); 
  859.  
  860. $data = array( 
  861. 'namespace' => $namespace,  
  862. 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ),  
  863. ); 
  864. $response = rest_ensure_response( $data ); 
  865.  
  866. // Link to the root index. 
  867. $response->add_link( 'up', rest_url( '/' ) ); 
  868.  
  869. /** 
  870. * Filters the namespace index data. 
  871. * This typically is just the route data for the namespace, but you can 
  872. * add any data you'd like here. 
  873. * @since 4.4.0 
  874. * @param WP_REST_Response $response Response data. 
  875. * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. 
  876. */ 
  877. return apply_filters( 'rest_namespace_index', $response, $request ); 
  878.  
  879. /** 
  880. * Retrieves the publicly-visible data for routes. 
  881. * @since 4.4.0 
  882. * @access public 
  883. * @param array $routes Routes to get data for. 
  884. * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'. 
  885. * @return array Route data to expose in indexes. 
  886. */ 
  887. public function get_data_for_routes( $routes, $context = 'view' ) { 
  888. $available = array(); 
  889.  
  890. // Find the available routes. 
  891. foreach ( $routes as $route => $callbacks ) { 
  892. $data = $this->get_data_for_route( $route, $callbacks, $context ); 
  893. if ( empty( $data ) ) { 
  894. continue; 
  895.  
  896. /** 
  897. * Filters the REST endpoint data. 
  898. * @since 4.4.0 
  899. * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. 
  900. */ 
  901. $available[ $route ] = apply_filters( 'rest_endpoints_description', $data ); 
  902.  
  903. /** 
  904. * Filters the publicly-visible data for routes. 
  905. * This data is exposed on indexes and can be used by clients or 
  906. * developers to investigate the site and find out how to use it. It 
  907. * acts as a form of self-documentation. 
  908. * @since 4.4.0 
  909. * @param array $available Map of route to route data. 
  910. * @param array $routes Internal route data as an associative array. 
  911. */ 
  912. return apply_filters( 'rest_route_data', $available, $routes ); 
  913.  
  914. /** 
  915. * Retrieves publicly-visible data for the route. 
  916. * @since 4.4.0 
  917. * @access public 
  918. * @param string $route Route to get data for. 
  919. * @param array $callbacks Callbacks to convert to data. 
  920. * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'. 
  921. * @return array|null Data for the route, or null if no publicly-visible data. 
  922. */ 
  923. public function get_data_for_route( $route, $callbacks, $context = 'view' ) { 
  924. $data = array( 
  925. 'namespace' => '',  
  926. 'methods' => array(),  
  927. 'endpoints' => array(),  
  928. ); 
  929.  
  930. if ( isset( $this->route_options[ $route ] ) ) { 
  931. $options = $this->route_options[ $route ]; 
  932.  
  933. if ( isset( $options['namespace'] ) ) { 
  934. $data['namespace'] = $options['namespace']; 
  935.  
  936. if ( isset( $options['schema'] ) && 'help' === $context ) { 
  937. $data['schema'] = call_user_func( $options['schema'] ); 
  938.  
  939. $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route ); 
  940.  
  941. foreach ( $callbacks as $callback ) { 
  942. // Skip to the next route if any callback is hidden. 
  943. if ( empty( $callback['show_in_index'] ) ) { 
  944. continue; 
  945.  
  946. $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) ); 
  947. $endpoint_data = array( 
  948. 'methods' => array_keys( $callback['methods'] ),  
  949. ); 
  950.  
  951. if ( isset( $callback['args'] ) ) { 
  952. $endpoint_data['args'] = array(); 
  953. foreach ( $callback['args'] as $key => $opts ) { 
  954. $arg_data = array( 
  955. 'required' => ! empty( $opts['required'] ),  
  956. ); 
  957. if ( isset( $opts['default'] ) ) { 
  958. $arg_data['default'] = $opts['default']; 
  959. if ( isset( $opts['enum'] ) ) { 
  960. $arg_data['enum'] = $opts['enum']; 
  961. if ( isset( $opts['description'] ) ) { 
  962. $arg_data['description'] = $opts['description']; 
  963. if ( isset( $opts['type'] ) ) { 
  964. $arg_data['type'] = $opts['type']; 
  965. if ( isset( $opts['items'] ) ) { 
  966. $arg_data['items'] = $opts['items']; 
  967. $endpoint_data['args'][ $key ] = $arg_data; 
  968.  
  969. $data['endpoints'][] = $endpoint_data; 
  970.  
  971. // For non-variable routes, generate links. 
  972. if ( strpos( $route, '{' ) === false ) { 
  973. $data['_links'] = array( 
  974. 'self' => rest_url( $route ),  
  975. ); 
  976.  
  977. if ( empty( $data['methods'] ) ) { 
  978. // No methods supported, hide the route. 
  979. return null; 
  980.  
  981. return $data; 
  982.  
  983. /** 
  984. * Sends an HTTP status code. 
  985. * @since 4.4.0 
  986. * @access protected 
  987. * @param int $code HTTP status. 
  988. */ 
  989. protected function set_status( $code ) { 
  990. status_header( $code ); 
  991.  
  992. /** 
  993. * Sends an HTTP header. 
  994. * @since 4.4.0 
  995. * @access public 
  996. * @param string $key Header key. 
  997. * @param string $value Header value. 
  998. */ 
  999. public function send_header( $key, $value ) { 
  1000. /** 
  1001. * Sanitize as per RFC2616 (Section 4.2): 
  1002. * Any LWS that occurs between field-content MAY be replaced with a 
  1003. * single SP before interpreting the field value or forwarding the 
  1004. * message downstream. 
  1005. */ 
  1006. $value = preg_replace( '/\s+/', ' ', $value ); 
  1007. header( sprintf( '%s: %s', $key, $value ) ); 
  1008.  
  1009. /** 
  1010. * Sends multiple HTTP headers. 
  1011. * @since 4.4.0 
  1012. * @access public 
  1013. * @param array $headers Map of header name to header value. 
  1014. */ 
  1015. public function send_headers( $headers ) { 
  1016. foreach ( $headers as $key => $value ) { 
  1017. $this->send_header( $key, $value ); 
  1018.  
  1019. /** 
  1020. * Retrieves the raw request entity (body). 
  1021. * @since 4.4.0 
  1022. * @access public 
  1023. * @global string $HTTP_RAW_POST_DATA Raw post data. 
  1024. * @return string Raw request data. 
  1025. */ 
  1026. public static function get_raw_data() { 
  1027. global $HTTP_RAW_POST_DATA; 
  1028.  
  1029. /** 
  1030. * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,  
  1031. * but we can do it ourself. 
  1032. */ 
  1033. if ( ! isset( $HTTP_RAW_POST_DATA ) ) { 
  1034. $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); 
  1035.  
  1036. return $HTTP_RAW_POST_DATA; 
  1037.  
  1038. /** 
  1039. * Extracts headers from a PHP-style $_SERVER array. 
  1040. * @since 4.4.0 
  1041. * @access public 
  1042. * @param array $server Associative array similar to `$_SERVER`. 
  1043. * @return array Headers extracted from the input. 
  1044. */ 
  1045. public function get_headers( $server ) { 
  1046. $headers = array(); 
  1047.  
  1048. // CONTENT_* headers are not prefixed with HTTP_. 
  1049. $additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true ); 
  1050.  
  1051. foreach ( $server as $key => $value ) { 
  1052. if ( strpos( $key, 'HTTP_' ) === 0 ) { 
  1053. $headers[ substr( $key, 5 ) ] = $value; 
  1054. } elseif ( isset( $additional[ $key ] ) ) { 
  1055. $headers[ $key ] = $value; 
  1056.  
  1057. return $headers;