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