/wp-includes/rest-api.php

  1. <?php 
  2. /** 
  3. * REST API functions. 
  4. * 
  5. * @package WordPress 
  6. * @subpackage REST_API 
  7. * @since 4.4.0 
  8. */ 
  9.  
  10. /** 
  11. * Version number for our API. 
  12. * 
  13. * @var string 
  14. */ 
  15. define( 'REST_API_VERSION', '2.0' ); 
  16.  
  17. /** 
  18. * Registers a REST API route. 
  19. * 
  20. * @since 4.4.0 
  21. * 
  22. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server). 
  23. * 
  24. * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin. 
  25. * @param string $route The base URL for route you are adding. 
  26. * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for 
  27. * multiple methods. Default empty array. 
  28. * @param bool $override Optional. If the route already exists, should we override it? True overrides,  
  29. * false merges (with newer overriding if duplicate keys exist). Default false. 
  30. * @return bool True on success, false on error. 
  31. */ 
  32. function register_rest_route( $namespace, $route, $args = array(), $override = false ) { 
  33. /** @var WP_REST_Server $wp_rest_server */ 
  34. global $wp_rest_server; 
  35.  
  36. if ( empty( $namespace ) ) { 
  37. /** 
  38. * Non-namespaced routes are not allowed, with the exception of the main 
  39. * and namespace indexes. If you really need to register a 
  40. * non-namespaced route, call `WP_REST_Server::register_route` directly. 
  41. */ 
  42. _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' ); 
  43. return false; 
  44. } else if ( empty( $route ) ) { 
  45. _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' ); 
  46. return false; 
  47.  
  48. if ( isset( $args['callback'] ) ) { 
  49. // Upgrade a single set to multiple. 
  50. $args = array( $args ); 
  51.  
  52. $defaults = array( 
  53. 'methods' => 'GET',  
  54. 'callback' => null,  
  55. 'args' => array(),  
  56. ); 
  57. foreach ( $args as $key => &$arg_group ) { 
  58. if ( ! is_numeric( $arg_group ) ) { 
  59. // Route option, skip here. 
  60. continue; 
  61.  
  62. $arg_group = array_merge( $defaults, $arg_group ); 
  63.  
  64. $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' ); 
  65. $wp_rest_server->register_route( $namespace, $full_route, $args, $override ); 
  66. return true; 
  67.  
  68. /** 
  69. * Registers a new field on an existing WordPress object type. 
  70. * 
  71. * @since 4.7.0 
  72. * 
  73. * @global array $wp_rest_additional_fields Holds registered fields, organized 
  74. * by object type. 
  75. * 
  76. * @param string|array $object_type Object(s) the field is being registered 
  77. * to, "post"|"term"|"comment" etc. 
  78. * @param string $attribute The attribute name. 
  79. * @param array $args { 
  80. * Optional. An array of arguments used to handle the registered field. 
  81. * 
  82. * @type string|array|null $get_callback Optional. The callback function used to retrieve the field 
  83. * value. Default is 'null', the field will not be returned in 
  84. * the response. 
  85. * @type string|array|null $update_callback Optional. The callback function used to set and update the 
  86. * field value. Default is 'null', the value cannot be set or 
  87. * updated. 
  88. * @type string|array|null $schema Optional. The callback function used to create the schema for 
  89. * this field. Default is 'null', no schema entry will be returned. 
  90. * } 
  91. */ 
  92. function register_rest_field( $object_type, $attribute, $args = array() ) { 
  93. $defaults = array( 
  94. 'get_callback' => null,  
  95. 'update_callback' => null,  
  96. 'schema' => null,  
  97. ); 
  98.  
  99. $args = wp_parse_args( $args, $defaults ); 
  100.  
  101. global $wp_rest_additional_fields; 
  102.  
  103. $object_types = (array) $object_type; 
  104.  
  105. foreach ( $object_types as $object_type ) { 
  106. $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args; 
  107.  
  108. /** 
  109. * Registers rewrite rules for the API. 
  110. * 
  111. * @since 4.4.0 
  112. * 
  113. * @see rest_api_register_rewrites() 
  114. * @global WP $wp Current WordPress environment instance. 
  115. */ 
  116. function rest_api_init() { 
  117. rest_api_register_rewrites(); 
  118.  
  119. global $wp; 
  120. $wp->add_query_var( 'rest_route' ); 
  121.  
  122. /** 
  123. * Adds REST rewrite rules. 
  124. * 
  125. * @since 4.4.0 
  126. * 
  127. * @see add_rewrite_rule() 
  128. * @global WP_Rewrite $wp_rewrite 
  129. */ 
  130. function rest_api_register_rewrites() { 
  131. global $wp_rewrite; 
  132.  
  133. add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); 
  134. add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); 
  135. add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' ); 
  136. add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' ); 
  137.  
  138. /** 
  139. * Registers the default REST API filters. 
  140. * 
  141. * Attached to the {@see 'rest_api_init'} action 
  142. * to make testing and disabling these filters easier. 
  143. * 
  144. * @since 4.4.0 
  145. */ 
  146. function rest_api_default_filters() { 
  147. // Deprecated reporting. 
  148. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 ); 
  149. add_filter( 'deprecated_function_trigger_error', '__return_false' ); 
  150. add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 ); 
  151. add_filter( 'deprecated_argument_trigger_error', '__return_false' ); 
  152.  
  153. // Default serving. 
  154. add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); 
  155. add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 ); 
  156.  
  157. add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 ); 
  158.  
  159. /** 
  160. * Registers default REST API routes. 
  161. * 
  162. * @since 4.7.0 
  163. */ 
  164. function create_initial_rest_routes() { 
  165. foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 
  166. $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller'; 
  167.  
  168. if ( ! class_exists( $class ) ) { 
  169. continue; 
  170. $controller = new $class( $post_type->name ); 
  171. if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { 
  172. continue; 
  173.  
  174. $controller->register_routes(); 
  175.  
  176. if ( post_type_supports( $post_type->name, 'revisions' ) ) { 
  177. $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); 
  178. $revisions_controller->register_routes(); 
  179.  
  180. // Post types. 
  181. $controller = new WP_REST_Post_Types_Controller; 
  182. $controller->register_routes(); 
  183.  
  184. // Post statuses. 
  185. $controller = new WP_REST_Post_Statuses_Controller; 
  186. $controller->register_routes(); 
  187.  
  188. // Taxonomies. 
  189. $controller = new WP_REST_Taxonomies_Controller; 
  190. $controller->register_routes(); 
  191.  
  192. // Terms. 
  193. foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) { 
  194. $class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller'; 
  195.  
  196. if ( ! class_exists( $class ) ) { 
  197. continue; 
  198. $controller = new $class( $taxonomy->name ); 
  199. if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) { 
  200. continue; 
  201.  
  202. $controller->register_routes(); 
  203.  
  204. // Users. 
  205. $controller = new WP_REST_Users_Controller; 
  206. $controller->register_routes(); 
  207.  
  208. // Comments. 
  209. $controller = new WP_REST_Comments_Controller; 
  210. $controller->register_routes(); 
  211.  
  212. // Settings. 
  213. $controller = new WP_REST_Settings_Controller; 
  214. $controller->register_routes(); 
  215.  
  216. /** 
  217. * Loads the REST API. 
  218. * 
  219. * @since 4.4.0 
  220. * 
  221. * @global WP $wp Current WordPress environment instance. 
  222. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server). 
  223. */ 
  224. function rest_api_loaded() { 
  225. if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) { 
  226. return; 
  227.  
  228. /** 
  229. * Whether this is a REST Request. 
  230. * 
  231. * @since 4.4.0 
  232. * @var bool 
  233. */ 
  234. define( 'REST_REQUEST', true ); 
  235.  
  236. // Initialize the server. 
  237. $server = rest_get_server(); 
  238.  
  239. // Fire off the request. 
  240. $server->serve_request( untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] ) ); 
  241.  
  242. // We're done. 
  243. die(); 
  244.  
  245. /** 
  246. * Retrieves the URL prefix for any API resource. 
  247. * 
  248. * @since 4.4.0 
  249. * 
  250. * @return string Prefix. 
  251. */ 
  252. function rest_get_url_prefix() { 
  253. /** 
  254. * Filters the REST URL prefix. 
  255. * 
  256. * @since 4.4.0 
  257. * 
  258. * @param string $prefix URL prefix. Default 'wp-json'. 
  259. */ 
  260. return apply_filters( 'rest_url_prefix', 'wp-json' ); 
  261.  
  262. /** 
  263. * Retrieves the URL to a REST endpoint on a site. 
  264. * 
  265. * Note: The returned URL is NOT escaped. 
  266. * 
  267. * @since 4.4.0 
  268. * 
  269. * @todo Check if this is even necessary 
  270. * @global WP_Rewrite $wp_rewrite 
  271. * 
  272. * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog. 
  273. * @param string $path Optional. REST route. Default '/'. 
  274. * @param string $scheme Optional. Sanitization scheme. Default 'rest'. 
  275. * @return string Full URL to the endpoint. 
  276. */ 
  277. function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) { 
  278. if ( empty( $path ) ) { 
  279. $path = '/'; 
  280.  
  281. if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) { 
  282. global $wp_rewrite; 
  283.  
  284. if ( $wp_rewrite->using_index_permalinks() ) { 
  285. $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme ); 
  286. } else { 
  287. $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme ); 
  288.  
  289. $url .= '/' . ltrim( $path, '/' ); 
  290. } else { 
  291. $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) ); 
  292.  
  293. $path = '/' . ltrim( $path, '/' ); 
  294.  
  295. $url = add_query_arg( 'rest_route', $path, $url ); 
  296.  
  297. if ( is_ssl() ) { 
  298. // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS. 
  299. if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) { 
  300. $url = set_url_scheme( $url, 'https' ); 
  301.  
  302. /** 
  303. * Filters the REST URL. 
  304. * 
  305. * Use this filter to adjust the url returned by the get_rest_url() function. 
  306. * 
  307. * @since 4.4.0 
  308. * 
  309. * @param string $url REST URL. 
  310. * @param string $path REST route. 
  311. * @param int $blog_id Blog ID. 
  312. * @param string $scheme Sanitization scheme. 
  313. */ 
  314. return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme ); 
  315.  
  316. /** 
  317. * Retrieves the URL to a REST endpoint. 
  318. * 
  319. * Note: The returned URL is NOT escaped. 
  320. * 
  321. * @since 4.4.0 
  322. * 
  323. * @param string $path Optional. REST route. Default empty. 
  324. * @param string $scheme Optional. Sanitization scheme. Default 'json'. 
  325. * @return string Full URL to the endpoint. 
  326. */ 
  327. function rest_url( $path = '', $scheme = 'json' ) { 
  328. return get_rest_url( null, $path, $scheme ); 
  329.  
  330. /** 
  331. * Do a REST request. 
  332. * 
  333. * Used primarily to route internal requests through WP_REST_Server. 
  334. * 
  335. * @since 4.4.0 
  336. * 
  337. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server). 
  338. * 
  339. * @param WP_REST_Request|string $request Request. 
  340. * @return WP_REST_Response REST response. 
  341. */ 
  342. function rest_do_request( $request ) { 
  343. $request = rest_ensure_request( $request ); 
  344. return rest_get_server()->dispatch( $request ); 
  345.  
  346. /** 
  347. * Retrieves the current REST server instance. 
  348. * 
  349. * Instantiates a new instance if none exists already. 
  350. * 
  351. * @since 4.5.0 
  352. * 
  353. * @global WP_REST_Server $wp_rest_server REST server instance. 
  354. * 
  355. * @return WP_REST_Server REST server instance. 
  356. */ 
  357. function rest_get_server() { 
  358. /** @var WP_REST_Server $wp_rest_server */ 
  359. global $wp_rest_server; 
  360.  
  361. if ( empty( $wp_rest_server ) ) { 
  362. /** 
  363. * Filters the REST Server Class. 
  364. * 
  365. * This filter allows you to adjust the server class used by the API, using a 
  366. * different class to handle requests. 
  367. * 
  368. * @since 4.4.0 
  369. * 
  370. * @param string $class_name The name of the server class. Default 'WP_REST_Server'. 
  371. */ 
  372. $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' ); 
  373. $wp_rest_server = new $wp_rest_server_class; 
  374.  
  375. /** 
  376. * Fires when preparing to serve an API request. 
  377. * 
  378. * Endpoint objects should be created and register their hooks on this action rather 
  379. * than another action to ensure they're only loaded when needed. 
  380. * 
  381. * @since 4.4.0 
  382. * 
  383. * @param WP_REST_Server $wp_rest_server Server object. 
  384. */ 
  385. do_action( 'rest_api_init', $wp_rest_server ); 
  386.  
  387. return $wp_rest_server; 
  388.  
  389. /** 
  390. * Ensures request arguments are a request object (for consistency). 
  391. * 
  392. * @since 4.4.0 
  393. * 
  394. * @param array|WP_REST_Request $request Request to check. 
  395. * @return WP_REST_Request REST request instance. 
  396. */ 
  397. function rest_ensure_request( $request ) { 
  398. if ( $request instanceof WP_REST_Request ) { 
  399. return $request; 
  400.  
  401. return new WP_REST_Request( 'GET', '', $request ); 
  402.  
  403. /** 
  404. * Ensures a REST response is a response object (for consistency). 
  405. * 
  406. * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc 
  407. * without needing to double-check the object. Will also allow WP_Error to indicate error 
  408. * responses, so users should immediately check for this value. 
  409. * 
  410. * @since 4.4.0 
  411. * 
  412. * @param WP_Error|WP_HTTP_Response|mixed $response Response to check. 
  413. * @return WP_REST_Response|mixed If response generated an error, WP_Error, if response 
  414. * is already an instance, WP_HTTP_Response, otherwise 
  415. * returns a new WP_REST_Response instance. 
  416. */ 
  417. function rest_ensure_response( $response ) { 
  418. if ( is_wp_error( $response ) ) { 
  419. return $response; 
  420.  
  421. if ( $response instanceof WP_HTTP_Response ) { 
  422. return $response; 
  423.  
  424. return new WP_REST_Response( $response ); 
  425.  
  426. /** 
  427. * Handles _deprecated_function() errors. 
  428. * 
  429. * @since 4.4.0 
  430. * 
  431. * @param string $function The function that was called. 
  432. * @param string $replacement The function that should have been called. 
  433. * @param string $version Version. 
  434. */ 
  435. function rest_handle_deprecated_function( $function, $replacement, $version ) { 
  436. if ( ! empty( $replacement ) ) { 
  437. /** translators: 1: function name, 2: WordPress version number, 3: new function name */ 
  438. $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement ); 
  439. } else { 
  440. /** translators: 1: function name, 2: WordPress version number */ 
  441. $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); 
  442.  
  443. header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) ); 
  444.  
  445. /** 
  446. * Handles _deprecated_argument() errors. 
  447. * 
  448. * @since 4.4.0 
  449. * 
  450. * @param string $function The function that was called. 
  451. * @param string $message A message regarding the change. 
  452. * @param string $version Version. 
  453. */ 
  454. function rest_handle_deprecated_argument( $function, $message, $version ) { 
  455. if ( ! empty( $message ) ) { 
  456. /** translators: 1: function name, 2: WordPress version number, 3: error message */ 
  457. $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message ); 
  458. } else { 
  459. /** translators: 1: function name, 2: WordPress version number */ 
  460. $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version ); 
  461.  
  462. header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) ); 
  463.  
  464. /** 
  465. * Sends Cross-Origin Resource Sharing headers with API requests. 
  466. * 
  467. * @since 4.4.0 
  468. * 
  469. * @param mixed $value Response data. 
  470. * @return mixed Response data. 
  471. */ 
  472. function rest_send_cors_headers( $value ) { 
  473. $origin = get_http_origin(); 
  474.  
  475. if ( $origin ) { 
  476. header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); 
  477. header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' ); 
  478. header( 'Access-Control-Allow-Credentials: true' ); 
  479. header( 'Vary: Origin' ); 
  480.  
  481. return $value; 
  482.  
  483. /** 
  484. * Handles OPTIONS requests for the server. 
  485. * 
  486. * This is handled outside of the server code, as it doesn't obey normal route 
  487. * mapping. 
  488. * 
  489. * @since 4.4.0 
  490. * 
  491. * @param mixed $response Current response, either response or `null` to indicate pass-through. 
  492. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). 
  493. * @param WP_REST_Request $request The request that was used to make current response. 
  494. * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through. 
  495. */ 
  496. function rest_handle_options_request( $response, $handler, $request ) { 
  497. if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) { 
  498. return $response; 
  499.  
  500. $response = new WP_REST_Response(); 
  501. $data = array(); 
  502.  
  503. foreach ( $handler->get_routes() as $route => $endpoints ) { 
  504. $match = preg_match( '@^' . $route . '$@i', $request->get_route() ); 
  505.  
  506. if ( ! $match ) { 
  507. continue; 
  508.  
  509. $data = $handler->get_data_for_route( $route, $endpoints, 'help' ); 
  510. $response->set_matched_route( $route ); 
  511. break; 
  512.  
  513. $response->set_data( $data ); 
  514. return $response; 
  515.  
  516. /** 
  517. * Sends the "Allow" header to state all methods that can be sent to the current route. 
  518. * 
  519. * @since 4.4.0 
  520. * 
  521. * @param WP_REST_Response $response Current response being served. 
  522. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server). 
  523. * @param WP_REST_Request $request The request that was used to make current response. 
  524. * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods. 
  525. */ 
  526. function rest_send_allow_header( $response, $server, $request ) { 
  527. $matched_route = $response->get_matched_route(); 
  528.  
  529. if ( ! $matched_route ) { 
  530. return $response; 
  531.  
  532. $routes = $server->get_routes(); 
  533.  
  534. $allowed_methods = array(); 
  535.  
  536. // Get the allowed methods across the routes. 
  537. foreach ( $routes[ $matched_route ] as $_handler ) { 
  538. foreach ( $_handler['methods'] as $handler_method => $value ) { 
  539.  
  540. if ( ! empty( $_handler['permission_callback'] ) ) { 
  541.  
  542. $permission = call_user_func( $_handler['permission_callback'], $request ); 
  543.  
  544. $allowed_methods[ $handler_method ] = true === $permission; 
  545. } else { 
  546. $allowed_methods[ $handler_method ] = true; 
  547.  
  548. // Strip out all the methods that are not allowed (false values). 
  549. $allowed_methods = array_filter( $allowed_methods ); 
  550.  
  551. if ( $allowed_methods ) { 
  552. $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) ); 
  553.  
  554. return $response; 
  555.  
  556. /** 
  557. * Adds the REST API URL to the WP RSD endpoint. 
  558. * 
  559. * @since 4.4.0 
  560. * 
  561. * @see get_rest_url() 
  562. */ 
  563. function rest_output_rsd() { 
  564. $api_root = get_rest_url(); 
  565.  
  566. if ( empty( $api_root ) ) { 
  567. return; 
  568. ?> 
  569. <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" /> 
  570. <?php 
  571.  
  572. /** 
  573. * Outputs the REST API link tag into page header. 
  574. * 
  575. * @since 4.4.0 
  576. * 
  577. * @see get_rest_url() 
  578. */ 
  579. function rest_output_link_wp_head() { 
  580. $api_root = get_rest_url(); 
  581.  
  582. if ( empty( $api_root ) ) { 
  583. return; 
  584.  
  585. echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n"; 
  586.  
  587. /** 
  588. * Sends a Link header for the REST API. 
  589. * 
  590. * @since 4.4.0 
  591. */ 
  592. function rest_output_link_header() { 
  593. if ( headers_sent() ) { 
  594. return; 
  595.  
  596. $api_root = get_rest_url(); 
  597.  
  598. if ( empty( $api_root ) ) { 
  599. return; 
  600.  
  601. header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false ); 
  602.  
  603. /** 
  604. * Checks for errors when using cookie-based authentication. 
  605. * 
  606. * WordPress' built-in cookie authentication is always active 
  607. * for logged in users. However, the API has to check nonces 
  608. * for each request to ensure users are not vulnerable to CSRF. 
  609. * 
  610. * @since 4.4.0 
  611. * 
  612. * @global mixed $wp_rest_auth_cookie 
  613. * @global WP_REST_Server $wp_rest_server REST server instance. 
  614. * 
  615. * @param WP_Error|mixed $result Error from another authentication handler,  
  616. * null if we should handle it, or another value 
  617. * if not. 
  618. * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true. 
  619. */ 
  620. function rest_cookie_check_errors( $result ) { 
  621. if ( ! empty( $result ) ) { 
  622. return $result; 
  623.  
  624. global $wp_rest_auth_cookie, $wp_rest_server; 
  625.  
  626. /** 
  627. * Is cookie authentication being used? (If we get an auth 
  628. * error, but we're still logged in, another authentication 
  629. * must have been used). 
  630. */ 
  631. if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) { 
  632. return $result; 
  633.  
  634. // Determine if there is a nonce. 
  635. $nonce = null; 
  636.  
  637. if ( isset( $_REQUEST['_wpnonce'] ) ) { 
  638. $nonce = $_REQUEST['_wpnonce']; 
  639. } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) { 
  640. $nonce = $_SERVER['HTTP_X_WP_NONCE']; 
  641.  
  642. if ( null === $nonce ) { 
  643. // No nonce at all, so act as if it's an unauthenticated request. 
  644. wp_set_current_user( 0 ); 
  645. return true; 
  646.  
  647. // Check the nonce. 
  648. $result = wp_verify_nonce( $nonce, 'wp_rest' ); 
  649.  
  650. if ( ! $result ) { 
  651. return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) ); 
  652.  
  653. // Send a refreshed nonce in header. 
  654. $wp_rest_server->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) ); 
  655.  
  656. return true; 
  657.  
  658. /** 
  659. * Collects cookie authentication status. 
  660. * 
  661. * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors. 
  662. * 
  663. * @since 4.4.0 
  664. * 
  665. * @see current_action() 
  666. * @global mixed $wp_rest_auth_cookie 
  667. */ 
  668. function rest_cookie_collect_status() { 
  669. global $wp_rest_auth_cookie; 
  670.  
  671. $status_type = current_action(); 
  672.  
  673. if ( 'auth_cookie_valid' !== $status_type ) { 
  674. $wp_rest_auth_cookie = substr( $status_type, 12 ); 
  675. return; 
  676.  
  677. $wp_rest_auth_cookie = true; 
  678.  
  679. /** 
  680. * Parses an RFC3339 time into a Unix timestamp. 
  681. * 
  682. * @since 4.4.0 
  683. * 
  684. * @param string $date RFC3339 timestamp. 
  685. * @param bool $force_utc Optional. Whether to force UTC timezone instead of using 
  686. * the timestamp's timezone. Default false. 
  687. * @return int Unix timestamp. 
  688. */ 
  689. function rest_parse_date( $date, $force_utc = false ) { 
  690. if ( $force_utc ) { 
  691. $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date ); 
  692.  
  693. $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#'; 
  694.  
  695. if ( ! preg_match( $regex, $date, $matches ) ) { 
  696. return false; 
  697.  
  698. return strtotime( $date ); 
  699.  
  700. /** 
  701. * Retrieves a local date with its GMT equivalent, in MySQL datetime format. 
  702. * 
  703. * @since 4.4.0 
  704. * 
  705. * @see rest_parse_date() 
  706. * 
  707. * @param string $date RFC3339 timestamp. 
  708. * @param bool $force_utc Whether a UTC timestamp should be forced. Default false. 
  709. * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),  
  710. * null on failure. 
  711. */ 
  712. function rest_get_date_with_gmt( $date, $force_utc = false ) { 
  713. $date = rest_parse_date( $date, $force_utc ); 
  714.  
  715. if ( empty( $date ) ) { 
  716. return null; 
  717.  
  718. $utc = date( 'Y-m-d H:i:s', $date ); 
  719. $local = get_date_from_gmt( $utc ); 
  720.  
  721. return array( $local, $utc ); 
  722.  
  723. /** 
  724. * Returns a contextual HTTP error code for authorization failure. 
  725. * 
  726. * @since 4.7.0 
  727. * 
  728. * @return integer 401 if the user is not logged in, 403 if the user is logged in. 
  729. */ 
  730. function rest_authorization_required_code() { 
  731. return is_user_logged_in() ? 403 : 401; 
  732.  
  733. /** 
  734. * Validate a request argument based on details registered to the route. 
  735. * 
  736. * @since 4.7.0 
  737. * 
  738. * @param mixed $value 
  739. * @param WP_REST_Request $request 
  740. * @param string $param 
  741. * @return WP_Error|boolean 
  742. */ 
  743. function rest_validate_request_arg( $value, $request, $param ) { 
  744. $attributes = $request->get_attributes(); 
  745. if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { 
  746. return true; 
  747. $args = $attributes['args'][ $param ]; 
  748.  
  749. return rest_validate_value_from_schema( $value, $args, $param ); 
  750.  
  751. /** 
  752. * Sanitize a request argument based on details registered to the route. 
  753. * 
  754. * @since 4.7.0 
  755. * 
  756. * @param mixed $value 
  757. * @param WP_REST_Request $request 
  758. * @param string $param 
  759. * @return mixed 
  760. */ 
  761. function rest_sanitize_request_arg( $value, $request, $param ) { 
  762. $attributes = $request->get_attributes(); 
  763. if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) { 
  764. return $value; 
  765. $args = $attributes['args'][ $param ]; 
  766.  
  767. return rest_sanitize_value_from_schema( $value, $args ); 
  768.  
  769. /** 
  770. * Parse a request argument based on details registered to the route. 
  771. * 
  772. * Runs a validation check and sanitizes the value, primarily to be used via 
  773. * the `sanitize_callback` arguments in the endpoint args registration. 
  774. * 
  775. * @since 4.7.0 
  776. * 
  777. * @param mixed $value 
  778. * @param WP_REST_Request $request 
  779. * @param string $param 
  780. * @return mixed 
  781. */ 
  782. function rest_parse_request_arg( $value, $request, $param ) { 
  783. $is_valid = rest_validate_request_arg( $value, $request, $param ); 
  784.  
  785. if ( is_wp_error( $is_valid ) ) { 
  786. return $is_valid; 
  787.  
  788. $value = rest_sanitize_request_arg( $value, $request, $param ); 
  789.  
  790. return $value; 
  791.  
  792. /** 
  793. * Determines if an IP address is valid. 
  794. * 
  795. * Handles both IPv4 and IPv6 addresses. 
  796. * 
  797. * @since 4.7.0 
  798. * 
  799. * @param string $ip IP address. 
  800. * @return string|false The valid IP address, otherwise false. 
  801. */ 
  802. function rest_is_ip_address( $ip ) { 
  803. $ipv4_pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.) {3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/'; 
  804.  
  805. if ( ! preg_match( $ipv4_pattern, $ip ) && ! Requests_IPv6::check_ipv6( $ip ) ) { 
  806. return false; 
  807.  
  808. return $ip; 
  809.  
  810. /** 
  811. * Changes a boolean-like value into the proper boolean value. 
  812. * 
  813. * @since 4.7.0 
  814. * 
  815. * @param bool|string|int $value The value being evaluated. 
  816. * @return boolean Returns the proper associated boolean value. 
  817. */ 
  818. function rest_sanitize_boolean( $value ) { 
  819. // String values are translated to `true`; make sure 'false' is false. 
  820. if ( is_string( $value ) ) { 
  821. $value = strtolower( $value ); 
  822. if ( in_array( $value, array( 'false', '0' ), true ) ) { 
  823. $value = false; 
  824.  
  825. // Everything else will map nicely to boolean. 
  826. return (boolean) $value; 
  827.  
  828. /** 
  829. * Determines if a given value is boolean-like. 
  830. * 
  831. * @since 4.7.0 
  832. * 
  833. * @param bool|string $maybe_bool The value being evaluated. 
  834. * @return boolean True if a boolean, otherwise false. 
  835. */ 
  836. function rest_is_boolean( $maybe_bool ) { 
  837. if ( is_bool( $maybe_bool ) ) { 
  838. return true; 
  839.  
  840. if ( is_string( $maybe_bool ) ) { 
  841. $maybe_bool = strtolower( $maybe_bool ); 
  842.  
  843. $valid_boolean_values = array( 
  844. 'false',  
  845. 'true',  
  846. '0',  
  847. '1',  
  848. ); 
  849.  
  850. return in_array( $maybe_bool, $valid_boolean_values, true ); 
  851.  
  852. if ( is_int( $maybe_bool ) ) { 
  853. return in_array( $maybe_bool, array( 0, 1 ), true ); 
  854.  
  855. return false; 
  856.  
  857. /** 
  858. * Retrieves the avatar urls in various sizes based on a given email address. 
  859. * 
  860. * @since 4.7.0 
  861. * 
  862. * @see get_avatar_url() 
  863. * 
  864. * @param string $email Email address. 
  865. * @return array $urls Gravatar url for each size. 
  866. */ 
  867. function rest_get_avatar_urls( $email ) { 
  868. $avatar_sizes = rest_get_avatar_sizes(); 
  869.  
  870. $urls = array(); 
  871. foreach ( $avatar_sizes as $size ) { 
  872. $urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) ); 
  873.  
  874. return $urls; 
  875.  
  876. /** 
  877. * Retrieves the pixel sizes for avatars. 
  878. * 
  879. * @since 4.7.0 
  880. * 
  881. * @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`. 
  882. */ 
  883. function rest_get_avatar_sizes() { 
  884. /** 
  885. * Filter the REST avatar sizes. 
  886. * 
  887. * Use this filter to adjust the array of sizes returned by the 
  888. * `rest_get_avatar_sizes` function. 
  889. * 
  890. * @since 4.4.0 
  891. * 
  892. * @param array $sizes An array of int values that are the pixel sizes for avatars. 
  893. * Default `[ 24, 48, 96 ]`. 
  894. */ 
  895. return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) ); 
  896.  
  897. /** 
  898. * Validate a value based on a schema. 
  899. * 
  900. * @param mixed $value The value to validate. 
  901. * @param array $args Schema array to use for validation. 
  902. * @param string $param The parameter name, used in error messages. 
  903. * @return true|WP_Error 
  904. */ 
  905. function rest_validate_value_from_schema( $value, $args, $param = '' ) { 
  906. if ( 'array' === $args['type'] ) { 
  907. if ( ! is_array( $value ) ) { 
  908. $value = preg_split( '/[\s, ]+/', $value ); 
  909. if ( ! wp_is_numeric_array( $value ) ) { 
  910. /** translators: 1: parameter, 2: type name */ 
  911. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) ); 
  912. foreach ( $value as $index => $v ) { 
  913. $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' ); 
  914. if ( is_wp_error( $is_valid ) ) { 
  915. return $is_valid; 
  916. if ( ! empty( $args['enum'] ) ) { 
  917. if ( ! in_array( $value, $args['enum'], true ) ) { 
  918. /** translators: 1: parameter, 2: list of valid values */ 
  919. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) ); 
  920.  
  921. if ( in_array( $args['type'], array( 'integer', 'number' ) ) && ! is_numeric( $value ) ) { 
  922. /** translators: 1: parameter, 2: type name */ 
  923. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) ); 
  924.  
  925. if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) { 
  926. /** translators: 1: parameter, 2: type name */ 
  927. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) ); 
  928.  
  929. if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) { 
  930. /** translators: 1: parameter, 2: type name */ 
  931. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) ); 
  932.  
  933. if ( 'string' === $args['type'] && ! is_string( $value ) ) { 
  934. /** translators: 1: parameter, 2: type name */ 
  935. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) ); 
  936.  
  937. if ( isset( $args['format'] ) ) { 
  938. switch ( $args['format'] ) { 
  939. case 'date-time' : 
  940. if ( ! rest_parse_date( $value ) ) { 
  941. return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) ); 
  942. break; 
  943.  
  944. case 'email' : 
  945. // is_email() checks for 3 characters (a@b), but 
  946. // wp_handle_comment_submission() requires 6 characters (a@b.co) 
  947. // 
  948. // https://core.trac.wordpress.org/ticket/38506 
  949. if ( ! is_email( $value ) || strlen( $value ) < 6 ) { 
  950. return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) ); 
  951. break; 
  952. case 'ip' : 
  953. if ( ! rest_is_ip_address( $value ) ) { 
  954. /** translators: %s: IP address */ 
  955. return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) ); 
  956. break; 
  957.  
  958. if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) { 
  959. if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) { 
  960. if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) { 
  961. /** translators: 1: parameter, 2: minimum number */ 
  962. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) ); 
  963. } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) { 
  964. /** translators: 1: parameter, 2: minimum number */ 
  965. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) ); 
  966. } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) { 
  967. if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) { 
  968. /** translators: 1: parameter, 2: maximum number */ 
  969. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) ); 
  970. } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) { 
  971. /** translators: 1: parameter, 2: maximum number */ 
  972. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) ); 
  973. } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) { 
  974. if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { 
  975. if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) { 
  976. /** translators: 1: parameter, 2: minimum number, 3: maximum number */ 
  977. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 
  978. } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) { 
  979. if ( $value >= $args['maximum'] || $value < $args['minimum'] ) { 
  980. /** translators: 1: parameter, 2: minimum number, 3: maximum number */ 
  981. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 
  982. } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { 
  983. if ( $value > $args['maximum'] || $value <= $args['minimum'] ) { 
  984. /** translators: 1: parameter, 2: minimum number, 3: maximum number */ 
  985. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 
  986. } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) { 
  987. if ( $value > $args['maximum'] || $value < $args['minimum'] ) { 
  988. /** translators: 1: parameter, 2: minimum number, 3: maximum number */ 
  989. return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) ); 
  990.  
  991. return true; 
  992.  
  993. /** 
  994. * Sanitize a value based on a schema. 
  995. * 
  996. * @param mixed $value The value to sanitize. 
  997. * @param array $args Schema array to use for sanitization. 
  998. * @return true|WP_Error 
  999. */ 
  1000. function rest_sanitize_value_from_schema( $value, $args ) { 
  1001. if ( 'array' === $args['type'] ) { 
  1002. if ( empty( $args['items'] ) ) { 
  1003. return (array) $value; 
  1004. if ( ! is_array( $value ) ) { 
  1005. $value = preg_split( '/[\s, ]+/', $value ); 
  1006. foreach ( $value as $index => $v ) { 
  1007. $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] ); 
  1008. // Normalize to numeric array so nothing unexpected 
  1009. // is in the keys. 
  1010. $value = array_values( $value ); 
  1011. return $value; 
  1012. if ( 'integer' === $args['type'] ) { 
  1013. return (int) $value; 
  1014.  
  1015. if ( 'number' === $args['type'] ) { 
  1016. return (float) $value; 
  1017.  
  1018. if ( 'boolean' === $args['type'] ) { 
  1019. return rest_sanitize_boolean( $value ); 
  1020.  
  1021. if ( isset( $args['format'] ) ) { 
  1022. switch ( $args['format'] ) { 
  1023. case 'date-time' : 
  1024. return sanitize_text_field( $value ); 
  1025.  
  1026. case 'email' : 
  1027. /** 
  1028. * sanitize_email() validates, which would be unexpected. 
  1029. */ 
  1030. return sanitize_text_field( $value ); 
  1031.  
  1032. case 'uri' : 
  1033. return esc_url_raw( $value ); 
  1034.  
  1035. case 'ip' : 
  1036. return sanitize_text_field( $value ); 
  1037.  
  1038. if ( 'string' === $args['type'] ) { 
  1039. return strval( $value ); 
  1040.  
  1041. return $value; 
.