/wp-includes/rest-api/class-wp-rest-server.php

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