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. * Pass an authentication error to the API 
  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' ); 
  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. * @param bool $rest_enabled Whether the REST API is enabled. Default true. 
  208. */ 
  209. $enabled = apply_filters( 'rest_enabled', true ); 
  210.  
  211. /** 
  212. * Filters whether jsonp is enabled. 
  213. * @since 4.4.0 
  214. * @param bool $jsonp_enabled Whether jsonp is enabled. Default true. 
  215. */ 
  216. $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true ); 
  217.  
  218. $jsonp_callback = null; 
  219.  
  220. if ( ! $enabled ) { 
  221. echo $this->json_error( 'rest_disabled', __( 'The REST API is disabled on this site.' ), 404 ); 
  222. return false; 
  223. if ( isset( $_GET['_jsonp'] ) ) { 
  224. if ( ! $jsonp_enabled ) { 
  225. echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 ); 
  226. return false; 
  227.  
  228. $jsonp_callback = $_GET['_jsonp']; 
  229. if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) { 
  230. echo $this->json_error( 'rest_callback_invalid', __( 'The JSONP callback function is invalid.' ), 400 ); 
  231. return false; 
  232.  
  233. if ( empty( $path ) ) { 
  234. if ( isset( $_SERVER['PATH_INFO'] ) ) { 
  235. $path = $_SERVER['PATH_INFO']; 
  236. } else { 
  237. $path = '/'; 
  238.  
  239. $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path ); 
  240.  
  241. $request->set_query_params( wp_unslash( $_GET ) ); 
  242. $request->set_body_params( wp_unslash( $_POST ) ); 
  243. $request->set_file_params( $_FILES ); 
  244. $request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) ); 
  245. $request->set_body( $this->get_raw_data() ); 
  246.  
  247. /** 
  248. * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check 
  249. * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE 
  250. * header. 
  251. */ 
  252. if ( isset( $_GET['_method'] ) ) { 
  253. $request->set_method( $_GET['_method'] ); 
  254. } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) { 
  255. $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ); 
  256.  
  257. $result = $this->check_authentication(); 
  258.  
  259. if ( ! is_wp_error( $result ) ) { 
  260. $result = $this->dispatch( $request ); 
  261.  
  262. // Normalize to either WP_Error or WP_REST_Response... 
  263. $result = rest_ensure_response( $result ); 
  264.  
  265. // ...then convert WP_Error across. 
  266. if ( is_wp_error( $result ) ) { 
  267. $result = $this->error_to_response( $result ); 
  268.  
  269. /** 
  270. * Filters the API response. 
  271. * Allows modification of the response before returning. 
  272. * @since 4.4.0 
  273. * @since 4.5.0 Applied to embedded responses. 
  274. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. 
  275. * @param WP_REST_Server $this Server instance. 
  276. * @param WP_REST_Request $request Request used to generate the response. 
  277. */ 
  278. $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request ); 
  279.  
  280. // Wrap the response in an envelope if asked for. 
  281. if ( isset( $_GET['_envelope'] ) ) { 
  282. $result = $this->envelope_response( $result, isset( $_GET['_embed'] ) ); 
  283.  
  284. // Send extra data from response objects. 
  285. $headers = $result->get_headers(); 
  286. $this->send_headers( $headers ); 
  287.  
  288. $code = $result->get_status(); 
  289. $this->set_status( $code ); 
  290.  
  291. /** 
  292. * Filters whether the request has already been served. 
  293. * Allow sending the request manually - by returning true, the API result 
  294. * will not be sent to the client. 
  295. * @since 4.4.0 
  296. * @param bool $served Whether the request has already been served. 
  297. * Default false. 
  298. * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. 
  299. * @param WP_REST_Request $request Request used to generate the response. 
  300. * @param WP_REST_Server $this Server instance. 
  301. */ 
  302. $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this ); 
  303.  
  304. if ( ! $served ) { 
  305. if ( 'HEAD' === $request->get_method() ) { 
  306. return null; 
  307.  
  308. // Embed links inside the request. 
  309. $result = $this->response_to_data( $result, isset( $_GET['_embed'] ) ); 
  310.  
  311. $result = wp_json_encode( $result ); 
  312.  
  313. $json_error_message = $this->get_json_last_error(); 
  314. if ( $json_error_message ) { 
  315. $json_error_obj = new WP_Error( 'rest_encode_error', $json_error_message, array( 'status' => 500 ) ); 
  316. $result = $this->error_to_response( $json_error_obj ); 
  317. $result = wp_json_encode( $result->data[0] ); 
  318.  
  319. if ( $jsonp_callback ) { 
  320. // Prepend '/**/' to mitigate possible JSONP Flash attacks 
  321. // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ 
  322. echo '/**/' . $jsonp_callback . '(' . $result . ')'; 
  323. } else { 
  324. echo $result; 
  325. return null; 
  326.  
  327. /** 
  328. * Converts a response to data to send. 
  329. * @since 4.4.0 
  330. * @access public 
  331. * @param WP_REST_Response $response Response object. 
  332. * @param bool $embed Whether links should be embedded. 
  333. * @return array { 
  334. * Data with sub-requests embedded. 
  335. * @type array [$_links] Links. 
  336. * @type array [$_embedded] Embeddeds. 
  337. * } 
  338. */ 
  339. public function response_to_data( $response, $embed ) { 
  340. $data = $response->get_data(); 
  341. $links = $this->get_compact_response_links( $response ); 
  342.  
  343. if ( ! empty( $links ) ) { 
  344. // Convert links to part of the data. 
  345. $data['_links'] = $links; 
  346. if ( $embed ) { 
  347. // Determine if this is a numeric array. 
  348. if ( wp_is_numeric_array( $data ) ) { 
  349. $data = array_map( array( $this, 'embed_links' ), $data ); 
  350. } else { 
  351. $data = $this->embed_links( $data ); 
  352.  
  353. return $data; 
  354.  
  355. /** 
  356. * Retrieves links from a response. 
  357. * Extracts the links from a response into a structured hash, suitable for 
  358. * direct output. 
  359. * @since 4.4.0 
  360. * @access public 
  361. * @static 
  362. * @param WP_REST_Response $response Response to extract links from. 
  363. * @return array Map of link relation to list of link hashes. 
  364. */ 
  365. public static function get_response_links( $response ) { 
  366. $links = $response->get_links(); 
  367. if ( empty( $links ) ) { 
  368. return array(); 
  369.  
  370. // Convert links to part of the data. 
  371. $data = array(); 
  372. foreach ( $links as $rel => $items ) { 
  373. $data[ $rel ] = array(); 
  374.  
  375. foreach ( $items as $item ) { 
  376. $attributes = $item['attributes']; 
  377. $attributes['href'] = $item['href']; 
  378. $data[ $rel ][] = $attributes; 
  379.  
  380. return $data; 
  381.  
  382. /** 
  383. * Retrieves the CURIEs (compact URIs) used for relations. 
  384. * Extracts the links from a response into a structured hash, suitable for 
  385. * direct output. 
  386. * @since 4.5.0 
  387. * @access public 
  388. * @static 
  389. * @param WP_REST_Response $response Response to extract links from. 
  390. * @return array Map of link relation to list of link hashes. 
  391. */ 
  392. public static function get_compact_response_links( $response ) { 
  393. $links = self::get_response_links( $response ); 
  394.  
  395. if ( empty( $links ) ) { 
  396. return array(); 
  397.  
  398. $curies = $response->get_curies(); 
  399. $used_curies = array(); 
  400.  
  401. foreach ( $links as $rel => $items ) { 
  402.  
  403. // Convert $rel URIs to their compact versions if they exist. 
  404. foreach ( $curies as $curie ) { 
  405. $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) ); 
  406. if ( strpos( $rel, $href_prefix ) !== 0 ) { 
  407. continue; 
  408.  
  409. // Relation now changes from '$uri' to '$curie:$relation' 
  410. $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) ); 
  411. preg_match( '!' . $rel_regex . '!', $rel, $matches ); 
  412. if ( $matches ) { 
  413. $new_rel = $curie['name'] . ':' . $matches[1]; 
  414. $used_curies[ $curie['name'] ] = $curie; 
  415. $links[ $new_rel ] = $items; 
  416. unset( $links[ $rel ] ); 
  417. break; 
  418.  
  419. // Push the curies onto the start of the links array. 
  420. if ( $used_curies ) { 
  421. $links['curies'] = array_values( $used_curies ); 
  422.  
  423. return $links; 
  424.  
  425. /** 
  426. * Embeds the links from the data into the request. 
  427. * @since 4.4.0 
  428. * @access protected 
  429. * @param array $data Data from the request. 
  430. * @return array { 
  431. * Data with sub-requests embedded. 
  432. * @type array [$_links] Links. 
  433. * @type array [$_embedded] Embeddeds. 
  434. * } 
  435. */ 
  436. protected function embed_links( $data ) { 
  437. if ( empty( $data['_links'] ) ) { 
  438. return $data; 
  439.  
  440. $embedded = array(); 
  441.  
  442. foreach ( $data['_links'] as $rel => $links ) { 
  443. // Ignore links to self, for obvious reasons. 
  444. if ( 'self' === $rel ) { 
  445. continue; 
  446.  
  447. $embeds = array(); 
  448.  
  449. foreach ( $links as $item ) { 
  450. // Determine if the link is embeddable. 
  451. if ( empty( $item['embeddable'] ) ) { 
  452. // Ensure we keep the same order. 
  453. $embeds[] = array(); 
  454. continue; 
  455.  
  456. // Run through our internal routing and serve. 
  457. $request = WP_REST_Request::from_url( $item['href'] ); 
  458. if ( ! $request ) { 
  459. $embeds[] = array(); 
  460. continue; 
  461.  
  462. // Embedded resources get passed context=embed. 
  463. if ( empty( $request['context'] ) ) { 
  464. $request['context'] = 'embed'; 
  465.  
  466. $response = $this->dispatch( $request ); 
  467.  
  468. /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */ 
  469. $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request ); 
  470.  
  471. $embeds[] = $this->response_to_data( $response, false ); 
  472.  
  473. // Determine if any real links were found. 
  474. $has_links = count( array_filter( $embeds ) ); 
  475. if ( $has_links ) { 
  476. $embedded[ $rel ] = $embeds; 
  477.  
  478. if ( ! empty( $embedded ) ) { 
  479. $data['_embedded'] = $embedded; 
  480.  
  481. return $data; 
  482.  
  483. /** 
  484. * Wraps the response in an envelope. 
  485. * The enveloping technique is used to work around browser/client 
  486. * compatibility issues. Essentially, it converts the full HTTP response to 
  487. * data instead. 
  488. * @since 4.4.0 
  489. * @access public 
  490. * @param WP_REST_Response $response Response object. 
  491. * @param bool $embed Whether links should be embedded. 
  492. * @return WP_REST_Response New response with wrapped data 
  493. */ 
  494. public function envelope_response( $response, $embed ) { 
  495. $envelope = array( 
  496. 'body' => $this->response_to_data( $response, $embed ),  
  497. 'status' => $response->get_status(),  
  498. 'headers' => $response->get_headers(),  
  499. ); 
  500.  
  501. /** 
  502. * Filters the enveloped form of a response. 
  503. * @since 4.4.0 
  504. * @param array $envelope Envelope data. 
  505. * @param WP_REST_Response $response Original response data. 
  506. */ 
  507. $envelope = apply_filters( 'rest_envelope_response', $envelope, $response ); 
  508.  
  509. // Ensure it's still a response and return. 
  510. return rest_ensure_response( $envelope ); 
  511.  
  512. /** 
  513. * Registers a route to the server. 
  514. * @since 4.4.0 
  515. * @access public 
  516. * @param string $namespace Namespace. 
  517. * @param string $route The REST route. 
  518. * @param array $route_args Route arguments. 
  519. * @param bool $override Optional. Whether the route should be overriden if it already exists. 
  520. * Default false. 
  521. */ 
  522. public function register_route( $namespace, $route, $route_args, $override = false ) { 
  523. if ( ! isset( $this->namespaces[ $namespace ] ) ) { 
  524. $this->namespaces[ $namespace ] = array(); 
  525.  
  526. $this->register_route( $namespace, '/' . $namespace, array( 
  527. array( 
  528. 'methods' => self::READABLE,  
  529. 'callback' => array( $this, 'get_namespace_index' ),  
  530. 'args' => array( 
  531. 'namespace' => array( 
  532. 'default' => $namespace,  
  533. ),  
  534. 'context' => array( 
  535. 'default' => 'view',  
  536. ),  
  537. ),  
  538. ),  
  539. ) ); 
  540.  
  541. // Associative to avoid double-registration. 
  542. $this->namespaces[ $namespace ][ $route ] = true; 
  543. $route_args['namespace'] = $namespace; 
  544.  
  545. if ( $override || empty( $this->endpoints[ $route ] ) ) { 
  546. $this->endpoints[ $route ] = $route_args; 
  547. } else { 
  548. $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args ); 
  549.  
  550. /** 
  551. * Retrieves the route map. 
  552. * The route map is an associative array with path regexes as the keys. The 
  553. * value is an indexed array with the callback function/method as the first 
  554. * item, and a bitmask of HTTP methods as the second item (see the class 
  555. * constants). 
  556. * Each route can be mapped to more than one callback by using an array of 
  557. * the indexed arrays. This allows mapping e.g. GET requests to one callback 
  558. * and POST requests to another. 
  559. * Note that the path regexes (array keys) must have @ escaped, as this is 
  560. * used as the delimiter with preg_match() 
  561. * @since 4.4.0 
  562. * @access public 
  563. * @return array `'/path/regex' => array( $callback, $bitmask )` or 
  564. * `'/path/regex' => array( array( $callback, $bitmask ), ...)`. 
  565. */ 
  566. public function get_routes() { 
  567.  
  568. /** 
  569. * Filters the array of available endpoints. 
  570. * @since 4.4.0 
  571. * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped 
  572. * to an array of callbacks for the endpoint. These take the format 
  573. * `'/path/regex' => array( $callback, $bitmask )` or 
  574. * `'/path/regex' => array( array( $callback, $bitmask ). 
  575. */ 
  576. $endpoints = apply_filters( 'rest_endpoints', $this->endpoints ); 
  577.  
  578. // Normalise the endpoints. 
  579. $defaults = array( 
  580. 'methods' => '',  
  581. 'accept_json' => false,  
  582. 'accept_raw' => false,  
  583. 'show_in_index' => true,  
  584. 'args' => array(),  
  585. ); 
  586.  
  587. foreach ( $endpoints as $route => &$handlers ) { 
  588.  
  589. if ( isset( $handlers['callback'] ) ) { 
  590. // Single endpoint, add one deeper. 
  591. $handlers = array( $handlers ); 
  592.  
  593. if ( ! isset( $this->route_options[ $route ] ) ) { 
  594. $this->route_options[ $route ] = array(); 
  595.  
  596. foreach ( $handlers as $key => &$handler ) { 
  597.  
  598. if ( ! is_numeric( $key ) ) { 
  599. // Route option, move it to the options. 
  600. $this->route_options[ $route ][ $key ] = $handler; 
  601. unset( $handlers[ $key ] ); 
  602. continue; 
  603.  
  604. $handler = wp_parse_args( $handler, $defaults ); 
  605.  
  606. // Allow comma-separated HTTP methods. 
  607. if ( is_string( $handler['methods'] ) ) { 
  608. $methods = explode( ', ', $handler['methods'] ); 
  609. } else if ( is_array( $handler['methods'] ) ) { 
  610. $methods = $handler['methods']; 
  611. } else { 
  612. $methods = array(); 
  613.  
  614. $handler['methods'] = array(); 
  615.  
  616. foreach ( $methods as $method ) { 
  617. $method = strtoupper( trim( $method ) ); 
  618. $handler['methods'][ $method ] = true; 
  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.  
  708. $request->sanitize_params(); 
  709.  
  710. if ( ! is_wp_error( $response ) ) { 
  711. // Check permission specified on the route. 
  712. if ( ! empty( $handler['permission_callback'] ) ) { 
  713. $permission = call_user_func( $handler['permission_callback'], $request ); 
  714.  
  715. if ( is_wp_error( $permission ) ) { 
  716. $response = $permission; 
  717. } else if ( false === $permission || null === $permission ) { 
  718. $response = new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to do that.' ), array( 'status' => 403 ) ); 
  719.  
  720. if ( ! is_wp_error( $response ) ) { 
  721. /** 
  722. * Filters the REST dispatch request result. 
  723. * Allow plugins to override dispatching the request. 
  724. * @since 4.4.0 
  725. * @since 4.5.0 Added `$route` and `$handler` parameters. 
  726. * @param bool $dispatch_result Dispatch result, will be used if not empty. 
  727. * @param WP_REST_Request $request Request used to generate the response. 
  728. * @param string $route Route matched for the request. 
  729. * @param array $handler Route handler used for the request. 
  730. */ 
  731. $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler ); 
  732.  
  733. // Allow plugins to halt the request via this filter. 
  734. if ( null !== $dispatch_result ) { 
  735. $response = $dispatch_result; 
  736. } else { 
  737. $response = call_user_func( $callback, $request ); 
  738.  
  739. if ( is_wp_error( $response ) ) { 
  740. $response = $this->error_to_response( $response ); 
  741. } else { 
  742. $response = rest_ensure_response( $response ); 
  743.  
  744. $response->set_matched_route( $route ); 
  745. $response->set_matched_handler( $handler ); 
  746.  
  747. return $response; 
  748.  
  749. return $this->error_to_response( new WP_Error( 'rest_no_route', __( 'No route was found matching the URL and request method' ), array( 'status' => 404 ) ) ); 
  750.  
  751. /** 
  752. * Returns if an error occurred during most recent JSON encode/decode. 
  753. * Strings to be translated will be in format like 
  754. * "Encoding error: Maximum stack depth exceeded". 
  755. * @since 4.4.0 
  756. * @access protected 
  757. * @return bool|string Boolean false or string error message. 
  758. */ 
  759. protected function get_json_last_error() { 
  760. // See https://core.trac.wordpress.org/ticket/27799. 
  761. if ( ! function_exists( 'json_last_error' ) ) { 
  762. return false; 
  763.  
  764. $last_error_code = json_last_error(); 
  765.  
  766. if ( ( defined( 'JSON_ERROR_NONE' ) && JSON_ERROR_NONE === $last_error_code ) || empty( $last_error_code ) ) { 
  767. return false; 
  768.  
  769. return json_last_error_msg(); 
  770.  
  771. /** 
  772. * Retrieves the site index. 
  773. * This endpoint describes the capabilities of the site. 
  774. * @since 4.4.0 
  775. * @access public 
  776. * @param array $request { 
  777. * Request. 
  778. * @type string $context Context. 
  779. * } 
  780. * @return array Index entity 
  781. */ 
  782. public function get_index( $request ) { 
  783. // General site data. 
  784. $available = array( 
  785. 'name' => get_option( 'blogname' ),  
  786. 'description' => get_option( 'blogdescription' ),  
  787. 'url' => get_option( 'siteurl' ),  
  788. 'home' => home_url(),  
  789. 'namespaces' => array_keys( $this->namespaces ),  
  790. 'authentication' => array(),  
  791. 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),  
  792. ); 
  793.  
  794. $response = new WP_REST_Response( $available ); 
  795.  
  796. $response->add_link( 'help', 'http://v2.wp-api.org/' ); 
  797.  
  798. /** 
  799. * Filters the API root index data. 
  800. * This contains the data describing the API. This includes information 
  801. * about supported authentication schemes, supported namespaces, routes 
  802. * available on the API, and a small amount of data about the site. 
  803. * @since 4.4.0 
  804. * @param WP_REST_Response $response Response data. 
  805. */ 
  806. return apply_filters( 'rest_index', $response ); 
  807.  
  808. /** 
  809. * Retrieves the index for a namespace. 
  810. * @since 4.4.0 
  811. * @access public 
  812. * @param WP_REST_Request $request REST request instance. 
  813. * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,  
  814. * WP_Error if the namespace isn't set. 
  815. */ 
  816. public function get_namespace_index( $request ) { 
  817. $namespace = $request['namespace']; 
  818.  
  819. if ( ! isset( $this->namespaces[ $namespace ] ) ) { 
  820. return new WP_Error( 'rest_invalid_namespace', __( 'The specified namespace could not be found.' ), array( 'status' => 404 ) ); 
  821.  
  822. $routes = $this->namespaces[ $namespace ]; 
  823. $endpoints = array_intersect_key( $this->get_routes(), $routes ); 
  824.  
  825. $data = array( 
  826. 'namespace' => $namespace,  
  827. 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ),  
  828. ); 
  829. $response = rest_ensure_response( $data ); 
  830.  
  831. // Link to the root index. 
  832. $response->add_link( 'up', rest_url( '/' ) ); 
  833.  
  834. /** 
  835. * Filters the namespace index data. 
  836. * This typically is just the route data for the namespace, but you can 
  837. * add any data you'd like here. 
  838. * @since 4.4.0 
  839. * @param WP_REST_Response $response Response data. 
  840. * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. 
  841. */ 
  842. return apply_filters( 'rest_namespace_index', $response, $request ); 
  843.  
  844. /** 
  845. * Retrieves the publicly-visible data for routes. 
  846. * @since 4.4.0 
  847. * @access public 
  848. * @param array $routes Routes to get data for. 
  849. * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'. 
  850. * @return array Route data to expose in indexes. 
  851. */ 
  852. public function get_data_for_routes( $routes, $context = 'view' ) { 
  853. $available = array(); 
  854.  
  855. // Find the available routes. 
  856. foreach ( $routes as $route => $callbacks ) { 
  857. $data = $this->get_data_for_route( $route, $callbacks, $context ); 
  858. if ( empty( $data ) ) { 
  859. continue; 
  860.  
  861. /** 
  862. * Filters the REST endpoint data. 
  863. * @since 4.4.0 
  864. * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter. 
  865. */ 
  866. $available[ $route ] = apply_filters( 'rest_endpoints_description', $data ); 
  867.  
  868. /** 
  869. * Filters the publicly-visible data for routes. 
  870. * This data is exposed on indexes and can be used by clients or 
  871. * developers to investigate the site and find out how to use it. It 
  872. * acts as a form of self-documentation. 
  873. * @since 4.4.0 
  874. * @param array $available Map of route to route data. 
  875. * @param array $routes Internal route data as an associative array. 
  876. */ 
  877. return apply_filters( 'rest_route_data', $available, $routes ); 
  878.  
  879. /** 
  880. * Retrieves publicly-visible data for the route. 
  881. * @since 4.4.0 
  882. * @access public 
  883. * @param string $route Route to get data for. 
  884. * @param array $callbacks Callbacks to convert to data. 
  885. * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'. 
  886. * @return array|null Data for the route, or null if no publicly-visible data. 
  887. */ 
  888. public function get_data_for_route( $route, $callbacks, $context = 'view' ) { 
  889. $data = array( 
  890. 'namespace' => '',  
  891. 'methods' => array(),  
  892. 'endpoints' => array(),  
  893. ); 
  894.  
  895. if ( isset( $this->route_options[ $route ] ) ) { 
  896. $options = $this->route_options[ $route ]; 
  897.  
  898. if ( isset( $options['namespace'] ) ) { 
  899. $data['namespace'] = $options['namespace']; 
  900.  
  901. if ( isset( $options['schema'] ) && 'help' === $context ) { 
  902. $data['schema'] = call_user_func( $options['schema'] ); 
  903.  
  904. $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route ); 
  905.  
  906. foreach ( $callbacks as $callback ) { 
  907. // Skip to the next route if any callback is hidden. 
  908. if ( empty( $callback['show_in_index'] ) ) { 
  909. continue; 
  910.  
  911. $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) ); 
  912. $endpoint_data = array( 
  913. 'methods' => array_keys( $callback['methods'] ),  
  914. ); 
  915.  
  916. if ( isset( $callback['args'] ) ) { 
  917. $endpoint_data['args'] = array(); 
  918. foreach ( $callback['args'] as $key => $opts ) { 
  919. $arg_data = array( 
  920. 'required' => ! empty( $opts['required'] ),  
  921. ); 
  922. if ( isset( $opts['default'] ) ) { 
  923. $arg_data['default'] = $opts['default']; 
  924. if ( isset( $opts['enum'] ) ) { 
  925. $arg_data['enum'] = $opts['enum']; 
  926. if ( isset( $opts['description'] ) ) { 
  927. $arg_data['description'] = $opts['description']; 
  928. $endpoint_data['args'][ $key ] = $arg_data; 
  929.  
  930. $data['endpoints'][] = $endpoint_data; 
  931.  
  932. // For non-variable routes, generate links. 
  933. if ( strpos( $route, '{' ) === false ) { 
  934. $data['_links'] = array( 
  935. 'self' => rest_url( $route ),  
  936. ); 
  937.  
  938. if ( empty( $data['methods'] ) ) { 
  939. // No methods supported, hide the route. 
  940. return null; 
  941.  
  942. return $data; 
  943.  
  944. /** 
  945. * Sends an HTTP status code. 
  946. * @since 4.4.0 
  947. * @access protected 
  948. * @param int $code HTTP status. 
  949. */ 
  950. protected function set_status( $code ) { 
  951. status_header( $code ); 
  952.  
  953. /** 
  954. * Sends an HTTP header. 
  955. * @since 4.4.0 
  956. * @access public 
  957. * @param string $key Header key. 
  958. * @param string $value Header value. 
  959. */ 
  960. public function send_header( $key, $value ) { 
  961. /** 
  962. * Sanitize as per RFC2616 (Section 4.2): 
  963. * Any LWS that occurs between field-content MAY be replaced with a 
  964. * single SP before interpreting the field value or forwarding the 
  965. * message downstream. 
  966. */ 
  967. $value = preg_replace( '/\s+/', ' ', $value ); 
  968. header( sprintf( '%s: %s', $key, $value ) ); 
  969.  
  970. /** 
  971. * Sends multiple HTTP headers. 
  972. * @since 4.4.0 
  973. * @access public 
  974. * @param array $headers Map of header name to header value. 
  975. */ 
  976. public function send_headers( $headers ) { 
  977. foreach ( $headers as $key => $value ) { 
  978. $this->send_header( $key, $value ); 
  979.  
  980. /** 
  981. * Retrieves the raw request entity (body). 
  982. * @since 4.4.0 
  983. * @access public 
  984. * @global string $HTTP_RAW_POST_DATA Raw post data. 
  985. * @return string Raw request data. 
  986. */ 
  987. public static function get_raw_data() { 
  988. global $HTTP_RAW_POST_DATA; 
  989.  
  990. /** 
  991. * A bug in PHP < 5.2.2 makes $HTTP_RAW_POST_DATA not set by default,  
  992. * but we can do it ourself. 
  993. */ 
  994. if ( ! isset( $HTTP_RAW_POST_DATA ) ) { 
  995. $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' ); 
  996.  
  997. return $HTTP_RAW_POST_DATA; 
  998.  
  999. /** 
  1000. * Extracts headers from a PHP-style $_SERVER array. 
  1001. * @since 4.4.0 
  1002. * @access public 
  1003. * @param array $server Associative array similar to `$_SERVER`. 
  1004. * @return array Headers extracted from the input. 
  1005. */ 
  1006. public function get_headers( $server ) { 
  1007. $headers = array(); 
  1008.  
  1009. // CONTENT_* headers are not prefixed with HTTP_. 
  1010. $additional = array( 'CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true ); 
  1011.  
  1012. foreach ( $server as $key => $value ) { 
  1013. if ( strpos( $key, 'HTTP_' ) === 0 ) { 
  1014. $headers[ substr( $key, 5 ) ] = $value; 
  1015. } elseif ( isset( $additional[ $key ] ) ) { 
  1016. $headers[ $key ] = $value; 
  1017.  
  1018. return $headers;