run:R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
25.9 KB
2026-03-11 16:18:52
R W Run
7.2 KB
2026-03-11 16:18:52
R W Run
56.67 KB
2026-03-11 16:18:52
R W Run
error_log
📄class-wp-rest-server.php
1<?php
2/**
3 * REST API: WP_REST_Server class
4 *
5 * @package WordPress
6 * @subpackage REST_API
7 * @since 4.4.0
8 */
9
10/**
11 * Core class used to implement the WordPress REST API server.
12 *
13 * @since 4.4.0
14 */
15#[AllowDynamicProperties]
16class WP_REST_Server {
17
18 /**
19 * Alias for GET transport method.
20 *
21 * @since 4.4.0
22 * @var string
23 */
24 const READABLE = 'GET';
25
26 /**
27 * Alias for POST transport method.
28 *
29 * @since 4.4.0
30 * @var string
31 */
32 const CREATABLE = 'POST';
33
34 /**
35 * Alias for POST, PUT, PATCH transport methods together.
36 *
37 * @since 4.4.0
38 * @var string
39 */
40 const EDITABLE = 'POST, PUT, PATCH';
41
42 /**
43 * Alias for DELETE transport method.
44 *
45 * @since 4.4.0
46 * @var string
47 */
48 const DELETABLE = 'DELETE';
49
50 /**
51 * Alias for GET, POST, PUT, PATCH & DELETE transport methods together.
52 *
53 * @since 4.4.0
54 * @var string
55 */
56 const ALLMETHODS = 'GET, POST, PUT, PATCH, DELETE';
57
58 /**
59 * Namespaces registered to the server.
60 *
61 * @since 4.4.0
62 * @var array
63 */
64 protected $namespaces = array();
65
66 /**
67 * Endpoints registered to the server.
68 *
69 * @since 4.4.0
70 * @var array
71 */
72 protected $endpoints = array();
73
74 /**
75 * Options defined for the routes.
76 *
77 * @since 4.4.0
78 * @var array
79 */
80 protected $route_options = array();
81
82 /**
83 * Caches embedded requests.
84 *
85 * @since 5.4.0
86 * @var array
87 */
88 protected $embed_cache = array();
89
90 /**
91 * Stores request objects that are currently being handled.
92 *
93 * @since 6.5.0
94 * @var array
95 */
96 protected $dispatching_requests = array();
97
98 /**
99 * Instantiates the REST server.
100 *
101 * @since 4.4.0
102 */
103 public function __construct() {
104 $this->endpoints = array(
105 // Meta endpoints.
106 '/' => array(
107 'callback' => array( $this, 'get_index' ),
108 'methods' => 'GET',
109 'args' => array(
110 'context' => array(
111 'default' => 'view',
112 ),
113 ),
114 ),
115 '/batch/v1' => array(
116 'callback' => array( $this, 'serve_batch_request_v1' ),
117 'methods' => 'POST',
118 'args' => array(
119 'validation' => array(
120 'type' => 'string',
121 'enum' => array( 'require-all-validate', 'normal' ),
122 'default' => 'normal',
123 ),
124 'requests' => array(
125 'required' => true,
126 'type' => 'array',
127 'maxItems' => $this->get_max_batch_size(),
128 'items' => array(
129 'type' => 'object',
130 'properties' => array(
131 'method' => array(
132 'type' => 'string',
133 'enum' => array( 'POST', 'PUT', 'PATCH', 'DELETE' ),
134 'default' => 'POST',
135 ),
136 'path' => array(
137 'type' => 'string',
138 'required' => true,
139 ),
140 'body' => array(
141 'type' => 'object',
142 'properties' => array(),
143 'additionalProperties' => true,
144 ),
145 'headers' => array(
146 'type' => 'object',
147 'properties' => array(),
148 'additionalProperties' => array(
149 'type' => array( 'string', 'array' ),
150 'items' => array(
151 'type' => 'string',
152 ),
153 ),
154 ),
155 ),
156 ),
157 ),
158 ),
159 ),
160 );
161 }
162
163
164 /**
165 * Checks the authentication headers if supplied.
166 *
167 * @since 4.4.0
168 *
169 * @return WP_Error|null|true WP_Error if authentication error occurred, null if authentication
170 * method wasn't used, true if authentication succeeded.
171 */
172 public function check_authentication() {
173 /**
174 * Filters REST API authentication errors.
175 *
176 * This is used to pass a WP_Error from an authentication method back to
177 * the API.
178 *
179 * Authentication methods should check first if they're being used, as
180 * multiple authentication methods can be enabled on a site (cookies,
181 * HTTP basic auth, OAuth). If the authentication method hooked in is
182 * not actually being attempted, null should be returned to indicate
183 * another authentication method should check instead. Similarly,
184 * callbacks should ensure the value is `null` before checking for
185 * errors.
186 *
187 * A WP_Error instance can be returned if an error occurs, and this should
188 * match the format used by API methods internally (that is, the `status`
189 * data should be used). A callback can return `true` to indicate that
190 * the authentication method was used, and it succeeded.
191 *
192 * @since 4.4.0
193 *
194 * @param WP_Error|null|true $errors WP_Error if authentication error occurred, null if authentication
195 * method wasn't used, true if authentication succeeded.
196 */
197 return apply_filters( 'rest_authentication_errors', null );
198 }
199
200 /**
201 * Converts an error to a response object.
202 *
203 * This iterates over all error codes and messages to change it into a flat
204 * array. This enables simpler client behavior, as it is represented as a
205 * list in JSON rather than an object/map.
206 *
207 * @since 4.4.0
208 * @since 5.7.0 Converted to a wrapper of {@see rest_convert_error_to_response()}.
209 *
210 * @param WP_Error $error WP_Error instance.
211 * @return WP_REST_Response List of associative arrays with code and message keys.
212 */
213 protected function error_to_response( $error ) {
214 return rest_convert_error_to_response( $error );
215 }
216
217 /**
218 * Retrieves an appropriate error representation in JSON.
219 *
220 * Note: This should only be used in WP_REST_Server::serve_request(), as it
221 * cannot handle WP_Error internally. All callbacks and other internal methods
222 * should instead return a WP_Error with the data set to an array that includes
223 * a 'status' key, with the value being the HTTP status to send.
224 *
225 * @since 4.4.0
226 *
227 * @param string $code WP_Error-style code.
228 * @param string $message Human-readable message.
229 * @param int|null $status Optional. HTTP status code to send. Default null.
230 * @return string JSON representation of the error.
231 */
232 protected function json_error( $code, $message, $status = null ) {
233 if ( $status ) {
234 $this->set_status( $status );
235 }
236
237 $error = compact( 'code', 'message' );
238
239 return wp_json_encode( $error );
240 }
241
242 /**
243 * Gets the encoding options passed to {@see wp_json_encode}.
244 *
245 * @since 6.1.0
246 *
247 * @param \WP_REST_Request $request The current request object.
248 *
249 * @return int The JSON encode options.
250 */
251 protected function get_json_encode_options( WP_REST_Request $request ) {
252 $options = 0;
253
254 if ( $request->has_param( '_pretty' ) ) {
255 $options |= JSON_PRETTY_PRINT;
256 }
257
258 /**
259 * Filters the JSON encoding options used to send the REST API response.
260 *
261 * @since 6.1.0
262 *
263 * @param int $options JSON encoding options {@see json_encode()}.
264 * @param WP_REST_Request $request Current request object.
265 */
266 return apply_filters( 'rest_json_encode_options', $options, $request );
267 }
268
269 /**
270 * Handles serving a REST API request.
271 *
272 * Matches the current server URI to a route and runs the first matching
273 * callback then outputs a JSON representation of the returned value.
274 *
275 * @since 4.4.0
276 *
277 * @see WP_REST_Server::dispatch()
278 *
279 * @global WP_User $current_user The currently authenticated user.
280 *
281 * @param string|null $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used.
282 * Default null.
283 * @return null|false Null if not served and a HEAD request, false otherwise.
284 */
285 public function serve_request( $path = null ) {
286 /* @var WP_User|null $current_user */
287 global $current_user;
288
289 if ( $current_user instanceof WP_User && ! $current_user->exists() ) {
290 /*
291 * If there is no current user authenticated via other means, clear
292 * the cached lack of user, so that an authenticate check can set it
293 * properly.
294 *
295 * This is done because for authentications such as Application
296 * Passwords, we don't want it to be accepted unless the current HTTP
297 * request is a REST API request, which can't always be identified early
298 * enough in evaluation.
299 */
300 $current_user = null;
301 }
302
303 /**
304 * Filters whether JSONP is enabled for the REST API.
305 *
306 * @since 4.4.0
307 *
308 * @param bool $jsonp_enabled Whether JSONP is enabled. Default true.
309 */
310 $jsonp_enabled = apply_filters( 'rest_jsonp_enabled', true );
311
312 $jsonp_callback = false;
313 if ( isset( $_GET['_jsonp'] ) ) {
314 $jsonp_callback = $_GET['_jsonp'];
315 }
316
317 $content_type = ( $jsonp_callback && $jsonp_enabled ) ? 'application/javascript' : 'application/json';
318 $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) );
319 $this->send_header( 'X-Robots-Tag', 'noindex' );
320
321 $api_root = get_rest_url();
322 if ( ! empty( $api_root ) ) {
323 $this->send_header( 'Link', '<' . sanitize_url( $api_root ) . '>; rel="https://api.w.org/"' );
324 }
325
326 /*
327 * Mitigate possible JSONP Flash attacks.
328 *
329 * https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
330 */
331 $this->send_header( 'X-Content-Type-Options', 'nosniff' );
332
333 /**
334 * Filters whether the REST API is enabled.
335 *
336 * @since 4.4.0
337 * @deprecated 4.7.0 Use the {@see 'rest_authentication_errors'} filter to
338 * restrict access to the REST API.
339 *
340 * @param bool $rest_enabled Whether the REST API is enabled. Default true.
341 */
342 apply_filters_deprecated(
343 'rest_enabled',
344 array( true ),
345 '4.7.0',
346 'rest_authentication_errors',
347 sprintf(
348 /* translators: %s: rest_authentication_errors */
349 __( 'The REST API can no longer be completely disabled, the %s filter can be used to restrict access to the API, instead.' ),
350 'rest_authentication_errors'
351 )
352 );
353
354 if ( $jsonp_callback ) {
355 if ( ! $jsonp_enabled ) {
356 echo $this->json_error( 'rest_callback_disabled', __( 'JSONP support is disabled on this site.' ), 400 );
357 return false;
358 }
359
360 if ( ! wp_check_jsonp_callback( $jsonp_callback ) ) {
361 echo $this->json_error( 'rest_callback_invalid', __( 'Invalid JSONP callback function.' ), 400 );
362 return false;
363 }
364 }
365
366 if ( empty( $path ) ) {
367 if ( isset( $_SERVER['PATH_INFO'] ) ) {
368 $path = $_SERVER['PATH_INFO'];
369 } else {
370 $path = '/';
371 }
372 }
373
374 $request = new WP_REST_Request( $_SERVER['REQUEST_METHOD'], $path );
375
376 $request->set_query_params( wp_unslash( $_GET ) );
377 $request->set_body_params( wp_unslash( $_POST ) );
378 $request->set_file_params( $_FILES );
379 $request->set_headers( $this->get_headers( wp_unslash( $_SERVER ) ) );
380 $request->set_body( self::get_raw_data() );
381
382 /*
383 * HTTP method override for clients that can't use PUT/PATCH/DELETE. First, we check
384 * $_GET['_method']. If that is not set, we check for the HTTP_X_HTTP_METHOD_OVERRIDE
385 * header.
386 */
387 $method_overridden = false;
388 if ( isset( $_GET['_method'] ) ) {
389 $request->set_method( $_GET['_method'] );
390 } elseif ( isset( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ) ) {
391 $request->set_method( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] );
392 $method_overridden = true;
393 }
394
395 $expose_headers = array( 'X-WP-Total', 'X-WP-TotalPages', 'Link' );
396
397 /**
398 * Filters the list of response headers that are exposed to REST API CORS requests.
399 *
400 * @since 5.5.0
401 * @since 6.3.0 The `$request` parameter was added.
402 *
403 * @param string[] $expose_headers The list of response headers to expose.
404 * @param WP_REST_Request $request The request in context.
405 */
406 $expose_headers = apply_filters( 'rest_exposed_cors_headers', $expose_headers, $request );
407
408 $this->send_header( 'Access-Control-Expose-Headers', implode( ', ', $expose_headers ) );
409
410 $allow_headers = array(
411 'Authorization',
412 'X-WP-Nonce',
413 'Content-Disposition',
414 'Content-MD5',
415 'Content-Type',
416 );
417
418 /**
419 * Filters the list of request headers that are allowed for REST API CORS requests.
420 *
421 * The allowed headers are passed to the browser to specify which
422 * headers can be passed to the REST API. By default, we allow the
423 * Content-* headers needed to upload files to the media endpoints.
424 * As well as the Authorization and Nonce headers for allowing authentication.
425 *
426 * @since 5.5.0
427 * @since 6.3.0 The `$request` parameter was added.
428 *
429 * @param string[] $allow_headers The list of request headers to allow.
430 * @param WP_REST_Request $request The request in context.
431 */
432 $allow_headers = apply_filters( 'rest_allowed_cors_headers', $allow_headers, $request );
433
434 $this->send_header( 'Access-Control-Allow-Headers', implode( ', ', $allow_headers ) );
435
436 $result = $this->check_authentication();
437
438 if ( ! is_wp_error( $result ) ) {
439 $result = $this->dispatch( $request );
440 }
441
442 // Normalize to either WP_Error or WP_REST_Response...
443 $result = rest_ensure_response( $result );
444
445 // ...then convert WP_Error across.
446 if ( is_wp_error( $result ) ) {
447 $result = $this->error_to_response( $result );
448 }
449
450 /**
451 * Filters the REST API response.
452 *
453 * Allows modification of the response before returning.
454 *
455 * @since 4.4.0
456 * @since 4.5.0 Applied to embedded responses.
457 *
458 * @param WP_HTTP_Response $result Result to send to the client. Usually a `WP_REST_Response`.
459 * @param WP_REST_Server $server Server instance.
460 * @param WP_REST_Request $request Request used to generate the response.
461 */
462 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request );
463
464 // Wrap the response in an envelope if asked for.
465 if ( isset( $_GET['_envelope'] ) ) {
466 $embed = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
467 $result = $this->envelope_response( $result, $embed );
468 }
469
470 // Send extra data from response objects.
471 $headers = $result->get_headers();
472 $this->send_headers( $headers );
473
474 $code = $result->get_status();
475 $this->set_status( $code );
476
477 /**
478 * Filters whether to send no-cache headers on a REST API request.
479 *
480 * @since 4.4.0
481 * @since 6.3.2 Moved the block to catch the filter added on rest_cookie_check_errors() from wp-includes/rest-api.php.
482 *
483 * @param bool $rest_send_nocache_headers Whether to send no-cache headers.
484 */
485 $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() );
486
487 /*
488 * Send no-cache headers if $send_no_cache_headers is true,
489 * OR if the HTTP_X_HTTP_METHOD_OVERRIDE is used but resulted a 4xx response code.
490 */
491 if ( $send_no_cache_headers || ( true === $method_overridden && str_starts_with( $code, '4' ) ) ) {
492 foreach ( wp_get_nocache_headers() as $header => $header_value ) {
493 if ( empty( $header_value ) ) {
494 $this->remove_header( $header );
495 } else {
496 $this->send_header( $header, $header_value );
497 }
498 }
499 }
500
501 /**
502 * Filters whether the REST API request has already been served.
503 *
504 * Allow sending the request manually - by returning true, the API result
505 * will not be sent to the client.
506 *
507 * @since 4.4.0
508 *
509 * @param bool $served Whether the request has already been served.
510 * Default false.
511 * @param WP_HTTP_Response $result Result to send to the client. Usually a `WP_REST_Response`.
512 * @param WP_REST_Request $request Request used to generate the response.
513 * @param WP_REST_Server $server Server instance.
514 */
515 $served = apply_filters( 'rest_pre_serve_request', false, $result, $request, $this );
516
517 if ( ! $served ) {
518 if ( 'HEAD' === $request->get_method() ) {
519 return null;
520 }
521
522 // Embed links inside the request.
523 $embed = isset( $_GET['_embed'] ) ? rest_parse_embed_param( $_GET['_embed'] ) : false;
524 $result = $this->response_to_data( $result, $embed );
525
526 /**
527 * Filters the REST API response.
528 *
529 * Allows modification of the response data after inserting
530 * embedded data (if any) and before echoing the response data.
531 *
532 * @since 4.8.1
533 *
534 * @param array $result Response data to send to the client.
535 * @param WP_REST_Server $server Server instance.
536 * @param WP_REST_Request $request Request used to generate the response.
537 */
538 $result = apply_filters( 'rest_pre_echo_response', $result, $this, $request );
539
540 // The 204 response shouldn't have a body.
541 if ( 204 === $code || null === $result ) {
542 return null;
543 }
544
545 $result = wp_json_encode( $result, $this->get_json_encode_options( $request ) );
546
547 $json_error_message = $this->get_json_last_error();
548
549 if ( $json_error_message ) {
550 $this->set_status( 500 );
551 $json_error_obj = new WP_Error(
552 'rest_encode_error',
553 $json_error_message,
554 array( 'status' => 500 )
555 );
556
557 $result = $this->error_to_response( $json_error_obj );
558 $result = wp_json_encode( $result->data, $this->get_json_encode_options( $request ) );
559 }
560
561 if ( $jsonp_callback ) {
562 // Prepend '/**/' to mitigate possible JSONP Flash attacks.
563 // https://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/
564 echo '/**/' . $jsonp_callback . '(' . $result . ')';
565 } else {
566 echo $result;
567 }
568 }
569
570 return null;
571 }
572
573 /**
574 * Converts a response to data to send.
575 *
576 * @since 4.4.0
577 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
578 *
579 * @param WP_REST_Response $response Response object.
580 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links.
581 * @return array {
582 * Data with sub-requests embedded.
583 *
584 * @type array $_links Links.
585 * @type array $_embedded Embedded objects.
586 * }
587 */
588 public function response_to_data( $response, $embed ) {
589 $data = $response->get_data();
590 $links = self::get_compact_response_links( $response );
591
592 if ( ! empty( $links ) ) {
593 // Convert links to part of the data.
594 $data['_links'] = $links;
595 }
596
597 if ( $embed ) {
598 $this->embed_cache = array();
599 // Determine if this is a numeric array.
600 if ( wp_is_numeric_array( $data ) ) {
601 foreach ( $data as $key => $item ) {
602 $data[ $key ] = $this->embed_links( $item, $embed );
603 }
604 } else {
605 $data = $this->embed_links( $data, $embed );
606 }
607 $this->embed_cache = array();
608 }
609
610 return $data;
611 }
612
613 /**
614 * Retrieves links from a response.
615 *
616 * Extracts the links from a response into a structured hash, suitable for
617 * direct output.
618 *
619 * @since 4.4.0
620 *
621 * @param WP_REST_Response $response Response to extract links from.
622 * @return array Map of link relation to list of link hashes.
623 */
624 public static function get_response_links( $response ) {
625 $links = $response->get_links();
626
627 if ( empty( $links ) ) {
628 return array();
629 }
630
631 // Convert links to part of the data.
632 $data = array();
633 foreach ( $links as $rel => $items ) {
634 $data[ $rel ] = array();
635
636 foreach ( $items as $item ) {
637 $attributes = $item['attributes'];
638 $attributes['href'] = $item['href'];
639
640 if ( 'self' !== $rel ) {
641 $data[ $rel ][] = $attributes;
642 continue;
643 }
644
645 $target_hints = self::get_target_hints_for_link( $attributes );
646 if ( $target_hints ) {
647 $attributes['targetHints'] = $target_hints;
648 }
649
650 $data[ $rel ][] = $attributes;
651 }
652 }
653
654 return $data;
655 }
656
657 /**
658 * Gets the target hints for a REST API Link.
659 *
660 * @since 6.7.0
661 *
662 * @param array $link The link to get target hints for.
663 * @return array|null
664 */
665 protected static function get_target_hints_for_link( $link ) {
666 // Prefer targetHints that were specifically designated by the developer.
667 if ( isset( $link['targetHints']['allow'] ) ) {
668 return null;
669 }
670
671 $request = WP_REST_Request::from_url( $link['href'] );
672 if ( ! $request ) {
673 return null;
674 }
675
676 $server = rest_get_server();
677 $match = $server->match_request_to_handler( $request );
678
679 if ( is_wp_error( $match ) ) {
680 return null;
681 }
682
683 if ( is_wp_error( $request->has_valid_params() ) ) {
684 return null;
685 }
686
687 if ( is_wp_error( $request->sanitize_params() ) ) {
688 return null;
689 }
690
691 $target_hints = array();
692
693 $response = new WP_REST_Response();
694 $response->set_matched_route( $match[0] );
695 $response->set_matched_handler( $match[1] );
696 $headers = rest_send_allow_header( $response, $server, $request )->get_headers();
697
698 foreach ( $headers as $name => $value ) {
699 $name = WP_REST_Request::canonicalize_header_name( $name );
700
701 $target_hints[ $name ] = array_map( 'trim', explode( ',', $value ) );
702 }
703
704 return $target_hints;
705 }
706
707 /**
708 * Retrieves the CURIEs (compact URIs) used for relations.
709 *
710 * Extracts the links from a response into a structured hash, suitable for
711 * direct output.
712 *
713 * @since 4.5.0
714 *
715 * @param WP_REST_Response $response Response to extract links from.
716 * @return array Map of link relation to list of link hashes.
717 */
718 public static function get_compact_response_links( $response ) {
719 $links = self::get_response_links( $response );
720
721 if ( empty( $links ) ) {
722 return array();
723 }
724
725 $curies = $response->get_curies();
726 $used_curies = array();
727
728 foreach ( $links as $rel => $items ) {
729
730 // Convert $rel URIs to their compact versions if they exist.
731 foreach ( $curies as $curie ) {
732 $href_prefix = substr( $curie['href'], 0, strpos( $curie['href'], '{rel}' ) );
733 if ( ! str_starts_with( $rel, $href_prefix ) ) {
734 continue;
735 }
736
737 // Relation now changes from '$uri' to '$curie:$relation'.
738 $rel_regex = str_replace( '\{rel\}', '(.+)', preg_quote( $curie['href'], '!' ) );
739 preg_match( '!' . $rel_regex . '!', $rel, $matches );
740 if ( $matches ) {
741 $new_rel = $curie['name'] . ':' . $matches[1];
742 $used_curies[ $curie['name'] ] = $curie;
743 $links[ $new_rel ] = $items;
744 unset( $links[ $rel ] );
745 break;
746 }
747 }
748 }
749
750 // Push the curies onto the start of the links array.
751 if ( $used_curies ) {
752 $links['curies'] = array_values( $used_curies );
753 }
754
755 return $links;
756 }
757
758 /**
759 * Embeds the links from the data into the request.
760 *
761 * @since 4.4.0
762 * @since 5.4.0 The `$embed` parameter can now contain a list of link relations to include.
763 *
764 * @param array $data Data from the request.
765 * @param bool|string[] $embed Whether to embed all links or a filtered list of link relations.
766 * Default true.
767 * @return array {
768 * Data with sub-requests embedded.
769 *
770 * @type array $_links Links.
771 * @type array $_embedded Embedded objects.
772 * }
773 */
774 protected function embed_links( $data, $embed = true ) {
775 if ( empty( $data['_links'] ) ) {
776 return $data;
777 }
778
779 $embedded = array();
780
781 foreach ( $data['_links'] as $rel => $links ) {
782 /*
783 * If a list of relations was specified, and the link relation
784 * is not in the list of allowed relations, don't process the link.
785 */
786 if ( is_array( $embed ) && ! in_array( $rel, $embed, true ) ) {
787 continue;
788 }
789
790 $embeds = array();
791
792 foreach ( $links as $item ) {
793 // Determine if the link is embeddable.
794 if ( empty( $item['embeddable'] ) ) {
795 // Ensure we keep the same order.
796 $embeds[] = array();
797 continue;
798 }
799
800 if ( ! array_key_exists( $item['href'], $this->embed_cache ) ) {
801 // Run through our internal routing and serve.
802 $request = WP_REST_Request::from_url( $item['href'] );
803 if ( ! $request ) {
804 $embeds[] = array();
805 continue;
806 }
807
808 // Embedded resources get passed context=embed.
809 if ( empty( $request['context'] ) ) {
810 $request['context'] = 'embed';
811 }
812
813 if ( empty( $request['per_page'] ) ) {
814 $matched = $this->match_request_to_handler( $request );
815 if ( ! is_wp_error( $matched ) && isset( $matched[1]['args']['per_page']['maximum'] ) ) {
816 $request['per_page'] = (int) $matched[1]['args']['per_page']['maximum'];
817 }
818 }
819
820 $response = $this->dispatch( $request );
821
822 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
823 $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $this, $request );
824
825 $this->embed_cache[ $item['href'] ] = $this->response_to_data( $response, false );
826 }
827
828 $embeds[] = $this->embed_cache[ $item['href'] ];
829 }
830
831 // Determine if any real links were found.
832 $has_links = count( array_filter( $embeds ) );
833
834 if ( $has_links ) {
835 $embedded[ $rel ] = $embeds;
836 }
837 }
838
839 if ( ! empty( $embedded ) ) {
840 $data['_embedded'] = $embedded;
841 }
842
843 return $data;
844 }
845
846 /**
847 * Wraps the response in an envelope.
848 *
849 * The enveloping technique is used to work around browser/client
850 * compatibility issues. Essentially, it converts the full HTTP response to
851 * data instead.
852 *
853 * @since 4.4.0
854 * @since 6.0.0 The `$embed` parameter can now contain a list of link relations to include.
855 *
856 * @param WP_REST_Response $response Response object.
857 * @param bool|string[] $embed Whether to embed all links, a filtered list of link relations, or no links.
858 * @return WP_REST_Response New response with wrapped data
859 */
860 public function envelope_response( $response, $embed ) {
861 $envelope = array(
862 'body' => $this->response_to_data( $response, $embed ),
863 'status' => $response->get_status(),
864 'headers' => $response->get_headers(),
865 );
866
867 /**
868 * Filters the enveloped form of a REST API response.
869 *
870 * @since 4.4.0
871 *
872 * @param array $envelope {
873 * Envelope data.
874 *
875 * @type array $body Response data.
876 * @type int $status The 3-digit HTTP status code.
877 * @type array $headers Map of header name to header value.
878 * }
879 * @param WP_REST_Response $response Original response data.
880 */
881 $envelope = apply_filters( 'rest_envelope_response', $envelope, $response );
882
883 // Ensure it's still a response and return.
884 return rest_ensure_response( $envelope );
885 }
886
887 /**
888 * Registers a route to the server.
889 *
890 * @since 4.4.0
891 *
892 * @param string $route_namespace Namespace.
893 * @param string $route The REST route.
894 * @param array $route_args Route arguments.
895 * @param bool $override Optional. Whether the route should be overridden if it already exists.
896 * Default false.
897 */
898 public function register_route( $route_namespace, $route, $route_args, $override = false ) {
899 if ( ! isset( $this->namespaces[ $route_namespace ] ) ) {
900 $this->namespaces[ $route_namespace ] = array();
901
902 $this->register_route(
903 $route_namespace,
904 '/' . $route_namespace,
905 array(
906 array(
907 'methods' => self::READABLE,
908 'callback' => array( $this, 'get_namespace_index' ),
909 'args' => array(
910 'namespace' => array(
911 'default' => $route_namespace,
912 ),
913 'context' => array(
914 'default' => 'view',
915 ),
916 ),
917 ),
918 )
919 );
920 }
921
922 // Associative to avoid double-registration.
923 $this->namespaces[ $route_namespace ][ $route ] = true;
924
925 $route_args['namespace'] = $route_namespace;
926
927 if ( $override || empty( $this->endpoints[ $route ] ) ) {
928 $this->endpoints[ $route ] = $route_args;
929 } else {
930 $this->endpoints[ $route ] = array_merge( $this->endpoints[ $route ], $route_args );
931 }
932 }
933
934 /**
935 * Retrieves the route map.
936 *
937 * The route map is an associative array with path regexes as the keys. The
938 * value is an indexed array with the callback function/method as the first
939 * item, and a bitmask of HTTP methods as the second item (see the class
940 * constants).
941 *
942 * Each route can be mapped to more than one callback by using an array of
943 * the indexed arrays. This allows mapping e.g. GET requests to one callback
944 * and POST requests to another.
945 *
946 * Note that the path regexes (array keys) must have @ escaped, as this is
947 * used as the delimiter with preg_match()
948 *
949 * @since 4.4.0
950 * @since 5.4.0 Added `$route_namespace` parameter.
951 *
952 * @param string $route_namespace Optionally, only return routes in the given namespace.
953 * @return array `'/path/regex' => array( $callback, $bitmask )` or
954 * `'/path/regex' => array( array( $callback, $bitmask ), ...)`.
955 */
956 public function get_routes( $route_namespace = '' ) {
957 $endpoints = $this->endpoints;
958
959 if ( $route_namespace ) {
960 $endpoints = wp_list_filter( $endpoints, array( 'namespace' => $route_namespace ) );
961 }
962
963 /**
964 * Filters the array of available REST API endpoints.
965 *
966 * @since 4.4.0
967 *
968 * @param array $endpoints The available endpoints. An array of matching regex patterns, each mapped
969 * to an array of callbacks for the endpoint. These take the format
970 * `'/path/regex' => array( $callback, $bitmask )` or
971 * `'/path/regex' => array( array( $callback, $bitmask ).
972 */
973 $endpoints = apply_filters( 'rest_endpoints', $endpoints );
974
975 // Normalize the endpoints.
976 $defaults = array(
977 'methods' => '',
978 'accept_json' => false,
979 'accept_raw' => false,
980 'show_in_index' => true,
981 'args' => array(),
982 );
983
984 foreach ( $endpoints as $route => &$handlers ) {
985
986 if ( isset( $handlers['callback'] ) ) {
987 // Single endpoint, add one deeper.
988 $handlers = array( $handlers );
989 }
990
991 if ( ! isset( $this->route_options[ $route ] ) ) {
992 $this->route_options[ $route ] = array();
993 }
994
995 foreach ( $handlers as $key => &$handler ) {
996
997 if ( ! is_numeric( $key ) ) {
998 // Route option, move it to the options.
999 $this->route_options[ $route ][ $key ] = $handler;
1000 unset( $handlers[ $key ] );
1001 continue;
1002 }
1003
1004 $handler = wp_parse_args( $handler, $defaults );
1005
1006 // Allow comma-separated HTTP methods.
1007 if ( is_string( $handler['methods'] ) ) {
1008 $methods = explode( ',', $handler['methods'] );
1009 } elseif ( is_array( $handler['methods'] ) ) {
1010 $methods = $handler['methods'];
1011 } else {
1012 $methods = array();
1013 }
1014
1015 $handler['methods'] = array();
1016
1017 foreach ( $methods as $method ) {
1018 $method = strtoupper( trim( $method ) );
1019 $handler['methods'][ $method ] = true;
1020 }
1021 }
1022 }
1023
1024 return $endpoints;
1025 }
1026
1027 /**
1028 * Retrieves namespaces registered on the server.
1029 *
1030 * @since 4.4.0
1031 *
1032 * @return string[] List of registered namespaces.
1033 */
1034 public function get_namespaces() {
1035 return array_keys( $this->namespaces );
1036 }
1037
1038 /**
1039 * Retrieves specified options for a route.
1040 *
1041 * @since 4.4.0
1042 *
1043 * @param string $route Route pattern to fetch options for.
1044 * @return array|null Data as an associative array if found, or null if not found.
1045 */
1046 public function get_route_options( $route ) {
1047 if ( ! isset( $this->route_options[ $route ] ) ) {
1048 return null;
1049 }
1050
1051 return $this->route_options[ $route ];
1052 }
1053
1054 /**
1055 * Matches the request to a callback and call it.
1056 *
1057 * @since 4.4.0
1058 *
1059 * @param WP_REST_Request $request Request to attempt dispatching.
1060 * @return WP_REST_Response Response returned by the callback.
1061 */
1062 public function dispatch( $request ) {
1063 $this->dispatching_requests[] = $request;
1064
1065 /**
1066 * Filters the pre-calculated result of a REST API dispatch request.
1067 *
1068 * Allow hijacking the request before dispatching by returning a non-empty. The returned value
1069 * will be used to serve the request instead.
1070 *
1071 * @since 4.4.0
1072 *
1073 * @param mixed $result Response to replace the requested version with. Can be anything
1074 * a normal endpoint can return, or null to not hijack the request.
1075 * @param WP_REST_Server $server Server instance.
1076 * @param WP_REST_Request $request Request used to generate the response.
1077 */
1078 $result = apply_filters( 'rest_pre_dispatch', null, $this, $request );
1079
1080 if ( ! empty( $result ) ) {
1081
1082 // Normalize to either WP_Error or WP_REST_Response...
1083 $result = rest_ensure_response( $result );
1084
1085 // ...then convert WP_Error across.
1086 if ( is_wp_error( $result ) ) {
1087 $result = $this->error_to_response( $result );
1088 }
1089
1090 array_pop( $this->dispatching_requests );
1091 return $result;
1092 }
1093
1094 $error = null;
1095 $matched = $this->match_request_to_handler( $request );
1096
1097 if ( is_wp_error( $matched ) ) {
1098 $response = $this->error_to_response( $matched );
1099 array_pop( $this->dispatching_requests );
1100 return $response;
1101 }
1102
1103 list( $route, $handler ) = $matched;
1104
1105 if ( ! is_callable( $handler['callback'] ) ) {
1106 $error = new WP_Error(
1107 'rest_invalid_handler',
1108 __( 'The handler for the route is invalid.' ),
1109 array( 'status' => 500 )
1110 );
1111 }
1112
1113 if ( ! is_wp_error( $error ) ) {
1114 $check_required = $request->has_valid_params();
1115 if ( is_wp_error( $check_required ) ) {
1116 $error = $check_required;
1117 } else {
1118 $check_sanitized = $request->sanitize_params();
1119 if ( is_wp_error( $check_sanitized ) ) {
1120 $error = $check_sanitized;
1121 }
1122 }
1123 }
1124
1125 $response = $this->respond_to_request( $request, $route, $handler, $error );
1126 array_pop( $this->dispatching_requests );
1127 return $response;
1128 }
1129
1130 /**
1131 * Returns whether the REST server is currently dispatching / responding to a request.
1132 *
1133 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
1134 *
1135 * @since 6.5.0
1136 *
1137 * @return bool Whether the REST server is currently handling a request.
1138 */
1139 public function is_dispatching() {
1140 return (bool) $this->dispatching_requests;
1141 }
1142
1143 /**
1144 * Matches a request object to its handler.
1145 *
1146 * @access private
1147 * @since 5.6.0
1148 *
1149 * @param WP_REST_Request $request The request object.
1150 * @return array|WP_Error The route and request handler on success or a WP_Error instance if no handler was found.
1151 */
1152 protected function match_request_to_handler( $request ) {
1153 $method = $request->get_method();
1154 $path = $request->get_route();
1155
1156 $with_namespace = array();
1157
1158 foreach ( $this->get_namespaces() as $namespace ) {
1159 if ( str_starts_with( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
1160 $with_namespace[] = $this->get_routes( $namespace );
1161 }
1162 }
1163
1164 if ( $with_namespace ) {
1165 $routes = array_merge( ...$with_namespace );
1166 } else {
1167 $routes = $this->get_routes();
1168 }
1169
1170 foreach ( $routes as $route => $handlers ) {
1171 $match = preg_match( '@^' . $route . '$@i', $path, $matches );
1172
1173 if ( ! $match ) {
1174 continue;
1175 }
1176
1177 $args = array();
1178
1179 foreach ( $matches as $param => $value ) {
1180 if ( ! is_int( $param ) ) {
1181 $args[ $param ] = $value;
1182 }
1183 }
1184
1185 foreach ( $handlers as $handler ) {
1186 $callback = $handler['callback'];
1187
1188 // Fallback to GET method if no HEAD method is registered.
1189 $checked_method = $method;
1190 if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
1191 $checked_method = 'GET';
1192 }
1193 if ( empty( $handler['methods'][ $checked_method ] ) ) {
1194 continue;
1195 }
1196
1197 if ( ! is_callable( $callback ) ) {
1198 return array( $route, $handler );
1199 }
1200
1201 $request->set_url_params( $args );
1202 $request->set_attributes( $handler );
1203
1204 $defaults = array();
1205
1206 foreach ( $handler['args'] as $arg => $options ) {
1207 if ( isset( $options['default'] ) ) {
1208 $defaults[ $arg ] = $options['default'];
1209 }
1210 }
1211
1212 $request->set_default_params( $defaults );
1213
1214 return array( $route, $handler );
1215 }
1216 }
1217
1218 return new WP_Error(
1219 'rest_no_route',
1220 __( 'No route was found matching the URL and request method.' ),
1221 array( 'status' => 404 )
1222 );
1223 }
1224
1225 /**
1226 * Dispatches the request to the callback handler.
1227 *
1228 * @access private
1229 * @since 5.6.0
1230 *
1231 * @param WP_REST_Request $request The request object.
1232 * @param string $route The matched route regex.
1233 * @param array $handler The matched route handler.
1234 * @param WP_Error|null $response The current error object if any.
1235 * @return WP_REST_Response
1236 */
1237 protected function respond_to_request( $request, $route, $handler, $response ) {
1238 /**
1239 * Filters the response before executing any REST API callbacks.
1240 *
1241 * Allows plugins to perform additional validation after a
1242 * request is initialized and matched to a registered route,
1243 * but before it is executed.
1244 *
1245 * Note that this filter will not be called for requests that
1246 * fail to authenticate or match to a registered route.
1247 *
1248 * @since 4.7.0
1249 *
1250 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
1251 * Usually a WP_REST_Response or WP_Error.
1252 * @param array $handler Route handler used for the request.
1253 * @param WP_REST_Request $request Request used to generate the response.
1254 */
1255 $response = apply_filters( 'rest_request_before_callbacks', $response, $handler, $request );
1256
1257 // Check permission specified on the route.
1258 if ( ! is_wp_error( $response ) && ! empty( $handler['permission_callback'] ) ) {
1259 $permission = call_user_func( $handler['permission_callback'], $request );
1260
1261 if ( is_wp_error( $permission ) ) {
1262 $response = $permission;
1263 } elseif ( false === $permission || null === $permission ) {
1264 $response = new WP_Error(
1265 'rest_forbidden',
1266 __( 'Sorry, you are not allowed to do that.' ),
1267 array( 'status' => rest_authorization_required_code() )
1268 );
1269 }
1270 }
1271
1272 if ( ! is_wp_error( $response ) ) {
1273 /**
1274 * Filters the REST API dispatch request result.
1275 *
1276 * Allow plugins to override dispatching the request.
1277 *
1278 * @since 4.4.0
1279 * @since 4.5.0 Added `$route` and `$handler` parameters.
1280 *
1281 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
1282 * @param WP_REST_Request $request Request used to generate the response.
1283 * @param string $route Route matched for the request.
1284 * @param array $handler Route handler used for the request.
1285 */
1286 $dispatch_result = apply_filters( 'rest_dispatch_request', null, $request, $route, $handler );
1287
1288 // Allow plugins to halt the request via this filter.
1289 if ( null !== $dispatch_result ) {
1290 $response = $dispatch_result;
1291 } else {
1292 $response = call_user_func( $handler['callback'], $request );
1293 }
1294 }
1295
1296 /**
1297 * Filters the response immediately after executing any REST API
1298 * callbacks.
1299 *
1300 * Allows plugins to perform any needed cleanup, for example,
1301 * to undo changes made during the {@see 'rest_request_before_callbacks'}
1302 * filter.
1303 *
1304 * Note that this filter will not be called for requests that
1305 * fail to authenticate or match to a registered route.
1306 *
1307 * Note that an endpoint's `permission_callback` can still be
1308 * called after this filter - see `rest_send_allow_header()`.
1309 *
1310 * @since 4.7.0
1311 *
1312 * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response Result to send to the client.
1313 * Usually a WP_REST_Response or WP_Error.
1314 * @param array $handler Route handler used for the request.
1315 * @param WP_REST_Request $request Request used to generate the response.
1316 */
1317 $response = apply_filters( 'rest_request_after_callbacks', $response, $handler, $request );
1318
1319 if ( is_wp_error( $response ) ) {
1320 $response = $this->error_to_response( $response );
1321 } else {
1322 $response = rest_ensure_response( $response );
1323 }
1324
1325 $response->set_matched_route( $route );
1326 $response->set_matched_handler( $handler );
1327
1328 return $response;
1329 }
1330
1331 /**
1332 * Returns if an error occurred during most recent JSON encode/decode.
1333 *
1334 * Strings to be translated will be in format like
1335 * "Encoding error: Maximum stack depth exceeded".
1336 *
1337 * @since 4.4.0
1338 *
1339 * @return false|string Boolean false or string error message.
1340 */
1341 protected function get_json_last_error() {
1342 if ( JSON_ERROR_NONE === json_last_error() ) {
1343 return false;
1344 }
1345
1346 return json_last_error_msg();
1347 }
1348
1349 /**
1350 * Retrieves the site index.
1351 *
1352 * This endpoint describes the capabilities of the site.
1353 *
1354 * @since 4.4.0
1355 *
1356 * @param WP_REST_Request $request Request data.
1357 * @return WP_REST_Response The API root index data.
1358 */
1359 public function get_index( $request ) {
1360 // General site data.
1361 $available = array(
1362 'name' => get_option( 'blogname' ),
1363 'description' => get_option( 'blogdescription' ),
1364 'url' => get_option( 'siteurl' ),
1365 'home' => home_url(),
1366 'gmt_offset' => get_option( 'gmt_offset' ),
1367 'timezone_string' => get_option( 'timezone_string' ),
1368 'page_for_posts' => (int) get_option( 'page_for_posts' ),
1369 'page_on_front' => (int) get_option( 'page_on_front' ),
1370 'show_on_front' => get_option( 'show_on_front' ),
1371 'namespaces' => array_keys( $this->namespaces ),
1372 'authentication' => array(),
1373 'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
1374 );
1375
1376 $response = new WP_REST_Response( $available );
1377
1378 $fields = isset( $request['_fields'] ) ? $request['_fields'] : '';
1379 $fields = wp_parse_list( $fields );
1380 if ( empty( $fields ) ) {
1381 $fields[] = '_links';
1382 }
1383
1384 if ( $request->has_param( '_embed' ) ) {
1385 $fields[] = '_embedded';
1386 }
1387
1388 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
1389 $response->add_link( 'help', 'https://developer.wordpress.org/rest-api/' );
1390 $this->add_active_theme_link_to_index( $response );
1391 $this->add_site_logo_to_index( $response );
1392 $this->add_site_icon_to_index( $response );
1393 } else {
1394 if ( rest_is_field_included( 'site_logo', $fields ) ) {
1395 $this->add_site_logo_to_index( $response );
1396 }
1397 if ( rest_is_field_included( 'site_icon', $fields ) || rest_is_field_included( 'site_icon_url', $fields ) ) {
1398 $this->add_site_icon_to_index( $response );
1399 }
1400 }
1401
1402 /**
1403 * Filters the REST API root index data.
1404 *
1405 * This contains the data describing the API. This includes information
1406 * about supported authentication schemes, supported namespaces, routes
1407 * available on the API, and a small amount of data about the site.
1408 *
1409 * @since 4.4.0
1410 * @since 6.0.0 Added `$request` parameter.
1411 *
1412 * @param WP_REST_Response $response Response data.
1413 * @param WP_REST_Request $request Request data.
1414 */
1415 return apply_filters( 'rest_index', $response, $request );
1416 }
1417
1418 /**
1419 * Adds a link to the active theme for users who have proper permissions.
1420 *
1421 * @since 5.7.0
1422 *
1423 * @param WP_REST_Response $response REST API response.
1424 */
1425 protected function add_active_theme_link_to_index( WP_REST_Response $response ) {
1426 $should_add = current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' );
1427
1428 if ( ! $should_add && current_user_can( 'edit_posts' ) ) {
1429 $should_add = true;
1430 }
1431
1432 if ( ! $should_add ) {
1433 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
1434 if ( current_user_can( $post_type->cap->edit_posts ) ) {
1435 $should_add = true;
1436 break;
1437 }
1438 }
1439 }
1440
1441 if ( $should_add ) {
1442 $theme = wp_get_theme();
1443 $response->add_link( 'https://api.w.org/active-theme', rest_url( 'wp/v2/themes/' . $theme->get_stylesheet() ) );
1444 }
1445 }
1446
1447 /**
1448 * Exposes the site logo through the WordPress REST API.
1449 *
1450 * This is used for fetching this information when user has no rights
1451 * to update settings.
1452 *
1453 * @since 5.8.0
1454 *
1455 * @param WP_REST_Response $response REST API response.
1456 */
1457 protected function add_site_logo_to_index( WP_REST_Response $response ) {
1458 $site_logo_id = get_theme_mod( 'custom_logo', 0 );
1459
1460 $this->add_image_to_index( $response, $site_logo_id, 'site_logo' );
1461 }
1462
1463 /**
1464 * Exposes the site icon through the WordPress REST API.
1465 *
1466 * This is used for fetching this information when user has no rights
1467 * to update settings.
1468 *
1469 * @since 5.9.0
1470 *
1471 * @param WP_REST_Response $response REST API response.
1472 */
1473 protected function add_site_icon_to_index( WP_REST_Response $response ) {
1474 $site_icon_id = get_option( 'site_icon', 0 );
1475
1476 $this->add_image_to_index( $response, $site_icon_id, 'site_icon' );
1477
1478 $response->data['site_icon_url'] = get_site_icon_url();
1479 }
1480
1481 /**
1482 * Exposes an image through the WordPress REST API.
1483 * This is used for fetching this information when user has no rights
1484 * to update settings.
1485 *
1486 * @since 5.9.0
1487 *
1488 * @param WP_REST_Response $response REST API response.
1489 * @param int $image_id Image attachment ID.
1490 * @param string $type Type of Image.
1491 */
1492 protected function add_image_to_index( WP_REST_Response $response, $image_id, $type ) {
1493 $response->data[ $type ] = (int) $image_id;
1494 if ( $image_id ) {
1495 $response->add_link(
1496 'https://api.w.org/featuredmedia',
1497 rest_url( rest_get_route_for_post( $image_id ) ),
1498 array(
1499 'embeddable' => true,
1500 'type' => $type,
1501 )
1502 );
1503 }
1504 }
1505
1506 /**
1507 * Retrieves the index for a namespace.
1508 *
1509 * @since 4.4.0
1510 *
1511 * @param WP_REST_Request $request REST request instance.
1512 * @return WP_REST_Response|WP_Error WP_REST_Response instance if the index was found,
1513 * WP_Error if the namespace isn't set.
1514 */
1515 public function get_namespace_index( $request ) {
1516 $namespace = $request['namespace'];
1517
1518 if ( ! isset( $this->namespaces[ $namespace ] ) ) {
1519 return new WP_Error(
1520 'rest_invalid_namespace',
1521 __( 'The specified namespace could not be found.' ),
1522 array( 'status' => 404 )
1523 );
1524 }
1525
1526 $routes = $this->namespaces[ $namespace ];
1527 $endpoints = array_intersect_key( $this->get_routes(), $routes );
1528
1529 $data = array(
1530 'namespace' => $namespace,
1531 'routes' => $this->get_data_for_routes( $endpoints, $request['context'] ),
1532 );
1533 $response = rest_ensure_response( $data );
1534
1535 // Link to the root index.
1536 $response->add_link( 'up', rest_url( '/' ) );
1537
1538 /**
1539 * Filters the REST API namespace index data.
1540 *
1541 * This typically is just the route data for the namespace, but you can
1542 * add any data you'd like here.
1543 *
1544 * @since 4.4.0
1545 *
1546 * @param WP_REST_Response $response Response data.
1547 * @param WP_REST_Request $request Request data. The namespace is passed as the 'namespace' parameter.
1548 */
1549 return apply_filters( 'rest_namespace_index', $response, $request );
1550 }
1551
1552 /**
1553 * Retrieves the publicly-visible data for routes.
1554 *
1555 * @since 4.4.0
1556 *
1557 * @param array $routes Routes to get data for.
1558 * @param string $context Optional. Context for data. Accepts 'view' or 'help'. Default 'view'.
1559 * @return array[] Route data to expose in indexes, keyed by route.
1560 */
1561 public function get_data_for_routes( $routes, $context = 'view' ) {
1562 $available = array();
1563
1564 // Find the available routes.
1565 foreach ( $routes as $route => $callbacks ) {
1566 $data = $this->get_data_for_route( $route, $callbacks, $context );
1567 if ( empty( $data ) ) {
1568 continue;
1569 }
1570
1571 /**
1572 * Filters the publicly-visible data for a single REST API route.
1573 *
1574 * @since 4.4.0
1575 *
1576 * @param array $data Publicly-visible data for the route.
1577 */
1578 $available[ $route ] = apply_filters( 'rest_endpoints_description', $data );
1579 }
1580
1581 /**
1582 * Filters the publicly-visible data for REST API routes.
1583 *
1584 * This data is exposed on indexes and can be used by clients or
1585 * developers to investigate the site and find out how to use it. It
1586 * acts as a form of self-documentation.
1587 *
1588 * @since 4.4.0
1589 *
1590 * @param array[] $available Route data to expose in indexes, keyed by route.
1591 * @param array $routes Internal route data as an associative array.
1592 */
1593 return apply_filters( 'rest_route_data', $available, $routes );
1594 }
1595
1596 /**
1597 * Retrieves publicly-visible data for the route.
1598 *
1599 * @since 4.4.0
1600 *
1601 * @param string $route Route to get data for.
1602 * @param array $callbacks Callbacks to convert to data.
1603 * @param string $context Optional. Context for the data. Accepts 'view' or 'help'. Default 'view'.
1604 * @return array|null Data for the route, or null if no publicly-visible data.
1605 */
1606 public function get_data_for_route( $route, $callbacks, $context = 'view' ) {
1607 $data = array(
1608 'namespace' => '',
1609 'methods' => array(),
1610 'endpoints' => array(),
1611 );
1612
1613 $allow_batch = false;
1614
1615 if ( isset( $this->route_options[ $route ] ) ) {
1616 $options = $this->route_options[ $route ];
1617
1618 if ( isset( $options['namespace'] ) ) {
1619 $data['namespace'] = $options['namespace'];
1620 }
1621
1622 $allow_batch = isset( $options['allow_batch'] ) ? $options['allow_batch'] : false;
1623
1624 if ( isset( $options['schema'] ) && 'help' === $context ) {
1625 $data['schema'] = call_user_func( $options['schema'] );
1626 }
1627 }
1628
1629 $allowed_schema_keywords = array_flip( rest_get_allowed_schema_keywords() );
1630
1631 $route = preg_replace( '#\(\?P<(\w+?)>.*?\)#', '{$1}', $route );
1632
1633 foreach ( $callbacks as $callback ) {
1634 // Skip to the next route if any callback is hidden.
1635 if ( empty( $callback['show_in_index'] ) ) {
1636 continue;
1637 }
1638
1639 $data['methods'] = array_merge( $data['methods'], array_keys( $callback['methods'] ) );
1640 $endpoint_data = array(
1641 'methods' => array_keys( $callback['methods'] ),
1642 );
1643
1644 $callback_batch = isset( $callback['allow_batch'] ) ? $callback['allow_batch'] : $allow_batch;
1645
1646 if ( $callback_batch ) {
1647 $endpoint_data['allow_batch'] = $callback_batch;
1648 }
1649
1650 if ( isset( $callback['args'] ) ) {
1651 $endpoint_data['args'] = array();
1652
1653 foreach ( $callback['args'] as $key => $opts ) {
1654 if ( is_string( $opts ) ) {
1655 $opts = array( $opts => 0 );
1656 } elseif ( ! is_array( $opts ) ) {
1657 $opts = array();
1658 }
1659 $arg_data = array_intersect_key( $opts, $allowed_schema_keywords );
1660 $arg_data['required'] = ! empty( $opts['required'] );
1661
1662 $endpoint_data['args'][ $key ] = $arg_data;
1663 }
1664 }
1665
1666 $data['endpoints'][] = $endpoint_data;
1667
1668 // For non-variable routes, generate links.
1669 if ( ! str_contains( $route, '{' ) ) {
1670 $data['_links'] = array(
1671 'self' => array(
1672 array(
1673 'href' => rest_url( $route ),
1674 ),
1675 ),
1676 );
1677 }
1678 }
1679
1680 if ( empty( $data['methods'] ) ) {
1681 // No methods supported, hide the route.
1682 return null;
1683 }
1684
1685 return $data;
1686 }
1687
1688 /**
1689 * Gets the maximum number of requests that can be included in a batch.
1690 *
1691 * @since 5.6.0
1692 *
1693 * @return int The maximum requests.
1694 */
1695 protected function get_max_batch_size() {
1696 /**
1697 * Filters the maximum number of REST API requests that can be included in a batch.
1698 *
1699 * @since 5.6.0
1700 *
1701 * @param int $max_size The maximum size.
1702 */
1703 return apply_filters( 'rest_get_max_batch_size', 25 );
1704 }
1705
1706 /**
1707 * Serves the batch/v1 request.
1708 *
1709 * @since 5.6.0
1710 *
1711 * @param WP_REST_Request $batch_request The batch request object.
1712 * @return WP_REST_Response The generated response object.
1713 */
1714 public function serve_batch_request_v1( WP_REST_Request $batch_request ) {
1715 $requests = array();
1716
1717 foreach ( $batch_request['requests'] as $args ) {
1718 $parsed_url = wp_parse_url( $args['path'] );
1719
1720 if ( false === $parsed_url ) {
1721 $requests[] = new WP_Error( 'parse_path_failed', __( 'Could not parse the path.' ), array( 'status' => 400 ) );
1722
1723 continue;
1724 }
1725
1726 $single_request = new WP_REST_Request( isset( $args['method'] ) ? $args['method'] : 'POST', $parsed_url['path'] );
1727
1728 if ( ! empty( $parsed_url['query'] ) ) {
1729 $query_args = array();
1730 wp_parse_str( $parsed_url['query'], $query_args );
1731 $single_request->set_query_params( $query_args );
1732 }
1733
1734 if ( ! empty( $args['body'] ) ) {
1735 $single_request->set_body_params( $args['body'] );
1736 }
1737
1738 if ( ! empty( $args['headers'] ) ) {
1739 $single_request->set_headers( $args['headers'] );
1740 }
1741
1742 $requests[] = $single_request;
1743 }
1744
1745 $matches = array();
1746 $validation = array();
1747 $has_error = false;
1748
1749 foreach ( $requests as $single_request ) {
1750 if ( is_wp_error( $single_request ) ) {
1751 $has_error = true;
1752 $validation[] = $single_request;
1753 continue;
1754 }
1755
1756 $match = $this->match_request_to_handler( $single_request );
1757 $matches[] = $match;
1758 $error = null;
1759
1760 if ( is_wp_error( $match ) ) {
1761 $error = $match;
1762 }
1763
1764 if ( ! $error ) {
1765 list( $route, $handler ) = $match;
1766
1767 if ( isset( $handler['allow_batch'] ) ) {
1768 $allow_batch = $handler['allow_batch'];
1769 } else {
1770 $route_options = $this->get_route_options( $route );
1771 $allow_batch = isset( $route_options['allow_batch'] ) ? $route_options['allow_batch'] : false;
1772 }
1773
1774 if ( ! is_array( $allow_batch ) || empty( $allow_batch['v1'] ) ) {
1775 $error = new WP_Error(
1776 'rest_batch_not_allowed',
1777 __( 'The requested route does not support batch requests.' ),
1778 array( 'status' => 400 )
1779 );
1780 }
1781 }
1782
1783 if ( ! $error ) {
1784 $check_required = $single_request->has_valid_params();
1785 if ( is_wp_error( $check_required ) ) {
1786 $error = $check_required;
1787 }
1788 }
1789
1790 if ( ! $error ) {
1791 $check_sanitized = $single_request->sanitize_params();
1792 if ( is_wp_error( $check_sanitized ) ) {
1793 $error = $check_sanitized;
1794 }
1795 }
1796
1797 if ( $error ) {
1798 $has_error = true;
1799 $validation[] = $error;
1800 } else {
1801 $validation[] = true;
1802 }
1803 }
1804
1805 $responses = array();
1806
1807 if ( $has_error && 'require-all-validate' === $batch_request['validation'] ) {
1808 foreach ( $validation as $valid ) {
1809 if ( is_wp_error( $valid ) ) {
1810 $responses[] = $this->envelope_response( $this->error_to_response( $valid ), false )->get_data();
1811 } else {
1812 $responses[] = null;
1813 }
1814 }
1815
1816 return new WP_REST_Response(
1817 array(
1818 'failed' => 'validation',
1819 'responses' => $responses,
1820 ),
1821 WP_Http::MULTI_STATUS
1822 );
1823 }
1824
1825 foreach ( $requests as $i => $single_request ) {
1826 if ( is_wp_error( $single_request ) ) {
1827 $result = $this->error_to_response( $single_request );
1828 $responses[] = $this->envelope_response( $result, false )->get_data();
1829 continue;
1830 }
1831
1832 $clean_request = clone $single_request;
1833 $clean_request->set_url_params( array() );
1834 $clean_request->set_attributes( array() );
1835 $clean_request->set_default_params( array() );
1836
1837 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
1838 $result = apply_filters( 'rest_pre_dispatch', null, $this, $clean_request );
1839
1840 if ( empty( $result ) ) {
1841 $match = $matches[ $i ];
1842 $error = null;
1843
1844 if ( is_wp_error( $validation[ $i ] ) ) {
1845 $error = $validation[ $i ];
1846 }
1847
1848 if ( is_wp_error( $match ) ) {
1849 $result = $this->error_to_response( $match );
1850 } else {
1851 list( $route, $handler ) = $match;
1852
1853 if ( ! $error && ! is_callable( $handler['callback'] ) ) {
1854 $error = new WP_Error(
1855 'rest_invalid_handler',
1856 __( 'The handler for the route is invalid' ),
1857 array( 'status' => 500 )
1858 );
1859 }
1860
1861 $result = $this->respond_to_request( $single_request, $route, $handler, $error );
1862 }
1863 }
1864
1865 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
1866 $result = apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $single_request );
1867
1868 $responses[] = $this->envelope_response( $result, false )->get_data();
1869 }
1870
1871 return new WP_REST_Response( array( 'responses' => $responses ), WP_Http::MULTI_STATUS );
1872 }
1873
1874 /**
1875 * Sends an HTTP status code.
1876 *
1877 * @since 4.4.0
1878 *
1879 * @param int $code HTTP status.
1880 */
1881 protected function set_status( $code ) {
1882 status_header( $code );
1883 }
1884
1885 /**
1886 * Sends an HTTP header.
1887 *
1888 * @since 4.4.0
1889 *
1890 * @param string $key Header key.
1891 * @param string $value Header value.
1892 */
1893 public function send_header( $key, $value ) {
1894 /*
1895 * Sanitize as per RFC2616 (Section 4.2):
1896 *
1897 * Any LWS that occurs between field-content MAY be replaced with a
1898 * single SP before interpreting the field value or forwarding the
1899 * message downstream.
1900 */
1901 $value = preg_replace( '/\s+/', ' ', $value );
1902 header( sprintf( '%s: %s', $key, $value ) );
1903 }
1904
1905 /**
1906 * Sends multiple HTTP headers.
1907 *
1908 * @since 4.4.0
1909 *
1910 * @param array $headers Map of header name to header value.
1911 */
1912 public function send_headers( $headers ) {
1913 foreach ( $headers as $key => $value ) {
1914 $this->send_header( $key, $value );
1915 }
1916 }
1917
1918 /**
1919 * Removes an HTTP header from the current response.
1920 *
1921 * @since 4.8.0
1922 *
1923 * @param string $key Header key.
1924 */
1925 public function remove_header( $key ) {
1926 header_remove( $key );
1927 }
1928
1929 /**
1930 * Retrieves the raw request entity (body).
1931 *
1932 * @since 4.4.0
1933 *
1934 * @global string $HTTP_RAW_POST_DATA Raw post data.
1935 *
1936 * @return string Raw request data.
1937 */
1938 public static function get_raw_data() {
1939 // phpcs:disable PHPCompatibility.Variables.RemovedPredefinedGlobalVariables.http_raw_post_dataDeprecatedRemoved
1940 global $HTTP_RAW_POST_DATA;
1941
1942 // $HTTP_RAW_POST_DATA was deprecated in PHP 5.6 and removed in PHP 7.0.
1943 if ( ! isset( $HTTP_RAW_POST_DATA ) ) {
1944 $HTTP_RAW_POST_DATA = file_get_contents( 'php://input' );
1945 }
1946
1947 return $HTTP_RAW_POST_DATA;
1948 // phpcs:enable
1949 }
1950
1951 /**
1952 * Extracts headers from a PHP-style $_SERVER array.
1953 *
1954 * @since 4.4.0
1955 *
1956 * @param array $server Associative array similar to `$_SERVER`.
1957 * @return array Headers extracted from the input.
1958 */
1959 public function get_headers( $server ) {
1960 $headers = array();
1961
1962 // CONTENT_* headers are not prefixed with HTTP_.
1963 $additional = array(
1964 'CONTENT_LENGTH' => true,
1965 'CONTENT_MD5' => true,
1966 'CONTENT_TYPE' => true,
1967 );
1968
1969 foreach ( $server as $key => $value ) {
1970 if ( str_starts_with( $key, 'HTTP_' ) ) {
1971 $headers[ substr( $key, 5 ) ] = $value;
1972 } elseif ( 'REDIRECT_HTTP_AUTHORIZATION' === $key && empty( $server['HTTP_AUTHORIZATION'] ) ) {
1973 /*
1974 * In some server configurations, the authorization header is passed in this alternate location.
1975 * Since it would not be passed in in both places we do not check for both headers and resolve.
1976 */
1977 $headers['AUTHORIZATION'] = $value;
1978 } elseif ( isset( $additional[ $key ] ) ) {
1979 $headers[ $key ] = $value;
1980 }
1981 }
1982
1983 return $headers;
1984 }
1985}
1986
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1