PageRenderTime 43ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/rest-api.php

https://gitlab.com/webkod3r/tripolis
PHP | 668 lines | 254 code | 91 blank | 323 comment | 43 complexity | e5b2aac895123305a3cce3876d23fd8c MD5 | raw file
  1. <?php
  2. /**
  3. * REST API functions.
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 4.4.0
  8. */
  9. /**
  10. * Version number for our API.
  11. *
  12. * @var string
  13. */
  14. define( 'REST_API_VERSION', '2.0' );
  15. /**
  16. * Registers a REST API route.
  17. *
  18. * @since 4.4.0
  19. *
  20. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
  21. *
  22. * @param string $namespace The first URL segment after core prefix. Should be unique to your package/plugin.
  23. * @param string $route The base URL for route you are adding.
  24. * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
  25. * multiple methods. Default empty array.
  26. * @param bool $override Optional. If the route already exists, should we override it? True overrides,
  27. * false merges (with newer overriding if duplicate keys exist). Default false.
  28. * @return bool True on success, false on error.
  29. */
  30. function register_rest_route( $namespace, $route, $args = array(), $override = false ) {
  31. /** @var WP_REST_Server $wp_rest_server */
  32. global $wp_rest_server;
  33. if ( empty( $namespace ) ) {
  34. /*
  35. * Non-namespaced routes are not allowed, with the exception of the main
  36. * and namespace indexes. If you really need to register a
  37. * non-namespaced route, call `WP_REST_Server::register_route` directly.
  38. */
  39. _doing_it_wrong( 'register_rest_route', __( 'Routes must be namespaced with plugin or theme name and version.' ), '4.4.0' );
  40. return false;
  41. } else if ( empty( $route ) ) {
  42. _doing_it_wrong( 'register_rest_route', __( 'Route must be specified.' ), '4.4.0' );
  43. return false;
  44. }
  45. if ( isset( $args['callback'] ) ) {
  46. // Upgrade a single set to multiple.
  47. $args = array( $args );
  48. }
  49. $defaults = array(
  50. 'methods' => 'GET',
  51. 'callback' => null,
  52. 'args' => array(),
  53. );
  54. foreach ( $args as $key => &$arg_group ) {
  55. if ( ! is_numeric( $arg_group ) ) {
  56. // Route option, skip here.
  57. continue;
  58. }
  59. $arg_group = array_merge( $defaults, $arg_group );
  60. }
  61. $full_route = '/' . trim( $namespace, '/' ) . '/' . trim( $route, '/' );
  62. $wp_rest_server->register_route( $namespace, $full_route, $args, $override );
  63. return true;
  64. }
  65. /**
  66. * Registers rewrite rules for the API.
  67. *
  68. * @since 4.4.0
  69. *
  70. * @see rest_api_register_rewrites()
  71. * @global WP $wp Current WordPress environment instance.
  72. */
  73. function rest_api_init() {
  74. rest_api_register_rewrites();
  75. global $wp;
  76. $wp->add_query_var( 'rest_route' );
  77. }
  78. /**
  79. * Adds REST rewrite rules.
  80. *
  81. * @since 4.4.0
  82. *
  83. * @see add_rewrite_rule()
  84. */
  85. function rest_api_register_rewrites() {
  86. add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$','index.php?rest_route=/','top' );
  87. add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?','index.php?rest_route=/$matches[1]','top' );
  88. }
  89. /**
  90. * Registers the default REST API filters.
  91. *
  92. * Attached to the {@see 'rest_api_init'} action
  93. * to make testing and disabling these filters easier.
  94. *
  95. * @since 4.4.0
  96. */
  97. function rest_api_default_filters() {
  98. // Deprecated reporting.
  99. add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
  100. add_filter( 'deprecated_function_trigger_error', '__return_false' );
  101. add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
  102. add_filter( 'deprecated_argument_trigger_error', '__return_false' );
  103. // Default serving.
  104. add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
  105. add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
  106. add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
  107. }
  108. /**
  109. * Loads the REST API.
  110. *
  111. * @since 4.4.0
  112. *
  113. * @global WP $wp Current WordPress environment instance.
  114. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
  115. */
  116. function rest_api_loaded() {
  117. if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
  118. return;
  119. }
  120. /**
  121. * Whether this is a REST Request.
  122. *
  123. * @since 4.4.0
  124. * @var bool
  125. */
  126. define( 'REST_REQUEST', true );
  127. // Initialize the server.
  128. $server = rest_get_server();
  129. // Fire off the request.
  130. $server->serve_request( $GLOBALS['wp']->query_vars['rest_route'] );
  131. // We're done.
  132. die();
  133. }
  134. /**
  135. * Retrieves the URL prefix for any API resource.
  136. *
  137. * @since 4.4.0
  138. *
  139. * @return string Prefix.
  140. */
  141. function rest_get_url_prefix() {
  142. /**
  143. * Filter the REST URL prefix.
  144. *
  145. * @since 4.4.0
  146. *
  147. * @param string $prefix URL prefix. Default 'wp-json'.
  148. */
  149. return apply_filters( 'rest_url_prefix', 'wp-json' );
  150. }
  151. /**
  152. * Retrieves the URL to a REST endpoint on a site.
  153. *
  154. * Note: The returned URL is NOT escaped.
  155. *
  156. * @since 4.4.0
  157. *
  158. * @todo Check if this is even necessary
  159. *
  160. * @param int $blog_id Optional. Blog ID. Default of null returns URL for current blog.
  161. * @param string $path Optional. REST route. Default '/'.
  162. * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
  163. * @return string Full URL to the endpoint.
  164. */
  165. function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
  166. if ( empty( $path ) ) {
  167. $path = '/';
  168. }
  169. if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
  170. $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
  171. $url .= '/' . ltrim( $path, '/' );
  172. } else {
  173. $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
  174. $path = '/' . ltrim( $path, '/' );
  175. $url = add_query_arg( 'rest_route', $path, $url );
  176. }
  177. if ( is_ssl() ) {
  178. // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
  179. if ( $_SERVER['SERVER_NAME'] === parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) ) {
  180. $url = set_url_scheme( $url, 'https' );
  181. }
  182. }
  183. /**
  184. * Filter the REST URL.
  185. *
  186. * Use this filter to adjust the url returned by the `get_rest_url` function.
  187. *
  188. * @since 4.4.0
  189. *
  190. * @param string $url REST URL.
  191. * @param string $path REST route.
  192. * @param int $blog_id Blog ID.
  193. * @param string $scheme Sanitization scheme.
  194. */
  195. return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
  196. }
  197. /**
  198. * Retrieves the URL to a REST endpoint.
  199. *
  200. * Note: The returned URL is NOT escaped.
  201. *
  202. * @since 4.4.0
  203. *
  204. * @param string $path Optional. REST route. Default empty.
  205. * @param string $scheme Optional. Sanitization scheme. Default 'json'.
  206. * @return string Full URL to the endpoint.
  207. */
  208. function rest_url( $path = '', $scheme = 'json' ) {
  209. return get_rest_url( null, $path, $scheme );
  210. }
  211. /**
  212. * Do a REST request.
  213. *
  214. * Used primarily to route internal requests through WP_REST_Server.
  215. *
  216. * @since 4.4.0
  217. *
  218. * @global WP_REST_Server $wp_rest_server ResponseHandler instance (usually WP_REST_Server).
  219. *
  220. * @param WP_REST_Request|string $request Request.
  221. * @return WP_REST_Response REST response.
  222. */
  223. function rest_do_request( $request ) {
  224. $request = rest_ensure_request( $request );
  225. return rest_get_server()->dispatch( $request );
  226. }
  227. /**
  228. * Retrieves the current REST server instance.
  229. *
  230. * Instantiates a new instance if none exists already.
  231. *
  232. * @since 4.5.0
  233. *
  234. * @global WP_REST_Server $wp_rest_server REST server instance.
  235. *
  236. * @return WP_REST_Server REST server instance.
  237. */
  238. function rest_get_server() {
  239. /* @var WP_REST_Server $wp_rest_server */
  240. global $wp_rest_server;
  241. if ( empty( $wp_rest_server ) ) {
  242. /**
  243. * Filter the REST Server Class.
  244. *
  245. * This filter allows you to adjust the server class used by the API, using a
  246. * different class to handle requests.
  247. *
  248. * @since 4.4.0
  249. *
  250. * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
  251. */
  252. $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
  253. $wp_rest_server = new $wp_rest_server_class;
  254. /**
  255. * Fires when preparing to serve an API request.
  256. *
  257. * Endpoint objects should be created and register their hooks on this action rather
  258. * than another action to ensure they're only loaded when needed.
  259. *
  260. * @since 4.4.0
  261. *
  262. * @param WP_REST_Server $wp_rest_server Server object.
  263. */
  264. do_action( 'rest_api_init', $wp_rest_server );
  265. }
  266. return $wp_rest_server;
  267. }
  268. /**
  269. * Ensures request arguments are a request object (for consistency).
  270. *
  271. * @since 4.4.0
  272. *
  273. * @param array|WP_REST_Request $request Request to check.
  274. * @return WP_REST_Request REST request instance.
  275. */
  276. function rest_ensure_request( $request ) {
  277. if ( $request instanceof WP_REST_Request ) {
  278. return $request;
  279. }
  280. return new WP_REST_Request( 'GET', '', $request );
  281. }
  282. /**
  283. * Ensures a REST response is a response object (for consistency).
  284. *
  285. * This implements WP_HTTP_Response, allowing usage of `set_status`/`header`/etc
  286. * without needing to double-check the object. Will also allow WP_Error to indicate error
  287. * responses, so users should immediately check for this value.
  288. *
  289. * @since 4.4.0
  290. *
  291. * @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
  292. * @return mixed WP_Error if response generated an error, WP_HTTP_Response if response
  293. * is a already an instance, otherwise returns a new WP_REST_Response instance.
  294. */
  295. function rest_ensure_response( $response ) {
  296. if ( is_wp_error( $response ) ) {
  297. return $response;
  298. }
  299. if ( $response instanceof WP_HTTP_Response ) {
  300. return $response;
  301. }
  302. return new WP_REST_Response( $response );
  303. }
  304. /**
  305. * Handles _deprecated_function() errors.
  306. *
  307. * @since 4.4.0
  308. *
  309. * @param string $function The function that was called.
  310. * @param string $replacement The function that should have been called.
  311. * @param string $version Version.
  312. */
  313. function rest_handle_deprecated_function( $function, $replacement, $version ) {
  314. if ( ! empty( $replacement ) ) {
  315. /* translators: 1: function name, 2: WordPress version number, 3: new function name */
  316. $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function, $version, $replacement );
  317. } else {
  318. /* translators: 1: function name, 2: WordPress version number */
  319. $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
  320. }
  321. header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
  322. }
  323. /**
  324. * Handles _deprecated_argument() errors.
  325. *
  326. * @since 4.4.0
  327. *
  328. * @param string $function The function that was called.
  329. * @param string $message A message regarding the change.
  330. * @param string $version Version.
  331. */
  332. function rest_handle_deprecated_argument( $function, $message, $version ) {
  333. if ( ! empty( $message ) ) {
  334. /* translators: 1: function name, 2: WordPress version number, 3: error message */
  335. $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function, $version, $message );
  336. } else {
  337. /* translators: 1: function name, 2: WordPress version number */
  338. $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function, $version );
  339. }
  340. header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
  341. }
  342. /**
  343. * Sends Cross-Origin Resource Sharing headers with API requests.
  344. *
  345. * @since 4.4.0
  346. *
  347. * @param mixed $value Response data.
  348. * @return mixed Response data.
  349. */
  350. function rest_send_cors_headers( $value ) {
  351. $origin = get_http_origin();
  352. if ( $origin ) {
  353. header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
  354. header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
  355. header( 'Access-Control-Allow-Credentials: true' );
  356. }
  357. return $value;
  358. }
  359. /**
  360. * Handles OPTIONS requests for the server.
  361. *
  362. * This is handled outside of the server code, as it doesn't obey normal route
  363. * mapping.
  364. *
  365. * @since 4.4.0
  366. *
  367. * @param mixed $response Current response, either response or `null` to indicate pass-through.
  368. * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
  369. * @param WP_REST_Request $request The request that was used to make current response.
  370. * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
  371. */
  372. function rest_handle_options_request( $response, $handler, $request ) {
  373. if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
  374. return $response;
  375. }
  376. $response = new WP_REST_Response();
  377. $data = array();
  378. $accept = array();
  379. foreach ( $handler->get_routes() as $route => $endpoints ) {
  380. $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $args );
  381. if ( ! $match ) {
  382. continue;
  383. }
  384. $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
  385. $response->set_matched_route( $route );
  386. break;
  387. }
  388. $response->set_data( $data );
  389. return $response;
  390. }
  391. /**
  392. * Sends the "Allow" header to state all methods that can be sent to the current route.
  393. *
  394. * @since 4.4.0
  395. *
  396. * @param WP_REST_Response $response Current response being served.
  397. * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
  398. * @param WP_REST_Request $request The request that was used to make current response.
  399. * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
  400. */
  401. function rest_send_allow_header( $response, $server, $request ) {
  402. $matched_route = $response->get_matched_route();
  403. if ( ! $matched_route ) {
  404. return $response;
  405. }
  406. $routes = $server->get_routes();
  407. $allowed_methods = array();
  408. // Get the allowed methods across the routes.
  409. foreach ( $routes[ $matched_route ] as $_handler ) {
  410. foreach ( $_handler['methods'] as $handler_method => $value ) {
  411. if ( ! empty( $_handler['permission_callback'] ) ) {
  412. $permission = call_user_func( $_handler['permission_callback'], $request );
  413. $allowed_methods[ $handler_method ] = true === $permission;
  414. } else {
  415. $allowed_methods[ $handler_method ] = true;
  416. }
  417. }
  418. }
  419. // Strip out all the methods that are not allowed (false values).
  420. $allowed_methods = array_filter( $allowed_methods );
  421. if ( $allowed_methods ) {
  422. $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
  423. }
  424. return $response;
  425. }
  426. /**
  427. * Adds the REST API URL to the WP RSD endpoint.
  428. *
  429. * @since 4.4.0
  430. *
  431. * @see get_rest_url()
  432. */
  433. function rest_output_rsd() {
  434. $api_root = get_rest_url();
  435. if ( empty( $api_root ) ) {
  436. return;
  437. }
  438. ?>
  439. <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
  440. <?php
  441. }
  442. /**
  443. * Outputs the REST API link tag into page header.
  444. *
  445. * @since 4.4.0
  446. *
  447. * @see get_rest_url()
  448. */
  449. function rest_output_link_wp_head() {
  450. $api_root = get_rest_url();
  451. if ( empty( $api_root ) ) {
  452. return;
  453. }
  454. echo "<link rel='https://api.w.org/' href='" . esc_url( $api_root ) . "' />\n";
  455. }
  456. /**
  457. * Sends a Link header for the REST API.
  458. *
  459. * @since 4.4.0
  460. */
  461. function rest_output_link_header() {
  462. if ( headers_sent() ) {
  463. return;
  464. }
  465. $api_root = get_rest_url();
  466. if ( empty( $api_root ) ) {
  467. return;
  468. }
  469. header( 'Link: <' . esc_url_raw( $api_root ) . '>; rel="https://api.w.org/"', false );
  470. }
  471. /**
  472. * Checks for errors when using cookie-based authentication.
  473. *
  474. * WordPress' built-in cookie authentication is always active
  475. * for logged in users. However, the API has to check nonces
  476. * for each request to ensure users are not vulnerable to CSRF.
  477. *
  478. * @since 4.4.0
  479. *
  480. * @global mixed $wp_rest_auth_cookie
  481. *
  482. * @param WP_Error|mixed $result Error from another authentication handler, null if we should handle it,
  483. * or another value if not.
  484. * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
  485. */
  486. function rest_cookie_check_errors( $result ) {
  487. if ( ! empty( $result ) ) {
  488. return $result;
  489. }
  490. global $wp_rest_auth_cookie;
  491. /*
  492. * Is cookie authentication being used? (If we get an auth
  493. * error, but we're still logged in, another authentication
  494. * must have been used).
  495. */
  496. if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
  497. return $result;
  498. }
  499. // Determine if there is a nonce.
  500. $nonce = null;
  501. if ( isset( $_REQUEST['_wpnonce'] ) ) {
  502. $nonce = $_REQUEST['_wpnonce'];
  503. } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
  504. $nonce = $_SERVER['HTTP_X_WP_NONCE'];
  505. }
  506. if ( null === $nonce ) {
  507. // No nonce at all, so act as if it's an unauthenticated request.
  508. wp_set_current_user( 0 );
  509. return true;
  510. }
  511. // Check the nonce.
  512. $result = wp_verify_nonce( $nonce, 'wp_rest' );
  513. if ( ! $result ) {
  514. return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie nonce is invalid' ), array( 'status' => 403 ) );
  515. }
  516. return true;
  517. }
  518. /**
  519. * Collects cookie authentication status.
  520. *
  521. * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
  522. *
  523. * @since 4.4.0
  524. *
  525. * @see current_action()
  526. * @global mixed $wp_rest_auth_cookie
  527. */
  528. function rest_cookie_collect_status() {
  529. global $wp_rest_auth_cookie;
  530. $status_type = current_action();
  531. if ( 'auth_cookie_valid' !== $status_type ) {
  532. $wp_rest_auth_cookie = substr( $status_type, 12 );
  533. return;
  534. }
  535. $wp_rest_auth_cookie = true;
  536. }
  537. /**
  538. * Parses an RFC3339 time into a Unix timestamp.
  539. *
  540. * @since 4.4.0
  541. *
  542. * @param string $date RFC3339 timestamp.
  543. * @param bool $force_utc Optional. Whether to force UTC timezone instead of using
  544. * the timestamp's timezone. Default false.
  545. * @return int Unix timestamp.
  546. */
  547. function rest_parse_date( $date, $force_utc = false ) {
  548. if ( $force_utc ) {
  549. $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
  550. }
  551. $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
  552. if ( ! preg_match( $regex, $date, $matches ) ) {
  553. return false;
  554. }
  555. return strtotime( $date );
  556. }
  557. /**
  558. * Retrieves a local date with its GMT equivalent, in MySQL datetime format.
  559. *
  560. * @since 4.4.0
  561. *
  562. * @see rest_parse_date()
  563. *
  564. * @param string $date RFC3339 timestamp.
  565. * @param bool $force_utc Whether a UTC timestamp should be forced. Default false.
  566. * @return array|null Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
  567. * null on failure.
  568. */
  569. function rest_get_date_with_gmt( $date, $force_utc = false ) {
  570. $date = rest_parse_date( $date, $force_utc );
  571. if ( empty( $date ) ) {
  572. return null;
  573. }
  574. $utc = date( 'Y-m-d H:i:s', $date );
  575. $local = get_date_from_gmt( $utc );
  576. return array( $local, $utc );
  577. }