run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
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:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
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
DIR
2026-03-11 16:18:51
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:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
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
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
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:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄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 */
15define( 'REST_API_VERSION', '2.0' );
16
17/**
18 * Registers a REST API route.
19 *
20 * Note: Do not use before the {@see 'rest_api_init'} hook.
21 *
22 * @since 4.4.0
23 * @since 5.1.0 Added a `_doing_it_wrong()` notice when not called on or after the `rest_api_init` hook.
24 * @since 5.5.0 Added a `_doing_it_wrong()` notice when the required `permission_callback` argument is not set.
25 *
26 * @param string $route_namespace The first URL segment after core prefix. Should be unique to your package/plugin.
27 * @param string $route The base URL for route you are adding.
28 * @param array $args Optional. Either an array of options for the endpoint, or an array of arrays for
29 * multiple methods. Default empty array.
30 * @param bool $override Optional. If the route already exists, should we override it? True overrides,
31 * false merges (with newer overriding if duplicate keys exist). Default false.
32 * @return bool True on success, false on error.
33 */
34function register_rest_route( $route_namespace, $route, $args = array(), $override = false ) {
35 if ( empty( $route_namespace ) ) {
36 /*
37 * Non-namespaced routes are not allowed, with the exception of the main
38 * and namespace indexes. If you really need to register a
39 * non-namespaced route, call `WP_REST_Server::register_route` directly.
40 */
41 _doing_it_wrong(
42 __FUNCTION__,
43 sprintf(
44 /* translators: 1: string value of the namespace, 2: string value of the route. */
45 __( 'Routes must be namespaced with plugin or theme name and version. Instead there seems to be an empty namespace \'%1$s\' for route \'%2$s\'.' ),
46 '<code>' . $route_namespace . '</code>',
47 '<code>' . $route . '</code>'
48 ),
49 '4.4.0'
50 );
51 return false;
52 } elseif ( empty( $route ) ) {
53 _doing_it_wrong(
54 __FUNCTION__,
55 sprintf(
56 /* translators: 1: string value of the namespace, 2: string value of the route. */
57 __( 'Route must be specified. Instead within the namespace \'%1$s\', there seems to be an empty route \'%2$s\'.' ),
58 '<code>' . $route_namespace . '</code>',
59 '<code>' . $route . '</code>'
60 ),
61 '4.4.0'
62 );
63 return false;
64 }
65
66 $clean_namespace = trim( $route_namespace, '/' );
67
68 if ( $clean_namespace !== $route_namespace ) {
69 _doing_it_wrong(
70 __FUNCTION__,
71 sprintf(
72 /* translators: 1: string value of the namespace, 2: string value of the route. */
73 __( 'Namespace must not start or end with a slash. Instead namespace \'%1$s\' for route \'%2$s\' seems to contain a slash.' ),
74 '<code>' . $route_namespace . '</code>',
75 '<code>' . $route . '</code>'
76 ),
77 '5.4.2'
78 );
79 }
80
81 if ( ! did_action( 'rest_api_init' ) ) {
82 _doing_it_wrong(
83 __FUNCTION__,
84 sprintf(
85 /* translators: 1: rest_api_init, 2: string value of the route, 3: string value of the namespace. */
86 __( 'REST API routes must be registered on the %1$s action. Instead route \'%2$s\' with namespace \'%3$s\' was not registered on this action.' ),
87 '<code>rest_api_init</code>',
88 '<code>' . $route . '</code>',
89 '<code>' . $route_namespace . '</code>'
90 ),
91 '5.1.0'
92 );
93 }
94
95 if ( isset( $args['args'] ) ) {
96 $common_args = $args['args'];
97 unset( $args['args'] );
98 } else {
99 $common_args = array();
100 }
101
102 if ( isset( $args['callback'] ) ) {
103 // Upgrade a single set to multiple.
104 $args = array( $args );
105 }
106
107 $defaults = array(
108 'methods' => 'GET',
109 'callback' => null,
110 'args' => array(),
111 );
112
113 foreach ( $args as $key => &$arg_group ) {
114 if ( ! is_numeric( $key ) ) {
115 // Route option, skip here.
116 continue;
117 }
118
119 $arg_group = array_merge( $defaults, $arg_group );
120 $arg_group['args'] = array_merge( $common_args, $arg_group['args'] );
121
122 if ( ! isset( $arg_group['permission_callback'] ) ) {
123 _doing_it_wrong(
124 __FUNCTION__,
125 sprintf(
126 /* translators: 1: The REST API route being registered, 2: The argument name, 3: The suggested function name. */
127 __( 'The REST API route definition for %1$s is missing the required %2$s argument. For REST API routes that are intended to be public, use %3$s as the permission callback.' ),
128 '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>',
129 '<code>permission_callback</code>',
130 '<code>__return_true</code>'
131 ),
132 '5.5.0'
133 );
134 }
135
136 foreach ( $arg_group['args'] as $arg ) {
137 if ( ! is_array( $arg ) ) {
138 _doing_it_wrong(
139 __FUNCTION__,
140 sprintf(
141 /* translators: 1: $args, 2: The REST API route being registered. */
142 __( 'REST API %1$s should be an array of arrays. Non-array value detected for %2$s.' ),
143 '<code>$args</code>',
144 '<code>' . $clean_namespace . '/' . trim( $route, '/' ) . '</code>'
145 ),
146 '6.1.0'
147 );
148 break; // Leave the foreach loop once a non-array argument was found.
149 }
150 }
151 }
152
153 $full_route = '/' . $clean_namespace . '/' . trim( $route, '/' );
154 rest_get_server()->register_route( $clean_namespace, $full_route, $args, $override );
155 return true;
156}
157
158/**
159 * Registers a new field on an existing WordPress object type.
160 *
161 * @since 4.7.0
162 *
163 * @global array $wp_rest_additional_fields Holds registered fields, organized
164 * by object type.
165 *
166 * @param string|array $object_type Object(s) the field is being registered to,
167 * "post"|"term"|"comment" etc.
168 * @param string $attribute The attribute name.
169 * @param array $args {
170 * Optional. An array of arguments used to handle the registered field.
171 *
172 * @type callable|null $get_callback Optional. The callback function used to retrieve the field value. Default is
173 * 'null', the field will not be returned in the response. The function will
174 * be passed the prepared object data.
175 * @type callable|null $update_callback Optional. The callback function used to set and update the field value. Default
176 * is 'null', the value cannot be set or updated. The function will be passed
177 * the model object, like WP_Post.
178 * @type array|null $schema Optional. The schema for this field.
179 * Default is 'null', no schema entry will be returned.
180 * }
181 */
182function register_rest_field( $object_type, $attribute, $args = array() ) {
183 global $wp_rest_additional_fields;
184
185 $defaults = array(
186 'get_callback' => null,
187 'update_callback' => null,
188 'schema' => null,
189 );
190
191 $args = wp_parse_args( $args, $defaults );
192
193 $object_types = (array) $object_type;
194
195 foreach ( $object_types as $object_type ) {
196 $wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
197 }
198}
199
200/**
201 * Registers rewrite rules for the REST API.
202 *
203 * @since 4.4.0
204 *
205 * @see rest_api_register_rewrites()
206 * @global WP $wp Current WordPress environment instance.
207 */
208function rest_api_init() {
209 rest_api_register_rewrites();
210
211 global $wp;
212 $wp->add_query_var( 'rest_route' );
213}
214
215/**
216 * Adds REST rewrite rules.
217 *
218 * @since 4.4.0
219 *
220 * @see add_rewrite_rule()
221 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
222 */
223function rest_api_register_rewrites() {
224 global $wp_rewrite;
225
226 add_rewrite_rule( '^' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
227 add_rewrite_rule( '^' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
228 add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/?$', 'index.php?rest_route=/', 'top' );
229 add_rewrite_rule( '^' . $wp_rewrite->index . '/' . rest_get_url_prefix() . '/(.*)?', 'index.php?rest_route=/$matches[1]', 'top' );
230}
231
232/**
233 * Registers the default REST API filters.
234 *
235 * Attached to the {@see 'rest_api_init'} action
236 * to make testing and disabling these filters easier.
237 *
238 * @since 4.4.0
239 */
240function rest_api_default_filters() {
241 if ( wp_is_serving_rest_request() ) {
242 // Deprecated reporting.
243 add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
244 add_filter( 'deprecated_function_trigger_error', '__return_false' );
245 add_action( 'deprecated_argument_run', 'rest_handle_deprecated_argument', 10, 3 );
246 add_filter( 'deprecated_argument_trigger_error', '__return_false' );
247 add_action( 'doing_it_wrong_run', 'rest_handle_doing_it_wrong', 10, 3 );
248 add_filter( 'doing_it_wrong_trigger_error', '__return_false' );
249 }
250
251 // Default serving.
252 add_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
253 add_filter( 'rest_post_dispatch', 'rest_send_allow_header', 10, 3 );
254 add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 );
255
256 add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
257 add_filter( 'rest_index', 'rest_add_application_passwords_to_index' );
258}
259
260/**
261 * Registers default REST API routes.
262 *
263 * @since 4.7.0
264 */
265function create_initial_rest_routes() {
266 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
267 $controller = $post_type->get_rest_controller();
268
269 if ( ! $controller ) {
270 continue;
271 }
272
273 if ( ! $post_type->late_route_registration ) {
274 $controller->register_routes();
275 }
276
277 $revisions_controller = $post_type->get_revisions_rest_controller();
278 if ( $revisions_controller ) {
279 $revisions_controller->register_routes();
280 }
281
282 $autosaves_controller = $post_type->get_autosave_rest_controller();
283 if ( $autosaves_controller ) {
284 $autosaves_controller->register_routes();
285 }
286
287 if ( $post_type->late_route_registration ) {
288 $controller->register_routes();
289 }
290 }
291
292 // Post types.
293 $controller = new WP_REST_Post_Types_Controller();
294 $controller->register_routes();
295
296 // Post statuses.
297 $controller = new WP_REST_Post_Statuses_Controller();
298 $controller->register_routes();
299
300 // Taxonomies.
301 $controller = new WP_REST_Taxonomies_Controller();
302 $controller->register_routes();
303
304 // Terms.
305 foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
306 $controller = $taxonomy->get_rest_controller();
307
308 if ( ! $controller ) {
309 continue;
310 }
311
312 $controller->register_routes();
313 }
314
315 // Users.
316 $controller = new WP_REST_Users_Controller();
317 $controller->register_routes();
318
319 // Application Passwords
320 $controller = new WP_REST_Application_Passwords_Controller();
321 $controller->register_routes();
322
323 // Comments.
324 $controller = new WP_REST_Comments_Controller();
325 $controller->register_routes();
326
327 $search_handlers = array(
328 new WP_REST_Post_Search_Handler(),
329 new WP_REST_Term_Search_Handler(),
330 new WP_REST_Post_Format_Search_Handler(),
331 );
332
333 /**
334 * Filters the search handlers to use in the REST search controller.
335 *
336 * @since 5.0.0
337 *
338 * @param array $search_handlers List of search handlers to use in the controller. Each search
339 * handler instance must extend the `WP_REST_Search_Handler` class.
340 * Default is only a handler for posts.
341 */
342 $search_handlers = apply_filters( 'wp_rest_search_handlers', $search_handlers );
343
344 $controller = new WP_REST_Search_Controller( $search_handlers );
345 $controller->register_routes();
346
347 // Block Renderer.
348 $controller = new WP_REST_Block_Renderer_Controller();
349 $controller->register_routes();
350
351 // Block Types.
352 $controller = new WP_REST_Block_Types_Controller();
353 $controller->register_routes();
354
355 // Settings.
356 $controller = new WP_REST_Settings_Controller();
357 $controller->register_routes();
358
359 // Themes.
360 $controller = new WP_REST_Themes_Controller();
361 $controller->register_routes();
362
363 // Plugins.
364 $controller = new WP_REST_Plugins_Controller();
365 $controller->register_routes();
366
367 // Sidebars.
368 $controller = new WP_REST_Sidebars_Controller();
369 $controller->register_routes();
370
371 // Widget Types.
372 $controller = new WP_REST_Widget_Types_Controller();
373 $controller->register_routes();
374
375 // Widgets.
376 $controller = new WP_REST_Widgets_Controller();
377 $controller->register_routes();
378
379 // Block Directory.
380 $controller = new WP_REST_Block_Directory_Controller();
381 $controller->register_routes();
382
383 // Pattern Directory.
384 $controller = new WP_REST_Pattern_Directory_Controller();
385 $controller->register_routes();
386
387 // Block Patterns.
388 $controller = new WP_REST_Block_Patterns_Controller();
389 $controller->register_routes();
390
391 // Block Pattern Categories.
392 $controller = new WP_REST_Block_Pattern_Categories_Controller();
393 $controller->register_routes();
394
395 // Site Health.
396 $site_health = WP_Site_Health::get_instance();
397 $controller = new WP_REST_Site_Health_Controller( $site_health );
398 $controller->register_routes();
399
400 // URL Details.
401 $controller = new WP_REST_URL_Details_Controller();
402 $controller->register_routes();
403
404 // Menu Locations.
405 $controller = new WP_REST_Menu_Locations_Controller();
406 $controller->register_routes();
407
408 // Site Editor Export.
409 $controller = new WP_REST_Edit_Site_Export_Controller();
410 $controller->register_routes();
411
412 // Navigation Fallback.
413 $controller = new WP_REST_Navigation_Fallback_Controller();
414 $controller->register_routes();
415
416 // Font Collections.
417 $font_collections_controller = new WP_REST_Font_Collections_Controller();
418 $font_collections_controller->register_routes();
419
420 // Abilities.
421 $abilities_categories_controller = new WP_REST_Abilities_V1_Categories_Controller();
422 $abilities_categories_controller->register_routes();
423 $abilities_run_controller = new WP_REST_Abilities_V1_Run_Controller();
424 $abilities_run_controller->register_routes();
425 $abilities_list_controller = new WP_REST_Abilities_V1_List_Controller();
426 $abilities_list_controller->register_routes();
427}
428
429/**
430 * Loads the REST API.
431 *
432 * @since 4.4.0
433 *
434 * @global WP $wp Current WordPress environment instance.
435 */
436function rest_api_loaded() {
437 if ( empty( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
438 return;
439 }
440
441 // Return an error message if query_var is not a string.
442 if ( ! is_string( $GLOBALS['wp']->query_vars['rest_route'] ) ) {
443 $rest_type_error = new WP_Error(
444 'rest_path_invalid_type',
445 __( 'The REST route parameter must be a string.' ),
446 array( 'status' => 400 )
447 );
448 wp_die( $rest_type_error );
449 }
450
451 /**
452 * Whether this is a REST Request.
453 *
454 * @since 4.4.0
455 * @var bool
456 */
457 define( 'REST_REQUEST', true );
458
459 // Initialize the server.
460 $server = rest_get_server();
461
462 // Fire off the request.
463 $route = untrailingslashit( $GLOBALS['wp']->query_vars['rest_route'] );
464 if ( empty( $route ) ) {
465 $route = '/';
466 }
467 $server->serve_request( $route );
468
469 // We're done.
470 die();
471}
472
473/**
474 * Retrieves the URL prefix for any API resource.
475 *
476 * @since 4.4.0
477 *
478 * @return string Prefix.
479 */
480function rest_get_url_prefix() {
481 /**
482 * Filters the REST URL prefix.
483 *
484 * @since 4.4.0
485 *
486 * @param string $prefix URL prefix. Default 'wp-json'.
487 */
488 return apply_filters( 'rest_url_prefix', 'wp-json' );
489}
490
491/**
492 * Retrieves the URL to a REST endpoint on a site.
493 *
494 * Note: The returned URL is NOT escaped.
495 *
496 * @since 4.4.0
497 *
498 * @todo Check if this is even necessary
499 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
500 *
501 * @param int|null $blog_id Optional. Blog ID. Default of null returns URL for current blog.
502 * @param string $path Optional. REST route. Default '/'.
503 * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
504 * @return string Full URL to the endpoint.
505 */
506function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
507 if ( empty( $path ) ) {
508 $path = '/';
509 }
510
511 $path = '/' . ltrim( $path, '/' );
512
513 if ( is_multisite() && get_blog_option( $blog_id, 'permalink_structure' ) || get_option( 'permalink_structure' ) ) {
514 global $wp_rewrite;
515
516 if ( $wp_rewrite->using_index_permalinks() ) {
517 $url = get_home_url( $blog_id, $wp_rewrite->index . '/' . rest_get_url_prefix(), $scheme );
518 } else {
519 $url = get_home_url( $blog_id, rest_get_url_prefix(), $scheme );
520 }
521
522 $url .= $path;
523 } else {
524 $url = trailingslashit( get_home_url( $blog_id, '', $scheme ) );
525 /*
526 * nginx only allows HTTP/1.0 methods when redirecting from / to /index.php.
527 * To work around this, we manually add index.php to the URL, avoiding the redirect.
528 */
529 if ( ! str_ends_with( $url, 'index.php' ) ) {
530 $url .= 'index.php';
531 }
532
533 $url = add_query_arg( 'rest_route', $path, $url );
534 }
535
536 if ( is_ssl() && isset( $_SERVER['SERVER_NAME'] ) ) {
537 // If the current host is the same as the REST URL host, force the REST URL scheme to HTTPS.
538 if ( parse_url( get_home_url( $blog_id ), PHP_URL_HOST ) === $_SERVER['SERVER_NAME'] ) {
539 $url = set_url_scheme( $url, 'https' );
540 }
541 }
542
543 if ( is_admin() && force_ssl_admin() ) {
544 /*
545 * In this situation the home URL may be http:, and `is_ssl()` may be false,
546 * but the admin is served over https: (one way or another), so REST API usage
547 * will be blocked by browsers unless it is also served over HTTPS.
548 */
549 $url = set_url_scheme( $url, 'https' );
550 }
551
552 /**
553 * Filters the REST URL.
554 *
555 * Use this filter to adjust the url returned by the get_rest_url() function.
556 *
557 * @since 4.4.0
558 *
559 * @param string $url REST URL.
560 * @param string $path REST route.
561 * @param int|null $blog_id Blog ID.
562 * @param string $scheme Sanitization scheme.
563 */
564 return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
565}
566
567/**
568 * Retrieves the URL to a REST endpoint.
569 *
570 * Note: The returned URL is NOT escaped.
571 *
572 * @since 4.4.0
573 *
574 * @param string $path Optional. REST route. Default empty.
575 * @param string $scheme Optional. Sanitization scheme. Default 'rest'.
576 * @return string Full URL to the endpoint.
577 */
578function rest_url( $path = '', $scheme = 'rest' ) {
579 return get_rest_url( null, $path, $scheme );
580}
581
582/**
583 * Do a REST request.
584 *
585 * Used primarily to route internal requests through WP_REST_Server.
586 *
587 * @since 4.4.0
588 *
589 * @param WP_REST_Request|string $request Request.
590 * @return WP_REST_Response REST response.
591 */
592function rest_do_request( $request ) {
593 $request = rest_ensure_request( $request );
594 return rest_get_server()->dispatch( $request );
595}
596
597/**
598 * Retrieves the current REST server instance.
599 *
600 * Instantiates a new instance if none exists already.
601 *
602 * @since 4.5.0
603 *
604 * @global WP_REST_Server $wp_rest_server REST server instance.
605 *
606 * @return WP_REST_Server REST server instance.
607 */
608function rest_get_server() {
609 /* @var WP_REST_Server $wp_rest_server */
610 global $wp_rest_server;
611
612 if ( empty( $wp_rest_server ) ) {
613 /**
614 * Filters the REST Server Class.
615 *
616 * This filter allows you to adjust the server class used by the REST API, using a
617 * different class to handle requests.
618 *
619 * @since 4.4.0
620 *
621 * @param string $class_name The name of the server class. Default 'WP_REST_Server'.
622 */
623 $wp_rest_server_class = apply_filters( 'wp_rest_server_class', 'WP_REST_Server' );
624 $wp_rest_server = new $wp_rest_server_class();
625
626 /**
627 * Fires when preparing to serve a REST API request.
628 *
629 * Endpoint objects should be created and register their hooks on this action rather
630 * than another action to ensure they're only loaded when needed.
631 *
632 * @since 4.4.0
633 *
634 * @param WP_REST_Server $wp_rest_server Server object.
635 */
636 do_action( 'rest_api_init', $wp_rest_server );
637 }
638
639 return $wp_rest_server;
640}
641
642/**
643 * Ensures request arguments are a request object (for consistency).
644 *
645 * @since 4.4.0
646 * @since 5.3.0 Accept string argument for the request path.
647 *
648 * @param array|string|WP_REST_Request $request Request to check.
649 * @return WP_REST_Request REST request instance.
650 */
651function rest_ensure_request( $request ) {
652 if ( $request instanceof WP_REST_Request ) {
653 return $request;
654 }
655
656 if ( is_string( $request ) ) {
657 return new WP_REST_Request( 'GET', $request );
658 }
659
660 return new WP_REST_Request( 'GET', '', $request );
661}
662
663/**
664 * Ensures a REST response is a response object (for consistency).
665 *
666 * This implements WP_REST_Response, allowing usage of `set_status`/`header`/etc
667 * without needing to double-check the object. Will also allow WP_Error to indicate error
668 * responses, so users should immediately check for this value.
669 *
670 * @since 4.4.0
671 *
672 * @param WP_REST_Response|WP_Error|WP_HTTP_Response|mixed $response Response to check.
673 * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response
674 * is already an instance, WP_REST_Response, otherwise
675 * returns a new WP_REST_Response instance.
676 */
677function rest_ensure_response( $response ) {
678 if ( is_wp_error( $response ) ) {
679 return $response;
680 }
681
682 if ( $response instanceof WP_REST_Response ) {
683 return $response;
684 }
685
686 /*
687 * While WP_HTTP_Response is the base class of WP_REST_Response, it doesn't provide
688 * all the required methods used in WP_REST_Server::dispatch().
689 */
690 if ( $response instanceof WP_HTTP_Response ) {
691 return new WP_REST_Response(
692 $response->get_data(),
693 $response->get_status(),
694 $response->get_headers()
695 );
696 }
697
698 return new WP_REST_Response( $response );
699}
700
701/**
702 * Handles _deprecated_function() errors.
703 *
704 * @since 4.4.0
705 *
706 * @param string $function_name The function that was called.
707 * @param string $replacement The function that should have been called.
708 * @param string $version Version.
709 */
710function rest_handle_deprecated_function( $function_name, $replacement, $version ) {
711 if ( ! WP_DEBUG || headers_sent() ) {
712 return;
713 }
714 if ( ! empty( $replacement ) ) {
715 /* translators: 1: Function name, 2: WordPress version number, 3: New function name. */
716 $string = sprintf( __( '%1$s (since %2$s; use %3$s instead)' ), $function_name, $version, $replacement );
717 } else {
718 /* translators: 1: Function name, 2: WordPress version number. */
719 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version );
720 }
721
722 header( sprintf( 'X-WP-DeprecatedFunction: %s', $string ) );
723}
724
725/**
726 * Handles _deprecated_argument() errors.
727 *
728 * @since 4.4.0
729 *
730 * @param string $function_name The function that was called.
731 * @param string $message A message regarding the change.
732 * @param string $version Version.
733 */
734function rest_handle_deprecated_argument( $function_name, $message, $version ) {
735 if ( ! WP_DEBUG || headers_sent() ) {
736 return;
737 }
738 if ( $message ) {
739 /* translators: 1: Function name, 2: WordPress version number, 3: Error message. */
740 $string = sprintf( __( '%1$s (since %2$s; %3$s)' ), $function_name, $version, $message );
741 } else {
742 /* translators: 1: Function name, 2: WordPress version number. */
743 $string = sprintf( __( '%1$s (since %2$s; no alternative available)' ), $function_name, $version );
744 }
745
746 header( sprintf( 'X-WP-DeprecatedParam: %s', $string ) );
747}
748
749/**
750 * Handles _doing_it_wrong errors.
751 *
752 * @since 5.5.0
753 *
754 * @param string $function_name The function that was called.
755 * @param string $message A message explaining what has been done incorrectly.
756 * @param string|null $version The version of WordPress where the message was added.
757 */
758function rest_handle_doing_it_wrong( $function_name, $message, $version ) {
759 if ( ! WP_DEBUG || headers_sent() ) {
760 return;
761 }
762
763 if ( $version ) {
764 /* translators: Developer debugging message. 1: PHP function name, 2: WordPress version number, 3: Explanatory message. */
765 $string = __( '%1$s (since %2$s; %3$s)' );
766 $string = sprintf( $string, $function_name, $version, $message );
767 } else {
768 /* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message. */
769 $string = __( '%1$s (%2$s)' );
770 $string = sprintf( $string, $function_name, $message );
771 }
772
773 header( sprintf( 'X-WP-DoingItWrong: %s', $string ) );
774}
775
776/**
777 * Sends Cross-Origin Resource Sharing headers with API requests.
778 *
779 * @since 4.4.0
780 *
781 * @param mixed $value Response data.
782 * @return mixed Response data.
783 */
784function rest_send_cors_headers( $value ) {
785 $origin = get_http_origin();
786
787 if ( $origin ) {
788 // Requests from file:// and data: URLs send "Origin: null".
789 if ( 'null' !== $origin ) {
790 $origin = sanitize_url( $origin );
791 }
792 header( 'Access-Control-Allow-Origin: ' . $origin );
793 header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
794 header( 'Access-Control-Allow-Credentials: true' );
795 header( 'Vary: Origin', false );
796 } elseif ( ! headers_sent() && 'GET' === $_SERVER['REQUEST_METHOD'] && ! is_user_logged_in() ) {
797 header( 'Vary: Origin', false );
798 }
799
800 return $value;
801}
802
803/**
804 * Handles OPTIONS requests for the server.
805 *
806 * This is handled outside of the server code, as it doesn't obey normal route
807 * mapping.
808 *
809 * @since 4.4.0
810 *
811 * @param mixed $response Current response, either response or `null` to indicate pass-through.
812 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
813 * @param WP_REST_Request $request The request that was used to make current response.
814 * @return WP_REST_Response Modified response, either response or `null` to indicate pass-through.
815 */
816function rest_handle_options_request( $response, $handler, $request ) {
817 if ( ! empty( $response ) || $request->get_method() !== 'OPTIONS' ) {
818 return $response;
819 }
820
821 $response = new WP_REST_Response();
822 $data = array();
823
824 foreach ( $handler->get_routes() as $route => $endpoints ) {
825 $match = preg_match( '@^' . $route . '$@i', $request->get_route(), $matches );
826
827 if ( ! $match ) {
828 continue;
829 }
830
831 $args = array();
832 foreach ( $matches as $param => $value ) {
833 if ( ! is_int( $param ) ) {
834 $args[ $param ] = $value;
835 }
836 }
837
838 foreach ( $endpoints as $endpoint ) {
839 $request->set_url_params( $args );
840 $request->set_attributes( $endpoint );
841 }
842
843 $data = $handler->get_data_for_route( $route, $endpoints, 'help' );
844 $response->set_matched_route( $route );
845 break;
846 }
847
848 $response->set_data( $data );
849 return $response;
850}
851
852/**
853 * Sends the "Allow" header to state all methods that can be sent to the current route.
854 *
855 * @since 4.4.0
856 *
857 * @param WP_REST_Response $response Current response being served.
858 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
859 * @param WP_REST_Request $request The request that was used to make current response.
860 * @return WP_REST_Response Response to be served, with "Allow" header if route has allowed methods.
861 */
862function rest_send_allow_header( $response, $server, $request ) {
863 $matched_route = $response->get_matched_route();
864
865 if ( ! $matched_route ) {
866 return $response;
867 }
868
869 $routes = $server->get_routes();
870
871 $allowed_methods = array();
872
873 // Get the allowed methods across the routes.
874 foreach ( $routes[ $matched_route ] as $_handler ) {
875 foreach ( $_handler['methods'] as $handler_method => $value ) {
876
877 if ( ! empty( $_handler['permission_callback'] ) ) {
878
879 $permission = call_user_func( $_handler['permission_callback'], $request );
880
881 $allowed_methods[ $handler_method ] = true === $permission;
882 } else {
883 $allowed_methods[ $handler_method ] = true;
884 }
885 }
886 }
887
888 // Strip out all the methods that are not allowed (false values).
889 $allowed_methods = array_filter( $allowed_methods );
890
891 if ( $allowed_methods ) {
892 $response->header( 'Allow', implode( ', ', array_map( 'strtoupper', array_keys( $allowed_methods ) ) ) );
893 }
894
895 return $response;
896}
897
898/**
899 * Recursively computes the intersection of arrays using keys for comparison.
900 *
901 * @since 5.3.0
902 *
903 * @param array $array1 The array with master keys to check.
904 * @param array $array2 An array to compare keys against.
905 * @return array An associative array containing all the entries of array1 which have keys
906 * that are present in all arguments.
907 */
908function _rest_array_intersect_key_recursive( $array1, $array2 ) {
909 $array1 = array_intersect_key( $array1, $array2 );
910 foreach ( $array1 as $key => $value ) {
911 if ( is_array( $value ) && is_array( $array2[ $key ] ) ) {
912 $array1[ $key ] = _rest_array_intersect_key_recursive( $value, $array2[ $key ] );
913 }
914 }
915 return $array1;
916}
917
918/**
919 * Filters the REST API response to include only an allow-listed set of response object fields.
920 *
921 * @since 4.8.0
922 *
923 * @param WP_REST_Response $response Current response being served.
924 * @param WP_REST_Server $server ResponseHandler instance (usually WP_REST_Server).
925 * @param WP_REST_Request $request The request that was used to make current response.
926 * @return WP_REST_Response Response to be served, trimmed down to contain a subset of fields.
927 */
928function rest_filter_response_fields( $response, $server, $request ) {
929 if ( ! isset( $request['_fields'] ) || $response->is_error() ) {
930 return $response;
931 }
932
933 $data = $response->get_data();
934
935 $fields = wp_parse_list( $request['_fields'] );
936
937 if ( 0 === count( $fields ) ) {
938 return $response;
939 }
940
941 // Trim off outside whitespace from the comma delimited list.
942 $fields = array_map( 'trim', $fields );
943
944 // Create nested array of accepted field hierarchy.
945 $fields_as_keyed = array();
946 foreach ( $fields as $field ) {
947 $parts = explode( '.', $field );
948 $ref = &$fields_as_keyed;
949 while ( count( $parts ) > 1 ) {
950 $next = array_shift( $parts );
951 if ( isset( $ref[ $next ] ) && true === $ref[ $next ] ) {
952 // Skip any sub-properties if their parent prop is already marked for inclusion.
953 break 2;
954 }
955 $ref[ $next ] = isset( $ref[ $next ] ) ? $ref[ $next ] : array();
956 $ref = &$ref[ $next ];
957 }
958 $last = array_shift( $parts );
959 $ref[ $last ] = true;
960 }
961
962 if ( wp_is_numeric_array( $data ) ) {
963 $new_data = array();
964 foreach ( $data as $item ) {
965 $new_data[] = _rest_array_intersect_key_recursive( $item, $fields_as_keyed );
966 }
967 } else {
968 $new_data = _rest_array_intersect_key_recursive( $data, $fields_as_keyed );
969 }
970
971 $response->set_data( $new_data );
972
973 return $response;
974}
975
976/**
977 * Given an array of fields to include in a response, some of which may be
978 * `nested.fields`, determine whether the provided field should be included
979 * in the response body.
980 *
981 * If a parent field is passed in, the presence of any nested field within
982 * that parent will cause the method to return `true`. For example "title"
983 * will return true if any of `title`, `title.raw` or `title.rendered` is
984 * provided.
985 *
986 * @since 5.3.0
987 *
988 * @param string $field A field to test for inclusion in the response body.
989 * @param array $fields An array of string fields supported by the endpoint.
990 * @return bool Whether to include the field or not.
991 */
992function rest_is_field_included( $field, $fields ) {
993 if ( in_array( $field, $fields, true ) ) {
994 return true;
995 }
996
997 foreach ( $fields as $accepted_field ) {
998 /*
999 * Check to see if $field is the parent of any item in $fields.
1000 * A field "parent" should be accepted if "parent.child" is accepted.
1001 */
1002 if ( str_starts_with( $accepted_field, "$field." ) ) {
1003 return true;
1004 }
1005 /*
1006 * Conversely, if "parent" is accepted, all "parent.child" fields
1007 * should also be accepted.
1008 */
1009 if ( str_starts_with( $field, "$accepted_field." ) ) {
1010 return true;
1011 }
1012 }
1013
1014 return false;
1015}
1016
1017/**
1018 * Adds the REST API URL to the WP RSD endpoint.
1019 *
1020 * @since 4.4.0
1021 *
1022 * @see get_rest_url()
1023 */
1024function rest_output_rsd() {
1025 $api_root = get_rest_url();
1026
1027 if ( empty( $api_root ) ) {
1028 return;
1029 }
1030 ?>
1031 <api name="WP-API" blogID="1" preferred="false" apiLink="<?php echo esc_url( $api_root ); ?>" />
1032 <?php
1033}
1034
1035/**
1036 * Outputs the REST API link tag into page header.
1037 *
1038 * @since 4.4.0
1039 *
1040 * @see get_rest_url()
1041 */
1042function rest_output_link_wp_head() {
1043 $api_root = get_rest_url();
1044
1045 if ( empty( $api_root ) ) {
1046 return;
1047 }
1048
1049 printf( '<link rel="https://api.w.org/" href="%s" />', esc_url( $api_root ) );
1050
1051 $resource = rest_get_queried_resource_route();
1052
1053 if ( $resource ) {
1054 printf(
1055 '<link rel="alternate" title="%1$s" type="application/json" href="%2$s" />',
1056 _x( 'JSON', 'REST API resource link name' ),
1057 esc_url( rest_url( $resource ) )
1058 );
1059 }
1060}
1061
1062/**
1063 * Sends a Link header for the REST API.
1064 *
1065 * @since 4.4.0
1066 */
1067function rest_output_link_header() {
1068 if ( headers_sent() ) {
1069 return;
1070 }
1071
1072 $api_root = get_rest_url();
1073
1074 if ( empty( $api_root ) ) {
1075 return;
1076 }
1077
1078 header( sprintf( 'Link: <%s>; rel="https://api.w.org/"', sanitize_url( $api_root ) ), false );
1079
1080 $resource = rest_get_queried_resource_route();
1081
1082 if ( $resource ) {
1083 header(
1084 sprintf(
1085 'Link: <%1$s>; rel="alternate"; title="%2$s"; type="application/json"',
1086 sanitize_url( rest_url( $resource ) ),
1087 _x( 'JSON', 'REST API resource link name' )
1088 ),
1089 false
1090 );
1091 }
1092}
1093
1094/**
1095 * Checks for errors when using cookie-based authentication.
1096 *
1097 * WordPress' built-in cookie authentication is always active
1098 * for logged in users. However, the API has to check nonces
1099 * for each request to ensure users are not vulnerable to CSRF.
1100 *
1101 * @since 4.4.0
1102 *
1103 * @global mixed $wp_rest_auth_cookie
1104 *
1105 * @param WP_Error|mixed $result Error from another authentication handler,
1106 * null if we should handle it, or another value if not.
1107 * @return WP_Error|mixed|bool WP_Error if the cookie is invalid, the $result, otherwise true.
1108 */
1109function rest_cookie_check_errors( $result ) {
1110 if ( ! empty( $result ) ) {
1111 return $result;
1112 }
1113
1114 global $wp_rest_auth_cookie;
1115
1116 /*
1117 * Is cookie authentication being used? (If we get an auth
1118 * error, but we're still logged in, another authentication
1119 * must have been used).
1120 */
1121 if ( true !== $wp_rest_auth_cookie && is_user_logged_in() ) {
1122 return $result;
1123 }
1124
1125 // Determine if there is a nonce.
1126 $nonce = null;
1127
1128 if ( isset( $_REQUEST['_wpnonce'] ) ) {
1129 $nonce = $_REQUEST['_wpnonce'];
1130 } elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
1131 $nonce = $_SERVER['HTTP_X_WP_NONCE'];
1132 }
1133
1134 if ( null === $nonce ) {
1135 // No nonce at all, so act as if it's an unauthenticated request.
1136 wp_set_current_user( 0 );
1137 return true;
1138 }
1139
1140 // Check the nonce.
1141 $result = wp_verify_nonce( $nonce, 'wp_rest' );
1142
1143 if ( ! $result ) {
1144 add_filter( 'rest_send_nocache_headers', '__return_true', 20 );
1145 return new WP_Error( 'rest_cookie_invalid_nonce', __( 'Cookie check failed' ), array( 'status' => 403 ) );
1146 }
1147
1148 // Send a refreshed nonce in header.
1149 rest_get_server()->send_header( 'X-WP-Nonce', wp_create_nonce( 'wp_rest' ) );
1150
1151 return true;
1152}
1153
1154/**
1155 * Collects cookie authentication status.
1156 *
1157 * Collects errors from wp_validate_auth_cookie for use by rest_cookie_check_errors.
1158 *
1159 * @since 4.4.0
1160 *
1161 * @see current_action()
1162 * @global mixed $wp_rest_auth_cookie
1163 */
1164function rest_cookie_collect_status() {
1165 global $wp_rest_auth_cookie;
1166
1167 $status_type = current_action();
1168
1169 if ( 'auth_cookie_valid' !== $status_type ) {
1170 $wp_rest_auth_cookie = substr( $status_type, 12 );
1171 return;
1172 }
1173
1174 $wp_rest_auth_cookie = true;
1175}
1176
1177/**
1178 * Collects the status of authenticating with an application password.
1179 *
1180 * @since 5.6.0
1181 * @since 5.7.0 Added the `$app_password` parameter.
1182 *
1183 * @global WP_User|WP_Error|null $wp_rest_application_password_status
1184 * @global string|null $wp_rest_application_password_uuid
1185 *
1186 * @param WP_Error $user_or_error The authenticated user or error instance.
1187 * @param array $app_password The Application Password used to authenticate.
1188 */
1189function rest_application_password_collect_status( $user_or_error, $app_password = array() ) {
1190 global $wp_rest_application_password_status, $wp_rest_application_password_uuid;
1191
1192 $wp_rest_application_password_status = $user_or_error;
1193
1194 if ( empty( $app_password['uuid'] ) ) {
1195 $wp_rest_application_password_uuid = null;
1196 } else {
1197 $wp_rest_application_password_uuid = $app_password['uuid'];
1198 }
1199}
1200
1201/**
1202 * Gets the Application Password used for authenticating the request.
1203 *
1204 * @since 5.7.0
1205 *
1206 * @global string|null $wp_rest_application_password_uuid
1207 *
1208 * @return string|null The Application Password UUID, or null if Application Passwords was not used.
1209 */
1210function rest_get_authenticated_app_password() {
1211 global $wp_rest_application_password_uuid;
1212
1213 return $wp_rest_application_password_uuid;
1214}
1215
1216/**
1217 * Checks for errors when using application password-based authentication.
1218 *
1219 * @since 5.6.0
1220 *
1221 * @global WP_User|WP_Error|null $wp_rest_application_password_status
1222 *
1223 * @param WP_Error|null|true $result Error from another authentication handler,
1224 * null if we should handle it, or another value if not.
1225 * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true.
1226 */
1227function rest_application_password_check_errors( $result ) {
1228 global $wp_rest_application_password_status;
1229
1230 if ( ! empty( $result ) ) {
1231 return $result;
1232 }
1233
1234 if ( is_wp_error( $wp_rest_application_password_status ) ) {
1235 $data = $wp_rest_application_password_status->get_error_data();
1236
1237 if ( ! isset( $data['status'] ) ) {
1238 $data['status'] = 401;
1239 }
1240
1241 $wp_rest_application_password_status->add_data( $data );
1242
1243 return $wp_rest_application_password_status;
1244 }
1245
1246 if ( $wp_rest_application_password_status instanceof WP_User ) {
1247 return true;
1248 }
1249
1250 return $result;
1251}
1252
1253/**
1254 * Adds Application Passwords info to the REST API index.
1255 *
1256 * @since 5.6.0
1257 *
1258 * @param WP_REST_Response $response The index response object.
1259 * @return WP_REST_Response
1260 */
1261function rest_add_application_passwords_to_index( $response ) {
1262 if ( ! wp_is_application_passwords_available() ) {
1263 return $response;
1264 }
1265
1266 $response->data['authentication']['application-passwords'] = array(
1267 'endpoints' => array(
1268 'authorization' => admin_url( 'authorize-application.php' ),
1269 ),
1270 );
1271
1272 return $response;
1273}
1274
1275/**
1276 * Retrieves the avatar URLs in various sizes.
1277 *
1278 * @since 4.7.0
1279 *
1280 * @see get_avatar_url()
1281 *
1282 * @param mixed $id_or_email The avatar to retrieve a URL for. Accepts a user ID, Gravatar MD5 hash,
1283 * user email, WP_User object, WP_Post object, or WP_Comment object.
1284 * @return (string|false)[] Avatar URLs keyed by size. Each value can be a URL string or boolean false.
1285 */
1286function rest_get_avatar_urls( $id_or_email ) {
1287 $avatar_sizes = rest_get_avatar_sizes();
1288
1289 $urls = array();
1290 foreach ( $avatar_sizes as $size ) {
1291 $urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
1292 }
1293
1294 return $urls;
1295}
1296
1297/**
1298 * Retrieves the pixel sizes for avatars.
1299 *
1300 * @since 4.7.0
1301 *
1302 * @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
1303 */
1304function rest_get_avatar_sizes() {
1305 /**
1306 * Filters the REST avatar sizes.
1307 *
1308 * Use this filter to adjust the array of sizes returned by the
1309 * `rest_get_avatar_sizes` function.
1310 *
1311 * @since 4.4.0
1312 *
1313 * @param int[] $sizes An array of int values that are the pixel sizes for avatars.
1314 * Default `[ 24, 48, 96 ]`.
1315 */
1316 return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
1317}
1318
1319/**
1320 * Parses an RFC3339 time into a Unix timestamp.
1321 *
1322 * Explicitly check for `false` to detect failure, as zero is a valid return
1323 * value on success.
1324 *
1325 * @since 4.4.0
1326 *
1327 * @param string $date RFC3339 timestamp.
1328 * @param bool $force_utc Optional. Whether to force UTC timezone instead of using
1329 * the timestamp's timezone. Default false.
1330 * @return int|false Unix timestamp on success, false on failure.
1331 */
1332function rest_parse_date( $date, $force_utc = false ) {
1333 if ( $force_utc ) {
1334 $date = preg_replace( '/[+-]\d+:?\d+$/', '+00:00', $date );
1335 }
1336
1337 $regex = '#^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?$#';
1338
1339 if ( ! preg_match( $regex, $date, $matches ) ) {
1340 return false;
1341 }
1342
1343 return strtotime( $date );
1344}
1345
1346/**
1347 * Parses a 3 or 6 digit hex color (with #).
1348 *
1349 * @since 5.4.0
1350 *
1351 * @param string $color 3 or 6 digit hex color (with #).
1352 * @return string|false Color value on success, false on failure.
1353 */
1354function rest_parse_hex_color( $color ) {
1355 $regex = '|^#([A-Fa-f0-9]{3}){1,2}$|';
1356 if ( ! preg_match( $regex, $color, $matches ) ) {
1357 return false;
1358 }
1359
1360 return $color;
1361}
1362
1363/**
1364 * Parses a date into both its local and UTC equivalent, in MySQL datetime format.
1365 *
1366 * @since 4.4.0
1367 *
1368 * @see rest_parse_date()
1369 *
1370 * @param string $date RFC3339 timestamp.
1371 * @param bool $is_utc Whether the provided date should be interpreted as UTC. Default false.
1372 * @return array|null {
1373 * Local and UTC datetime strings, in MySQL datetime format (Y-m-d H:i:s),
1374 * null on failure.
1375 *
1376 * @type string $0 Local datetime string.
1377 * @type string $1 UTC datetime string.
1378 * }
1379 */
1380function rest_get_date_with_gmt( $date, $is_utc = false ) {
1381 /*
1382 * Whether or not the original date actually has a timezone string
1383 * changes the way we need to do timezone conversion.
1384 * Store this info before parsing the date, and use it later.
1385 */
1386 $has_timezone = preg_match( '#(Z|[+-]\d{2}(:\d{2})?)$#', $date );
1387
1388 $date = rest_parse_date( $date );
1389
1390 if ( false === $date ) {
1391 return null;
1392 }
1393
1394 /*
1395 * At this point $date could either be a local date (if we were passed
1396 * a *local* date without a timezone offset) or a UTC date (otherwise).
1397 * Timezone conversion needs to be handled differently between these two cases.
1398 */
1399 if ( ! $is_utc && ! $has_timezone ) {
1400 $local = gmdate( 'Y-m-d H:i:s', $date );
1401 $utc = get_gmt_from_date( $local );
1402 } else {
1403 $utc = gmdate( 'Y-m-d H:i:s', $date );
1404 $local = get_date_from_gmt( $utc );
1405 }
1406
1407 return array( $local, $utc );
1408}
1409
1410/**
1411 * Returns a contextual HTTP error code for authorization failure.
1412 *
1413 * @since 4.7.0
1414 *
1415 * @return int 401 if the user is not logged in, 403 if the user is logged in.
1416 */
1417function rest_authorization_required_code() {
1418 return is_user_logged_in() ? 403 : 401;
1419}
1420
1421/**
1422 * Validate a request argument based on details registered to the route.
1423 *
1424 * @since 4.7.0
1425 *
1426 * @param mixed $value
1427 * @param WP_REST_Request $request
1428 * @param string $param
1429 * @return true|WP_Error
1430 */
1431function rest_validate_request_arg( $value, $request, $param ) {
1432 $attributes = $request->get_attributes();
1433 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1434 return true;
1435 }
1436 $args = $attributes['args'][ $param ];
1437
1438 return rest_validate_value_from_schema( $value, $args, $param );
1439}
1440
1441/**
1442 * Sanitize a request argument based on details registered to the route.
1443 *
1444 * @since 4.7.0
1445 *
1446 * @param mixed $value
1447 * @param WP_REST_Request $request
1448 * @param string $param
1449 * @return mixed
1450 */
1451function rest_sanitize_request_arg( $value, $request, $param ) {
1452 $attributes = $request->get_attributes();
1453 if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
1454 return $value;
1455 }
1456 $args = $attributes['args'][ $param ];
1457
1458 return rest_sanitize_value_from_schema( $value, $args, $param );
1459}
1460
1461/**
1462 * Parse a request argument based on details registered to the route.
1463 *
1464 * Runs a validation check and sanitizes the value, primarily to be used via
1465 * the `sanitize_callback` arguments in the endpoint args registration.
1466 *
1467 * @since 4.7.0
1468 *
1469 * @param mixed $value
1470 * @param WP_REST_Request $request
1471 * @param string $param
1472 * @return mixed
1473 */
1474function rest_parse_request_arg( $value, $request, $param ) {
1475 $is_valid = rest_validate_request_arg( $value, $request, $param );
1476
1477 if ( is_wp_error( $is_valid ) ) {
1478 return $is_valid;
1479 }
1480
1481 $value = rest_sanitize_request_arg( $value, $request, $param );
1482
1483 return $value;
1484}
1485
1486/**
1487 * Determines if an IP address is valid.
1488 *
1489 * Handles both IPv4 and IPv6 addresses.
1490 *
1491 * @since 4.7.0
1492 *
1493 * @param string $ip IP address.
1494 * @return string|false The valid IP address, otherwise false.
1495 */
1496function rest_is_ip_address( $ip ) {
1497 $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]?)$/';
1498
1499 if ( ! preg_match( $ipv4_pattern, $ip ) && ! WpOrg\Requests\Ipv6::check_ipv6( $ip ) ) {
1500 return false;
1501 }
1502
1503 return $ip;
1504}
1505
1506/**
1507 * Changes a boolean-like value into the proper boolean value.
1508 *
1509 * @since 4.7.0
1510 *
1511 * @param bool|string|int $value The value being evaluated.
1512 * @return bool Returns the proper associated boolean value.
1513 */
1514function rest_sanitize_boolean( $value ) {
1515 // String values are translated to `true`; make sure 'false' is false.
1516 if ( is_string( $value ) ) {
1517 $value = strtolower( $value );
1518 if ( in_array( $value, array( 'false', '0' ), true ) ) {
1519 $value = false;
1520 }
1521 }
1522
1523 // Everything else will map nicely to boolean.
1524 return (bool) $value;
1525}
1526
1527/**
1528 * Determines if a given value is boolean-like.
1529 *
1530 * @since 4.7.0
1531 *
1532 * @param bool|string $maybe_bool The value being evaluated.
1533 * @return bool True if a boolean, otherwise false.
1534 */
1535function rest_is_boolean( $maybe_bool ) {
1536 if ( is_bool( $maybe_bool ) ) {
1537 return true;
1538 }
1539
1540 if ( is_string( $maybe_bool ) ) {
1541 $maybe_bool = strtolower( $maybe_bool );
1542
1543 $valid_boolean_values = array(
1544 'false',
1545 'true',
1546 '0',
1547 '1',
1548 );
1549
1550 return in_array( $maybe_bool, $valid_boolean_values, true );
1551 }
1552
1553 if ( is_int( $maybe_bool ) ) {
1554 return in_array( $maybe_bool, array( 0, 1 ), true );
1555 }
1556
1557 return false;
1558}
1559
1560/**
1561 * Determines if a given value is integer-like.
1562 *
1563 * @since 5.5.0
1564 *
1565 * @param mixed $maybe_integer The value being evaluated.
1566 * @return bool True if an integer, otherwise false.
1567 */
1568function rest_is_integer( $maybe_integer ) {
1569 return is_numeric( $maybe_integer ) && round( (float) $maybe_integer ) === (float) $maybe_integer;
1570}
1571
1572/**
1573 * Determines if a given value is array-like.
1574 *
1575 * @since 5.5.0
1576 *
1577 * @param mixed $maybe_array The value being evaluated.
1578 * @return bool
1579 */
1580function rest_is_array( $maybe_array ) {
1581 if ( is_scalar( $maybe_array ) ) {
1582 $maybe_array = wp_parse_list( $maybe_array );
1583 }
1584
1585 return wp_is_numeric_array( $maybe_array );
1586}
1587
1588/**
1589 * Converts an array-like value to an array.
1590 *
1591 * @since 5.5.0
1592 *
1593 * @param mixed $maybe_array The value being evaluated.
1594 * @return array Returns the array extracted from the value.
1595 */
1596function rest_sanitize_array( $maybe_array ) {
1597 if ( is_scalar( $maybe_array ) ) {
1598 return wp_parse_list( $maybe_array );
1599 }
1600
1601 if ( ! is_array( $maybe_array ) ) {
1602 return array();
1603 }
1604
1605 // Normalize to numeric array so nothing unexpected is in the keys.
1606 return array_values( $maybe_array );
1607}
1608
1609/**
1610 * Determines if a given value is object-like.
1611 *
1612 * @since 5.5.0
1613 *
1614 * @param mixed $maybe_object The value being evaluated.
1615 * @return bool True if object like, otherwise false.
1616 */
1617function rest_is_object( $maybe_object ) {
1618 if ( '' === $maybe_object ) {
1619 return true;
1620 }
1621
1622 if ( $maybe_object instanceof stdClass ) {
1623 return true;
1624 }
1625
1626 if ( $maybe_object instanceof JsonSerializable ) {
1627 $maybe_object = $maybe_object->jsonSerialize();
1628 }
1629
1630 return is_array( $maybe_object );
1631}
1632
1633/**
1634 * Converts an object-like value to an array.
1635 *
1636 * @since 5.5.0
1637 *
1638 * @param mixed $maybe_object The value being evaluated.
1639 * @return array Returns the object extracted from the value as an associative array.
1640 */
1641function rest_sanitize_object( $maybe_object ) {
1642 if ( '' === $maybe_object ) {
1643 return array();
1644 }
1645
1646 if ( $maybe_object instanceof stdClass ) {
1647 return (array) $maybe_object;
1648 }
1649
1650 if ( $maybe_object instanceof JsonSerializable ) {
1651 $maybe_object = $maybe_object->jsonSerialize();
1652 }
1653
1654 if ( ! is_array( $maybe_object ) ) {
1655 return array();
1656 }
1657
1658 return $maybe_object;
1659}
1660
1661/**
1662 * Gets the best type for a value.
1663 *
1664 * @since 5.5.0
1665 *
1666 * @param mixed $value The value to check.
1667 * @param string[] $types The list of possible types.
1668 * @return string The best matching type, an empty string if no types match.
1669 */
1670function rest_get_best_type_for_value( $value, $types ) {
1671 static $checks = array(
1672 'array' => 'rest_is_array',
1673 'object' => 'rest_is_object',
1674 'integer' => 'rest_is_integer',
1675 'number' => 'is_numeric',
1676 'boolean' => 'rest_is_boolean',
1677 'string' => 'is_string',
1678 'null' => 'is_null',
1679 );
1680
1681 /*
1682 * Both arrays and objects allow empty strings to be converted to their types.
1683 * But the best answer for this type is a string.
1684 */
1685 if ( '' === $value && in_array( 'string', $types, true ) ) {
1686 return 'string';
1687 }
1688
1689 foreach ( $types as $type ) {
1690 if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
1691 return $type;
1692 }
1693 }
1694
1695 return '';
1696}
1697
1698/**
1699 * Handles getting the best type for a multi-type schema.
1700 *
1701 * This is a wrapper for {@see rest_get_best_type_for_value()} that handles
1702 * backward compatibility for schemas that use invalid types.
1703 *
1704 * @since 5.5.0
1705 *
1706 * @param mixed $value The value to check.
1707 * @param array $args The schema array to use.
1708 * @param string $param The parameter name, used in error messages.
1709 * @return string
1710 */
1711function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
1712 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
1713 $invalid_types = array_diff( $args['type'], $allowed_types );
1714
1715 if ( $invalid_types ) {
1716 _doing_it_wrong(
1717 __FUNCTION__,
1718 /* translators: 1: Parameter, 2: List of allowed types. */
1719 wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
1720 '5.5.0'
1721 );
1722 }
1723
1724 $best_type = rest_get_best_type_for_value( $value, $args['type'] );
1725
1726 if ( ! $best_type ) {
1727 if ( ! $invalid_types ) {
1728 return '';
1729 }
1730
1731 // Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
1732 $best_type = reset( $invalid_types );
1733 }
1734
1735 return $best_type;
1736}
1737
1738/**
1739 * Checks if an array is made up of unique items.
1740 *
1741 * @since 5.5.0
1742 *
1743 * @param array $input_array The array to check.
1744 * @return bool True if the array contains unique items, false otherwise.
1745 */
1746function rest_validate_array_contains_unique_items( $input_array ) {
1747 $seen = array();
1748
1749 foreach ( $input_array as $item ) {
1750 $stabilized = rest_stabilize_value( $item );
1751 $key = serialize( $stabilized );
1752
1753 if ( ! isset( $seen[ $key ] ) ) {
1754 $seen[ $key ] = true;
1755
1756 continue;
1757 }
1758
1759 return false;
1760 }
1761
1762 return true;
1763}
1764
1765/**
1766 * Stabilizes a value following JSON Schema semantics.
1767 *
1768 * For lists, order is preserved. For objects, properties are reordered alphabetically.
1769 *
1770 * @since 5.5.0
1771 *
1772 * @param mixed $value The value to stabilize. Must already be sanitized. Objects should have been converted to arrays.
1773 * @return mixed The stabilized value.
1774 */
1775function rest_stabilize_value( $value ) {
1776 if ( is_scalar( $value ) || is_null( $value ) ) {
1777 return $value;
1778 }
1779
1780 if ( is_object( $value ) ) {
1781 _doing_it_wrong( __FUNCTION__, __( 'Cannot stabilize objects. Convert the object to an array first.' ), '5.5.0' );
1782
1783 return $value;
1784 }
1785
1786 ksort( $value );
1787
1788 foreach ( $value as $k => $v ) {
1789 $value[ $k ] = rest_stabilize_value( $v );
1790 }
1791
1792 return $value;
1793}
1794
1795/**
1796 * Validates if the JSON Schema pattern matches a value.
1797 *
1798 * @since 5.6.0
1799 *
1800 * @param string $pattern The pattern to match against.
1801 * @param string $value The value to check.
1802 * @return bool True if the pattern matches the given value, false otherwise.
1803 */
1804function rest_validate_json_schema_pattern( $pattern, $value ) {
1805 $escaped_pattern = str_replace( '#', '\\#', $pattern );
1806
1807 return 1 === preg_match( '#' . $escaped_pattern . '#u', $value );
1808}
1809
1810/**
1811 * Finds the schema for a property using the patternProperties keyword.
1812 *
1813 * @since 5.6.0
1814 *
1815 * @param string $property The property name to check.
1816 * @param array $args The schema array to use.
1817 * @return array|null The schema of matching pattern property, or null if no patterns match.
1818 */
1819function rest_find_matching_pattern_property_schema( $property, $args ) {
1820 if ( isset( $args['patternProperties'] ) ) {
1821 foreach ( $args['patternProperties'] as $pattern => $child_schema ) {
1822 if ( rest_validate_json_schema_pattern( $pattern, $property ) ) {
1823 return $child_schema;
1824 }
1825 }
1826 }
1827
1828 return null;
1829}
1830
1831/**
1832 * Formats a combining operation error into a WP_Error object.
1833 *
1834 * @since 5.6.0
1835 *
1836 * @param string $param The parameter name.
1837 * @param array $error The error details.
1838 * @return WP_Error
1839 */
1840function rest_format_combining_operation_error( $param, $error ) {
1841 $position = $error['index'];
1842 $reason = $error['error_object']->get_error_message();
1843
1844 if ( isset( $error['schema']['title'] ) ) {
1845 $title = $error['schema']['title'];
1846
1847 return new WP_Error(
1848 'rest_no_matching_schema',
1849 /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
1850 sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
1851 array( 'position' => $position )
1852 );
1853 }
1854
1855 return new WP_Error(
1856 'rest_no_matching_schema',
1857 /* translators: 1: Parameter, 2: Reason. */
1858 sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
1859 array( 'position' => $position )
1860 );
1861}
1862
1863/**
1864 * Gets the error of combining operation.
1865 *
1866 * @since 5.6.0
1867 *
1868 * @param array $value The value to validate.
1869 * @param string $param The parameter name, used in error messages.
1870 * @param array $errors The errors array, to search for possible error.
1871 * @return WP_Error The combining operation error.
1872 */
1873function rest_get_combining_operation_error( $value, $param, $errors ) {
1874 // If there is only one error, simply return it.
1875 if ( 1 === count( $errors ) ) {
1876 return rest_format_combining_operation_error( $param, $errors[0] );
1877 }
1878
1879 // Filter out all errors related to type validation.
1880 $filtered_errors = array();
1881 foreach ( $errors as $error ) {
1882 $error_code = $error['error_object']->get_error_code();
1883 $error_data = $error['error_object']->get_error_data();
1884
1885 if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
1886 $filtered_errors[] = $error;
1887 }
1888 }
1889
1890 // If there is only one error left, simply return it.
1891 if ( 1 === count( $filtered_errors ) ) {
1892 return rest_format_combining_operation_error( $param, $filtered_errors[0] );
1893 }
1894
1895 // If there are only errors related to object validation, try choosing the most appropriate one.
1896 if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
1897 $result = null;
1898 $number = 0;
1899
1900 foreach ( $filtered_errors as $error ) {
1901 if ( isset( $error['schema']['properties'] ) ) {
1902 $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
1903 if ( $n > $number ) {
1904 $result = $error;
1905 $number = $n;
1906 }
1907 }
1908 }
1909
1910 if ( null !== $result ) {
1911 return rest_format_combining_operation_error( $param, $result );
1912 }
1913 }
1914
1915 // If each schema has a title, include those titles in the error message.
1916 $schema_titles = array();
1917 foreach ( $errors as $error ) {
1918 if ( isset( $error['schema']['title'] ) ) {
1919 $schema_titles[] = $error['schema']['title'];
1920 }
1921 }
1922
1923 if ( count( $schema_titles ) === count( $errors ) ) {
1924 /* translators: 1: Parameter, 2: Schema titles. */
1925 return new WP_Error( 'rest_no_matching_schema', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
1926 }
1927
1928 /* translators: %s: Parameter. */
1929 return new WP_Error( 'rest_no_matching_schema', sprintf( __( '%s does not match any of the expected formats.' ), $param ) );
1930}
1931
1932/**
1933 * Finds the matching schema among the "anyOf" schemas.
1934 *
1935 * @since 5.6.0
1936 *
1937 * @param mixed $value The value to validate.
1938 * @param array $args The schema array to use.
1939 * @param string $param The parameter name, used in error messages.
1940 * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
1941 */
1942function rest_find_any_matching_schema( $value, $args, $param ) {
1943 $errors = array();
1944
1945 foreach ( $args['anyOf'] as $index => $schema ) {
1946 if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1947 $schema['type'] = $args['type'];
1948 }
1949
1950 $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1951 if ( ! is_wp_error( $is_valid ) ) {
1952 return $schema;
1953 }
1954
1955 $errors[] = array(
1956 'error_object' => $is_valid,
1957 'schema' => $schema,
1958 'index' => $index,
1959 );
1960 }
1961
1962 return rest_get_combining_operation_error( $value, $param, $errors );
1963}
1964
1965/**
1966 * Finds the matching schema among the "oneOf" schemas.
1967 *
1968 * @since 5.6.0
1969 *
1970 * @param mixed $value The value to validate.
1971 * @param array $args The schema array to use.
1972 * @param string $param The parameter name, used in error messages.
1973 * @param bool $stop_after_first_match Optional. Whether the process should stop after the first successful match.
1974 * @return array|WP_Error The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
1975 */
1976function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
1977 $matching_schemas = array();
1978 $errors = array();
1979
1980 foreach ( $args['oneOf'] as $index => $schema ) {
1981 if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
1982 $schema['type'] = $args['type'];
1983 }
1984
1985 $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
1986 if ( ! is_wp_error( $is_valid ) ) {
1987 if ( $stop_after_first_match ) {
1988 return $schema;
1989 }
1990
1991 $matching_schemas[] = array(
1992 'schema_object' => $schema,
1993 'index' => $index,
1994 );
1995 } else {
1996 $errors[] = array(
1997 'error_object' => $is_valid,
1998 'schema' => $schema,
1999 'index' => $index,
2000 );
2001 }
2002 }
2003
2004 if ( ! $matching_schemas ) {
2005 return rest_get_combining_operation_error( $value, $param, $errors );
2006 }
2007
2008 if ( count( $matching_schemas ) > 1 ) {
2009 $schema_positions = array();
2010 $schema_titles = array();
2011
2012 foreach ( $matching_schemas as $schema ) {
2013 $schema_positions[] = $schema['index'];
2014
2015 if ( isset( $schema['schema_object']['title'] ) ) {
2016 $schema_titles[] = $schema['schema_object']['title'];
2017 }
2018 }
2019
2020 // If each schema has a title, include those titles in the error message.
2021 if ( count( $schema_titles ) === count( $matching_schemas ) ) {
2022 return new WP_Error(
2023 'rest_one_of_multiple_matches',
2024 /* translators: 1: Parameter, 2: Schema titles. */
2025 wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
2026 array( 'positions' => $schema_positions )
2027 );
2028 }
2029
2030 return new WP_Error(
2031 'rest_one_of_multiple_matches',
2032 /* translators: %s: Parameter. */
2033 sprintf( __( '%s matches more than one of the expected formats.' ), $param ),
2034 array( 'positions' => $schema_positions )
2035 );
2036 }
2037
2038 return $matching_schemas[0]['schema_object'];
2039}
2040
2041/**
2042 * Checks the equality of two values, following JSON Schema semantics.
2043 *
2044 * Property order is ignored for objects.
2045 *
2046 * Values must have been previously sanitized/coerced to their native types.
2047 *
2048 * @since 5.7.0
2049 *
2050 * @param mixed $value1 The first value to check.
2051 * @param mixed $value2 The second value to check.
2052 * @return bool True if the values are equal or false otherwise.
2053 */
2054function rest_are_values_equal( $value1, $value2 ) {
2055 if ( is_array( $value1 ) && is_array( $value2 ) ) {
2056 if ( count( $value1 ) !== count( $value2 ) ) {
2057 return false;
2058 }
2059
2060 foreach ( $value1 as $index => $value ) {
2061 if ( ! array_key_exists( $index, $value2 ) || ! rest_are_values_equal( $value, $value2[ $index ] ) ) {
2062 return false;
2063 }
2064 }
2065
2066 return true;
2067 }
2068
2069 if ( is_int( $value1 ) && is_float( $value2 )
2070 || is_float( $value1 ) && is_int( $value2 )
2071 ) {
2072 return (float) $value1 === (float) $value2;
2073 }
2074
2075 return $value1 === $value2;
2076}
2077
2078/**
2079 * Validates that the given value is a member of the JSON Schema "enum".
2080 *
2081 * @since 5.7.0
2082 *
2083 * @param mixed $value The value to validate.
2084 * @param array $args The schema array to use.
2085 * @param string $param The parameter name, used in error messages.
2086 * @return true|WP_Error True if the "enum" contains the value or a WP_Error instance otherwise.
2087 */
2088function rest_validate_enum( $value, $args, $param ) {
2089 $sanitized_value = rest_sanitize_value_from_schema( $value, $args, $param );
2090 if ( is_wp_error( $sanitized_value ) ) {
2091 return $sanitized_value;
2092 }
2093
2094 foreach ( $args['enum'] as $enum_value ) {
2095 if ( rest_are_values_equal( $sanitized_value, $enum_value ) ) {
2096 return true;
2097 }
2098 }
2099
2100 $encoded_enum_values = array();
2101 foreach ( $args['enum'] as $enum_value ) {
2102 $encoded_enum_values[] = is_scalar( $enum_value ) ? $enum_value : wp_json_encode( $enum_value );
2103 }
2104
2105 if ( count( $encoded_enum_values ) === 1 ) {
2106 /* translators: 1: Parameter, 2: Valid values. */
2107 return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not %2$s.' ), $param, $encoded_enum_values[0] ) );
2108 }
2109
2110 /* translators: 1: Parameter, 2: List of valid values. */
2111 return new WP_Error( 'rest_not_in_enum', wp_sprintf( __( '%1$s is not one of %2$l.' ), $param, $encoded_enum_values ) );
2112}
2113
2114/**
2115 * Get all valid JSON schema properties.
2116 *
2117 * @since 5.6.0
2118 *
2119 * @return string[] All valid JSON schema properties.
2120 */
2121function rest_get_allowed_schema_keywords() {
2122 return array(
2123 'title',
2124 'description',
2125 'default',
2126 'type',
2127 'format',
2128 'enum',
2129 'items',
2130 'properties',
2131 'additionalProperties',
2132 'patternProperties',
2133 'minProperties',
2134 'maxProperties',
2135 'minimum',
2136 'maximum',
2137 'exclusiveMinimum',
2138 'exclusiveMaximum',
2139 'multipleOf',
2140 'minLength',
2141 'maxLength',
2142 'pattern',
2143 'minItems',
2144 'maxItems',
2145 'uniqueItems',
2146 'anyOf',
2147 'oneOf',
2148 );
2149}
2150
2151/**
2152 * Validate a value based on a schema.
2153 *
2154 * @since 4.7.0
2155 * @since 4.9.0 Support the "object" type.
2156 * @since 5.2.0 Support validating "additionalProperties" against a schema.
2157 * @since 5.3.0 Support multiple types.
2158 * @since 5.4.0 Convert an empty string to an empty object.
2159 * @since 5.5.0 Add the "uuid" and "hex-color" formats.
2160 * Support the "minLength", "maxLength" and "pattern" keywords for strings.
2161 * Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
2162 * Validate required properties.
2163 * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
2164 * Support the "multipleOf" keyword for numbers and integers.
2165 * Support the "patternProperties" keyword for objects.
2166 * Support the "anyOf" and "oneOf" keywords.
2167 *
2168 * @param mixed $value The value to validate.
2169 * @param array $args Schema array to use for validation.
2170 * @param string $param The parameter name, used in error messages.
2171 * @return true|WP_Error
2172 */
2173function rest_validate_value_from_schema( $value, $args, $param = '' ) {
2174 if ( isset( $args['anyOf'] ) ) {
2175 $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2176 if ( is_wp_error( $matching_schema ) ) {
2177 return $matching_schema;
2178 }
2179
2180 if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2181 $args['type'] = $matching_schema['type'];
2182 }
2183 }
2184
2185 if ( isset( $args['oneOf'] ) ) {
2186 $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2187 if ( is_wp_error( $matching_schema ) ) {
2188 return $matching_schema;
2189 }
2190
2191 if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
2192 $args['type'] = $matching_schema['type'];
2193 }
2194 }
2195
2196 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2197
2198 if ( ! isset( $args['type'] ) ) {
2199 /* translators: %s: Parameter. */
2200 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2201 }
2202
2203 if ( is_array( $args['type'] ) ) {
2204 $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2205
2206 if ( ! $best_type ) {
2207 return new WP_Error(
2208 'rest_invalid_type',
2209 /* translators: 1: Parameter, 2: List of types. */
2210 sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
2211 array( 'param' => $param )
2212 );
2213 }
2214
2215 $args['type'] = $best_type;
2216 }
2217
2218 if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2219 _doing_it_wrong(
2220 __FUNCTION__,
2221 /* translators: 1: Parameter, 2: The list of allowed types. */
2222 wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2223 '5.5.0'
2224 );
2225 }
2226
2227 switch ( $args['type'] ) {
2228 case 'null':
2229 $is_valid = rest_validate_null_value_from_schema( $value, $param );
2230 break;
2231 case 'boolean':
2232 $is_valid = rest_validate_boolean_value_from_schema( $value, $param );
2233 break;
2234 case 'object':
2235 $is_valid = rest_validate_object_value_from_schema( $value, $args, $param );
2236 break;
2237 case 'array':
2238 $is_valid = rest_validate_array_value_from_schema( $value, $args, $param );
2239 break;
2240 case 'number':
2241 $is_valid = rest_validate_number_value_from_schema( $value, $args, $param );
2242 break;
2243 case 'string':
2244 $is_valid = rest_validate_string_value_from_schema( $value, $args, $param );
2245 break;
2246 case 'integer':
2247 $is_valid = rest_validate_integer_value_from_schema( $value, $args, $param );
2248 break;
2249 default:
2250 $is_valid = true;
2251 break;
2252 }
2253
2254 if ( is_wp_error( $is_valid ) ) {
2255 return $is_valid;
2256 }
2257
2258 if ( ! empty( $args['enum'] ) ) {
2259 $enum_contains_value = rest_validate_enum( $value, $args, $param );
2260 if ( is_wp_error( $enum_contains_value ) ) {
2261 return $enum_contains_value;
2262 }
2263 }
2264
2265 /*
2266 * The "format" keyword should only be applied to strings. However, for backward compatibility,
2267 * we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
2268 */
2269 if ( isset( $args['format'] )
2270 && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2271 ) {
2272 switch ( $args['format'] ) {
2273 case 'hex-color':
2274 if ( ! rest_parse_hex_color( $value ) ) {
2275 return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
2276 }
2277 break;
2278
2279 case 'date-time':
2280 if ( false === rest_parse_date( $value ) ) {
2281 return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
2282 }
2283 break;
2284
2285 case 'email':
2286 if ( ! is_email( $value ) ) {
2287 return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
2288 }
2289 break;
2290 case 'ip':
2291 if ( ! rest_is_ip_address( $value ) ) {
2292 /* translators: %s: IP address. */
2293 return new WP_Error( 'rest_invalid_ip', sprintf( __( '%s is not a valid IP address.' ), $param ) );
2294 }
2295 break;
2296 case 'uuid':
2297 if ( ! wp_is_uuid( $value ) ) {
2298 /* translators: %s: The name of a JSON field expecting a valid UUID. */
2299 return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
2300 }
2301 break;
2302 }
2303 }
2304
2305 return true;
2306}
2307
2308/**
2309 * Validates a null value based on a schema.
2310 *
2311 * @since 5.7.0
2312 *
2313 * @param mixed $value The value to validate.
2314 * @param string $param The parameter name, used in error messages.
2315 * @return true|WP_Error
2316 */
2317function rest_validate_null_value_from_schema( $value, $param ) {
2318 if ( null !== $value ) {
2319 return new WP_Error(
2320 'rest_invalid_type',
2321 /* translators: 1: Parameter, 2: Type name. */
2322 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
2323 array( 'param' => $param )
2324 );
2325 }
2326
2327 return true;
2328}
2329
2330/**
2331 * Validates a boolean value based on a schema.
2332 *
2333 * @since 5.7.0
2334 *
2335 * @param mixed $value The value to validate.
2336 * @param string $param The parameter name, used in error messages.
2337 * @return true|WP_Error
2338 */
2339function rest_validate_boolean_value_from_schema( $value, $param ) {
2340 if ( ! rest_is_boolean( $value ) ) {
2341 return new WP_Error(
2342 'rest_invalid_type',
2343 /* translators: 1: Parameter, 2: Type name. */
2344 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
2345 array( 'param' => $param )
2346 );
2347 }
2348
2349 return true;
2350}
2351
2352/**
2353 * Validates an object value based on a schema.
2354 *
2355 * @since 5.7.0
2356 *
2357 * @param mixed $value The value to validate.
2358 * @param array $args Schema array to use for validation.
2359 * @param string $param The parameter name, used in error messages.
2360 * @return true|WP_Error
2361 */
2362function rest_validate_object_value_from_schema( $value, $args, $param ) {
2363 if ( ! rest_is_object( $value ) ) {
2364 return new WP_Error(
2365 'rest_invalid_type',
2366 /* translators: 1: Parameter, 2: Type name. */
2367 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
2368 array( 'param' => $param )
2369 );
2370 }
2371
2372 $value = rest_sanitize_object( $value );
2373
2374 if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
2375 foreach ( $args['required'] as $name ) {
2376 if ( ! array_key_exists( $name, $value ) ) {
2377 return new WP_Error(
2378 'rest_property_required',
2379 /* translators: 1: Property of an object, 2: Parameter. */
2380 sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2381 );
2382 }
2383 }
2384 } elseif ( isset( $args['properties'] ) ) { // schema version 3
2385 foreach ( $args['properties'] as $name => $property ) {
2386 if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
2387 return new WP_Error(
2388 'rest_property_required',
2389 /* translators: 1: Property of an object, 2: Parameter. */
2390 sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param )
2391 );
2392 }
2393 }
2394 }
2395
2396 foreach ( $value as $property => $v ) {
2397 if ( isset( $args['properties'][ $property ] ) ) {
2398 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2399 if ( is_wp_error( $is_valid ) ) {
2400 return $is_valid;
2401 }
2402 continue;
2403 }
2404
2405 $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2406 if ( null !== $pattern_property_schema ) {
2407 $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2408 if ( is_wp_error( $is_valid ) ) {
2409 return $is_valid;
2410 }
2411 continue;
2412 }
2413
2414 if ( isset( $args['additionalProperties'] ) ) {
2415 if ( false === $args['additionalProperties'] ) {
2416 return new WP_Error(
2417 'rest_additional_properties_forbidden',
2418 /* translators: %s: Property of an object. */
2419 sprintf( __( '%1$s is not a valid property of Object.' ), $property )
2420 );
2421 }
2422
2423 if ( is_array( $args['additionalProperties'] ) ) {
2424 $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2425 if ( is_wp_error( $is_valid ) ) {
2426 return $is_valid;
2427 }
2428 }
2429 }
2430 }
2431
2432 if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
2433 return new WP_Error(
2434 'rest_too_few_properties',
2435 sprintf(
2436 /* translators: 1: Parameter, 2: Number. */
2437 _n(
2438 '%1$s must contain at least %2$s property.',
2439 '%1$s must contain at least %2$s properties.',
2440 $args['minProperties']
2441 ),
2442 $param,
2443 number_format_i18n( $args['minProperties'] )
2444 )
2445 );
2446 }
2447
2448 if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
2449 return new WP_Error(
2450 'rest_too_many_properties',
2451 sprintf(
2452 /* translators: 1: Parameter, 2: Number. */
2453 _n(
2454 '%1$s must contain at most %2$s property.',
2455 '%1$s must contain at most %2$s properties.',
2456 $args['maxProperties']
2457 ),
2458 $param,
2459 number_format_i18n( $args['maxProperties'] )
2460 )
2461 );
2462 }
2463
2464 return true;
2465}
2466
2467/**
2468 * Validates an array value based on a schema.
2469 *
2470 * @since 5.7.0
2471 *
2472 * @param mixed $value The value to validate.
2473 * @param array $args Schema array to use for validation.
2474 * @param string $param The parameter name, used in error messages.
2475 * @return true|WP_Error
2476 */
2477function rest_validate_array_value_from_schema( $value, $args, $param ) {
2478 if ( ! rest_is_array( $value ) ) {
2479 return new WP_Error(
2480 'rest_invalid_type',
2481 /* translators: 1: Parameter, 2: Type name. */
2482 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
2483 array( 'param' => $param )
2484 );
2485 }
2486
2487 $value = rest_sanitize_array( $value );
2488
2489 if ( isset( $args['items'] ) ) {
2490 foreach ( $value as $index => $v ) {
2491 $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2492 if ( is_wp_error( $is_valid ) ) {
2493 return $is_valid;
2494 }
2495 }
2496 }
2497
2498 if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
2499 return new WP_Error(
2500 'rest_too_few_items',
2501 sprintf(
2502 /* translators: 1: Parameter, 2: Number. */
2503 _n(
2504 '%1$s must contain at least %2$s item.',
2505 '%1$s must contain at least %2$s items.',
2506 $args['minItems']
2507 ),
2508 $param,
2509 number_format_i18n( $args['minItems'] )
2510 )
2511 );
2512 }
2513
2514 if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
2515 return new WP_Error(
2516 'rest_too_many_items',
2517 sprintf(
2518 /* translators: 1: Parameter, 2: Number. */
2519 _n(
2520 '%1$s must contain at most %2$s item.',
2521 '%1$s must contain at most %2$s items.',
2522 $args['maxItems']
2523 ),
2524 $param,
2525 number_format_i18n( $args['maxItems'] )
2526 )
2527 );
2528 }
2529
2530 if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2531 /* translators: %s: Parameter. */
2532 return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2533 }
2534
2535 return true;
2536}
2537
2538/**
2539 * Validates a number value based on a schema.
2540 *
2541 * @since 5.7.0
2542 *
2543 * @param mixed $value The value to validate.
2544 * @param array $args Schema array to use for validation.
2545 * @param string $param The parameter name, used in error messages.
2546 * @return true|WP_Error
2547 */
2548function rest_validate_number_value_from_schema( $value, $args, $param ) {
2549 if ( ! is_numeric( $value ) ) {
2550 return new WP_Error(
2551 'rest_invalid_type',
2552 /* translators: 1: Parameter, 2: Type name. */
2553 sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
2554 array( 'param' => $param )
2555 );
2556 }
2557
2558 if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
2559 return new WP_Error(
2560 'rest_invalid_multiple',
2561 /* translators: 1: Parameter, 2: Multiplier. */
2562 sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] )
2563 );
2564 }
2565
2566 if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
2567 if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
2568 return new WP_Error(
2569 'rest_out_of_bounds',
2570 /* translators: 1: Parameter, 2: Minimum number. */
2571 sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] )
2572 );
2573 }
2574
2575 if ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
2576 return new WP_Error(
2577 'rest_out_of_bounds',
2578 /* translators: 1: Parameter, 2: Minimum number. */
2579 sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] )
2580 );
2581 }
2582 }
2583
2584 if ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
2585 if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
2586 return new WP_Error(
2587 'rest_out_of_bounds',
2588 /* translators: 1: Parameter, 2: Maximum number. */
2589 sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] )
2590 );
2591 }
2592
2593 if ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
2594 return new WP_Error(
2595 'rest_out_of_bounds',
2596 /* translators: 1: Parameter, 2: Maximum number. */
2597 sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] )
2598 );
2599 }
2600 }
2601
2602 if ( isset( $args['minimum'], $args['maximum'] ) ) {
2603 if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
2604 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
2605 return new WP_Error(
2606 'rest_out_of_bounds',
2607 sprintf(
2608 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2609 __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ),
2610 $param,
2611 $args['minimum'],
2612 $args['maximum']
2613 )
2614 );
2615 }
2616 }
2617
2618 if ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2619 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
2620 return new WP_Error(
2621 'rest_out_of_bounds',
2622 sprintf(
2623 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2624 __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ),
2625 $param,
2626 $args['minimum'],
2627 $args['maximum']
2628 )
2629 );
2630 }
2631 }
2632
2633 if ( ! empty( $args['exclusiveMaximum'] ) && empty( $args['exclusiveMinimum'] ) ) {
2634 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
2635 return new WP_Error(
2636 'rest_out_of_bounds',
2637 sprintf(
2638 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2639 __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ),
2640 $param,
2641 $args['minimum'],
2642 $args['maximum']
2643 )
2644 );
2645 }
2646 }
2647
2648 if ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
2649 if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
2650 return new WP_Error(
2651 'rest_out_of_bounds',
2652 sprintf(
2653 /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
2654 __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ),
2655 $param,
2656 $args['minimum'],
2657 $args['maximum']
2658 )
2659 );
2660 }
2661 }
2662 }
2663
2664 return true;
2665}
2666
2667/**
2668 * Validates a string value based on a schema.
2669 *
2670 * @since 5.7.0
2671 *
2672 * @param mixed $value The value to validate.
2673 * @param array $args Schema array to use for validation.
2674 * @param string $param The parameter name, used in error messages.
2675 * @return true|WP_Error
2676 */
2677function rest_validate_string_value_from_schema( $value, $args, $param ) {
2678 if ( ! is_string( $value ) ) {
2679 return new WP_Error(
2680 'rest_invalid_type',
2681 /* translators: 1: Parameter, 2: Type name. */
2682 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
2683 array( 'param' => $param )
2684 );
2685 }
2686
2687 if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
2688 return new WP_Error(
2689 'rest_too_short',
2690 sprintf(
2691 /* translators: 1: Parameter, 2: Number of characters. */
2692 _n(
2693 '%1$s must be at least %2$s character long.',
2694 '%1$s must be at least %2$s characters long.',
2695 $args['minLength']
2696 ),
2697 $param,
2698 number_format_i18n( $args['minLength'] )
2699 )
2700 );
2701 }
2702
2703 if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
2704 return new WP_Error(
2705 'rest_too_long',
2706 sprintf(
2707 /* translators: 1: Parameter, 2: Number of characters. */
2708 _n(
2709 '%1$s must be at most %2$s character long.',
2710 '%1$s must be at most %2$s characters long.',
2711 $args['maxLength']
2712 ),
2713 $param,
2714 number_format_i18n( $args['maxLength'] )
2715 )
2716 );
2717 }
2718
2719 if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
2720 return new WP_Error(
2721 'rest_invalid_pattern',
2722 /* translators: 1: Parameter, 2: Pattern. */
2723 sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] )
2724 );
2725 }
2726
2727 return true;
2728}
2729
2730/**
2731 * Validates an integer value based on a schema.
2732 *
2733 * @since 5.7.0
2734 *
2735 * @param mixed $value The value to validate.
2736 * @param array $args Schema array to use for validation.
2737 * @param string $param The parameter name, used in error messages.
2738 * @return true|WP_Error
2739 */
2740function rest_validate_integer_value_from_schema( $value, $args, $param ) {
2741 $is_valid_number = rest_validate_number_value_from_schema( $value, $args, $param );
2742 if ( is_wp_error( $is_valid_number ) ) {
2743 return $is_valid_number;
2744 }
2745
2746 if ( ! rest_is_integer( $value ) ) {
2747 return new WP_Error(
2748 'rest_invalid_type',
2749 /* translators: 1: Parameter, 2: Type name. */
2750 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
2751 array( 'param' => $param )
2752 );
2753 }
2754
2755 return true;
2756}
2757
2758/**
2759 * Sanitize a value based on a schema.
2760 *
2761 * @since 4.7.0
2762 * @since 5.5.0 Added the `$param` parameter.
2763 * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
2764 * @since 5.9.0 Added `text-field` and `textarea-field` formats.
2765 *
2766 * @param mixed $value The value to sanitize.
2767 * @param array $args Schema array to use for sanitization.
2768 * @param string $param The parameter name, used in error messages.
2769 * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
2770 */
2771function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
2772 if ( isset( $args['anyOf'] ) ) {
2773 $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
2774 if ( is_wp_error( $matching_schema ) ) {
2775 return $matching_schema;
2776 }
2777
2778 if ( ! isset( $args['type'] ) ) {
2779 $args['type'] = $matching_schema['type'];
2780 }
2781
2782 $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2783 }
2784
2785 if ( isset( $args['oneOf'] ) ) {
2786 $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
2787 if ( is_wp_error( $matching_schema ) ) {
2788 return $matching_schema;
2789 }
2790
2791 if ( ! isset( $args['type'] ) ) {
2792 $args['type'] = $matching_schema['type'];
2793 }
2794
2795 $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
2796 }
2797
2798 $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
2799
2800 if ( ! isset( $args['type'] ) ) {
2801 /* translators: %s: Parameter. */
2802 _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
2803 }
2804
2805 if ( is_array( $args['type'] ) ) {
2806 $best_type = rest_handle_multi_type_schema( $value, $args, $param );
2807
2808 if ( ! $best_type ) {
2809 return null;
2810 }
2811
2812 $args['type'] = $best_type;
2813 }
2814
2815 if ( ! in_array( $args['type'], $allowed_types, true ) ) {
2816 _doing_it_wrong(
2817 __FUNCTION__,
2818 /* translators: 1: Parameter, 2: The list of allowed types. */
2819 wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
2820 '5.5.0'
2821 );
2822 }
2823
2824 if ( 'array' === $args['type'] ) {
2825 $value = rest_sanitize_array( $value );
2826
2827 if ( ! empty( $args['items'] ) ) {
2828 foreach ( $value as $index => $v ) {
2829 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
2830 }
2831 }
2832
2833 if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
2834 /* translators: %s: Parameter. */
2835 return new WP_Error( 'rest_duplicate_items', sprintf( __( '%s has duplicate items.' ), $param ) );
2836 }
2837
2838 return $value;
2839 }
2840
2841 if ( 'object' === $args['type'] ) {
2842 $value = rest_sanitize_object( $value );
2843
2844 foreach ( $value as $property => $v ) {
2845 if ( isset( $args['properties'][ $property ] ) ) {
2846 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
2847 continue;
2848 }
2849
2850 $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
2851 if ( null !== $pattern_property_schema ) {
2852 $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
2853 continue;
2854 }
2855
2856 if ( isset( $args['additionalProperties'] ) ) {
2857 if ( false === $args['additionalProperties'] ) {
2858 unset( $value[ $property ] );
2859 } elseif ( is_array( $args['additionalProperties'] ) ) {
2860 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
2861 }
2862 }
2863 }
2864
2865 return $value;
2866 }
2867
2868 if ( 'null' === $args['type'] ) {
2869 return null;
2870 }
2871
2872 if ( 'integer' === $args['type'] ) {
2873 return (int) $value;
2874 }
2875
2876 if ( 'number' === $args['type'] ) {
2877 return (float) $value;
2878 }
2879
2880 if ( 'boolean' === $args['type'] ) {
2881 return rest_sanitize_boolean( $value );
2882 }
2883
2884 // This behavior matches rest_validate_value_from_schema().
2885 if ( isset( $args['format'] )
2886 && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
2887 ) {
2888 switch ( $args['format'] ) {
2889 case 'hex-color':
2890 return (string) sanitize_hex_color( $value );
2891
2892 case 'date-time':
2893 return sanitize_text_field( $value );
2894
2895 case 'email':
2896 // sanitize_email() validates, which would be unexpected.
2897 return sanitize_text_field( $value );
2898
2899 case 'uri':
2900 return sanitize_url( $value );
2901
2902 case 'ip':
2903 return sanitize_text_field( $value );
2904
2905 case 'uuid':
2906 return sanitize_text_field( $value );
2907
2908 case 'text-field':
2909 return sanitize_text_field( $value );
2910
2911 case 'textarea-field':
2912 return sanitize_textarea_field( $value );
2913 }
2914 }
2915
2916 if ( 'string' === $args['type'] ) {
2917 return (string) $value;
2918 }
2919
2920 return $value;
2921}
2922
2923/**
2924 * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
2925 * Expected to be called in the context of `array_reduce`.
2926 *
2927 * @since 5.0.0
2928 *
2929 * @param array $memo Reduce accumulator.
2930 * @param string $path REST API path to preload.
2931 * @return array Modified reduce accumulator.
2932 */
2933function rest_preload_api_request( $memo, $path ) {
2934 /*
2935 * array_reduce() doesn't support passing an array in PHP 5.2,
2936 * so we need to make sure we start with one.
2937 */
2938 if ( ! is_array( $memo ) ) {
2939 $memo = array();
2940 }
2941
2942 if ( empty( $path ) ) {
2943 return $memo;
2944 }
2945
2946 $method = 'GET';
2947 if ( is_array( $path ) && 2 === count( $path ) ) {
2948 $method = end( $path );
2949 $path = reset( $path );
2950
2951 if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
2952 $method = 'GET';
2953 }
2954 }
2955
2956 // Remove trailing slashes at the end of the REST API path (query part).
2957 $path = untrailingslashit( $path );
2958 if ( empty( $path ) ) {
2959 $path = '/';
2960 }
2961
2962 $path_parts = parse_url( $path );
2963 if ( false === $path_parts ) {
2964 return $memo;
2965 }
2966
2967 if ( isset( $path_parts['path'] ) && '/' !== $path_parts['path'] ) {
2968 // Remove trailing slashes from the "path" part of the REST API path.
2969 $path_parts['path'] = untrailingslashit( $path_parts['path'] );
2970 $path = str_contains( $path, '?' ) ?
2971 $path_parts['path'] . '?' . ( $path_parts['query'] ?? '' ) :
2972 $path_parts['path'];
2973 }
2974
2975 $request = new WP_REST_Request( $method, $path_parts['path'] );
2976 if ( ! empty( $path_parts['query'] ) ) {
2977 parse_str( $path_parts['query'], $query_params );
2978 $request->set_query_params( $query_params );
2979 }
2980
2981 $response = rest_do_request( $request );
2982 if ( 200 === $response->status ) {
2983 $server = rest_get_server();
2984 /** This filter is documented in wp-includes/rest-api/class-wp-rest-server.php */
2985 $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $server, $request );
2986 $embed = $request->has_param( '_embed' ) ? rest_parse_embed_param( $request['_embed'] ) : false;
2987 $data = (array) $server->response_to_data( $response, $embed );
2988
2989 if ( 'OPTIONS' === $method ) {
2990 $memo[ $method ][ $path ] = array(
2991 'body' => $data,
2992 'headers' => $response->headers,
2993 );
2994 } else {
2995 $memo[ $path ] = array(
2996 'body' => $data,
2997 'headers' => $response->headers,
2998 );
2999 }
3000 }
3001
3002 return $memo;
3003}
3004
3005/**
3006 * Parses the "_embed" parameter into the list of resources to embed.
3007 *
3008 * @since 5.4.0
3009 *
3010 * @param string|array $embed Raw "_embed" parameter value.
3011 * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
3012 */
3013function rest_parse_embed_param( $embed ) {
3014 if ( ! $embed || 'true' === $embed || '1' === $embed ) {
3015 return true;
3016 }
3017
3018 $rels = wp_parse_list( $embed );
3019
3020 if ( ! $rels ) {
3021 return true;
3022 }
3023
3024 return $rels;
3025}
3026
3027/**
3028 * Filters the response to remove any fields not available in the given context.
3029 *
3030 * @since 5.5.0
3031 * @since 5.6.0 Support the "patternProperties" keyword for objects.
3032 * Support the "anyOf" and "oneOf" keywords.
3033 *
3034 * @param array|object $response_data The response data to modify.
3035 * @param array $schema The schema for the endpoint used to filter the response.
3036 * @param string $context The requested context.
3037 * @return array|object The filtered response data.
3038 */
3039function rest_filter_response_by_context( $response_data, $schema, $context ) {
3040 if ( isset( $schema['anyOf'] ) ) {
3041 $matching_schema = rest_find_any_matching_schema( $response_data, $schema, '' );
3042 if ( ! is_wp_error( $matching_schema ) ) {
3043 if ( ! isset( $schema['type'] ) ) {
3044 $schema['type'] = $matching_schema['type'];
3045 }
3046
3047 $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context );
3048 }
3049 }
3050
3051 if ( isset( $schema['oneOf'] ) ) {
3052 $matching_schema = rest_find_one_matching_schema( $response_data, $schema, '', true );
3053 if ( ! is_wp_error( $matching_schema ) ) {
3054 if ( ! isset( $schema['type'] ) ) {
3055 $schema['type'] = $matching_schema['type'];
3056 }
3057
3058 $response_data = rest_filter_response_by_context( $response_data, $matching_schema, $context );
3059 }
3060 }
3061
3062 if ( ! is_array( $response_data ) && ! is_object( $response_data ) ) {
3063 return $response_data;
3064 }
3065
3066 if ( isset( $schema['type'] ) ) {
3067 $type = $schema['type'];
3068 } elseif ( isset( $schema['properties'] ) ) {
3069 $type = 'object'; // Back compat if a developer accidentally omitted the type.
3070 } else {
3071 return $response_data;
3072 }
3073
3074 $is_array_type = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
3075 $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
3076
3077 if ( $is_array_type && $is_object_type ) {
3078 if ( rest_is_array( $response_data ) ) {
3079 $is_object_type = false;
3080 } else {
3081 $is_array_type = false;
3082 }
3083 }
3084
3085 $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
3086
3087 foreach ( $response_data as $key => $value ) {
3088 $check = array();
3089
3090 if ( $is_array_type ) {
3091 $check = isset( $schema['items'] ) ? $schema['items'] : array();
3092 } elseif ( $is_object_type ) {
3093 if ( isset( $schema['properties'][ $key ] ) ) {
3094 $check = $schema['properties'][ $key ];
3095 } else {
3096 $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
3097 if ( null !== $pattern_property_schema ) {
3098 $check = $pattern_property_schema;
3099 } elseif ( $has_additional_properties ) {
3100 $check = $schema['additionalProperties'];
3101 }
3102 }
3103 }
3104
3105 if ( ! isset( $check['context'] ) ) {
3106 continue;
3107 }
3108
3109 if ( ! in_array( $context, $check['context'], true ) ) {
3110 if ( $is_array_type ) {
3111 // All array items share schema, so there's no need to check each one.
3112 $response_data = array();
3113 break;
3114 }
3115
3116 if ( is_object( $response_data ) ) {
3117 unset( $response_data->$key );
3118 } else {
3119 unset( $response_data[ $key ] );
3120 }
3121 } elseif ( is_array( $value ) || is_object( $value ) ) {
3122 $new_value = rest_filter_response_by_context( $value, $check, $context );
3123
3124 if ( is_object( $response_data ) ) {
3125 $response_data->$key = $new_value;
3126 } else {
3127 $response_data[ $key ] = $new_value;
3128 }
3129 }
3130 }
3131
3132 return $response_data;
3133}
3134
3135/**
3136 * Sets the "additionalProperties" to false by default for all object definitions in the schema.
3137 *
3138 * @since 5.5.0
3139 * @since 5.6.0 Support the "patternProperties" keyword.
3140 *
3141 * @param array $schema The schema to modify.
3142 * @return array The modified schema.
3143 */
3144function rest_default_additional_properties_to_false( $schema ) {
3145 $type = (array) $schema['type'];
3146
3147 if ( in_array( 'object', $type, true ) ) {
3148 if ( isset( $schema['properties'] ) ) {
3149 foreach ( $schema['properties'] as $key => $child_schema ) {
3150 $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3151 }
3152 }
3153
3154 if ( isset( $schema['patternProperties'] ) ) {
3155 foreach ( $schema['patternProperties'] as $key => $child_schema ) {
3156 $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
3157 }
3158 }
3159
3160 if ( ! isset( $schema['additionalProperties'] ) ) {
3161 $schema['additionalProperties'] = false;
3162 }
3163 }
3164
3165 if ( in_array( 'array', $type, true ) ) {
3166 if ( isset( $schema['items'] ) ) {
3167 $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
3168 }
3169 }
3170
3171 return $schema;
3172}
3173
3174/**
3175 * Gets the REST API route for a post.
3176 *
3177 * @since 5.5.0
3178 *
3179 * @param int|WP_Post $post Post ID or post object.
3180 * @return string The route path with a leading slash for the given post,
3181 * or an empty string if there is not a route.
3182 */
3183function rest_get_route_for_post( $post ) {
3184 $post = get_post( $post );
3185
3186 if ( ! $post instanceof WP_Post ) {
3187 return '';
3188 }
3189
3190 $post_type_route = rest_get_route_for_post_type_items( $post->post_type );
3191 if ( ! $post_type_route ) {
3192 return '';
3193 }
3194
3195 $route = sprintf( '%s/%d', $post_type_route, $post->ID );
3196
3197 /**
3198 * Filters the REST API route for a post.
3199 *
3200 * @since 5.5.0
3201 *
3202 * @param string $route The route path.
3203 * @param WP_Post $post The post object.
3204 */
3205 return apply_filters( 'rest_route_for_post', $route, $post );
3206}
3207
3208/**
3209 * Gets the REST API route for a post type.
3210 *
3211 * @since 5.9.0
3212 *
3213 * @param string $post_type The name of a registered post type.
3214 * @return string The route path with a leading slash for the given post type,
3215 * or an empty string if there is not a route.
3216 */
3217function rest_get_route_for_post_type_items( $post_type ) {
3218 $post_type = get_post_type_object( $post_type );
3219 if ( ! $post_type ) {
3220 return '';
3221 }
3222
3223 if ( ! $post_type->show_in_rest ) {
3224 return '';
3225 }
3226
3227 $namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2';
3228 $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
3229 $route = sprintf( '/%s/%s', $namespace, $rest_base );
3230
3231 /**
3232 * Filters the REST API route for a post type.
3233 *
3234 * @since 5.9.0
3235 *
3236 * @param string $route The route path.
3237 * @param WP_Post_Type $post_type The post type object.
3238 */
3239 return apply_filters( 'rest_route_for_post_type_items', $route, $post_type );
3240}
3241
3242/**
3243 * Gets the REST API route for a term.
3244 *
3245 * @since 5.5.0
3246 *
3247 * @param int|WP_Term $term Term ID or term object.
3248 * @return string The route path with a leading slash for the given term,
3249 * or an empty string if there is not a route.
3250 */
3251function rest_get_route_for_term( $term ) {
3252 $term = get_term( $term );
3253
3254 if ( ! $term instanceof WP_Term ) {
3255 return '';
3256 }
3257
3258 $taxonomy_route = rest_get_route_for_taxonomy_items( $term->taxonomy );
3259 if ( ! $taxonomy_route ) {
3260 return '';
3261 }
3262
3263 $route = sprintf( '%s/%d', $taxonomy_route, $term->term_id );
3264
3265 /**
3266 * Filters the REST API route for a term.
3267 *
3268 * @since 5.5.0
3269 *
3270 * @param string $route The route path.
3271 * @param WP_Term $term The term object.
3272 */
3273 return apply_filters( 'rest_route_for_term', $route, $term );
3274}
3275
3276/**
3277 * Gets the REST API route for a taxonomy.
3278 *
3279 * @since 5.9.0
3280 *
3281 * @param string $taxonomy Name of taxonomy.
3282 * @return string The route path with a leading slash for the given taxonomy.
3283 */
3284function rest_get_route_for_taxonomy_items( $taxonomy ) {
3285 $taxonomy = get_taxonomy( $taxonomy );
3286 if ( ! $taxonomy ) {
3287 return '';
3288 }
3289
3290 if ( ! $taxonomy->show_in_rest ) {
3291 return '';
3292 }
3293
3294 $namespace = ! empty( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2';
3295 $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
3296 $route = sprintf( '/%s/%s', $namespace, $rest_base );
3297
3298 /**
3299 * Filters the REST API route for a taxonomy.
3300 *
3301 * @since 5.9.0
3302 *
3303 * @param string $route The route path.
3304 * @param WP_Taxonomy $taxonomy The taxonomy object.
3305 */
3306 return apply_filters( 'rest_route_for_taxonomy_items', $route, $taxonomy );
3307}
3308
3309/**
3310 * Gets the REST route for the currently queried object.
3311 *
3312 * @since 5.5.0
3313 *
3314 * @return string The REST route of the resource, or an empty string if no resource identified.
3315 */
3316function rest_get_queried_resource_route() {
3317 if ( is_singular() ) {
3318 $route = rest_get_route_for_post( get_queried_object() );
3319 } elseif ( is_category() || is_tag() || is_tax() ) {
3320 $route = rest_get_route_for_term( get_queried_object() );
3321 } elseif ( is_author() ) {
3322 $route = '/wp/v2/users/' . get_queried_object_id();
3323 } else {
3324 $route = '';
3325 }
3326
3327 /**
3328 * Filters the REST route for the currently queried object.
3329 *
3330 * @since 5.5.0
3331 *
3332 * @param string $link The route with a leading slash, or an empty string.
3333 */
3334 return apply_filters( 'rest_queried_resource_route', $route );
3335}
3336
3337/**
3338 * Retrieves an array of endpoint arguments from the item schema and endpoint method.
3339 *
3340 * @since 5.6.0
3341 *
3342 * @param array $schema The full JSON schema for the endpoint.
3343 * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
3344 * checked for required values and may fall-back to a given default, this is not done
3345 * on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
3346 * @return array The endpoint arguments.
3347 */
3348function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
3349
3350 $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
3351 $endpoint_args = array();
3352 $valid_schema_properties = rest_get_allowed_schema_keywords();
3353 $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
3354
3355 foreach ( $schema_properties as $field_id => $params ) {
3356
3357 // Arguments specified as `readonly` are not allowed to be set.
3358 if ( ! empty( $params['readonly'] ) ) {
3359 continue;
3360 }
3361
3362 $endpoint_args[ $field_id ] = array(
3363 'validate_callback' => 'rest_validate_request_arg',
3364 'sanitize_callback' => 'rest_sanitize_request_arg',
3365 );
3366
3367 if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
3368 $endpoint_args[ $field_id ]['default'] = $params['default'];
3369 }
3370
3371 if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
3372 $endpoint_args[ $field_id ]['required'] = true;
3373 }
3374
3375 foreach ( $valid_schema_properties as $schema_prop ) {
3376 if ( isset( $params[ $schema_prop ] ) ) {
3377 $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
3378 }
3379 }
3380
3381 // Merge in any options provided by the schema property.
3382 if ( isset( $params['arg_options'] ) ) {
3383
3384 // Only use required / default from arg_options on CREATABLE endpoints.
3385 if ( WP_REST_Server::CREATABLE !== $method ) {
3386 $params['arg_options'] = array_diff_key(
3387 $params['arg_options'],
3388 array(
3389 'required' => '',
3390 'default' => '',
3391 )
3392 );
3393 }
3394
3395 $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
3396 }
3397 }
3398
3399 return $endpoint_args;
3400}
3401
3402
3403/**
3404 * Converts an error to a response object.
3405 *
3406 * This iterates over all error codes and messages to change it into a flat
3407 * array. This enables simpler client behavior, as it is represented as a
3408 * list in JSON rather than an object/map.
3409 *
3410 * @since 5.7.0
3411 *
3412 * @param WP_Error $error WP_Error instance.
3413 *
3414 * @return WP_REST_Response List of associative arrays with code and message keys.
3415 */
3416function rest_convert_error_to_response( $error ) {
3417 $status = array_reduce(
3418 $error->get_all_error_data(),
3419 static function ( $status, $error_data ) {
3420 return is_array( $error_data ) && isset( $error_data['status'] ) ? $error_data['status'] : $status;
3421 },
3422 500
3423 );
3424
3425 $errors = array();
3426
3427 foreach ( (array) $error->errors as $code => $messages ) {
3428 $all_data = $error->get_all_error_data( $code );
3429 $last_data = array_pop( $all_data );
3430
3431 foreach ( (array) $messages as $message ) {
3432 $formatted = array(
3433 'code' => $code,
3434 'message' => $message,
3435 'data' => $last_data,
3436 );
3437
3438 if ( $all_data ) {
3439 $formatted['additional_data'] = $all_data;
3440 }
3441
3442 $errors[] = $formatted;
3443 }
3444 }
3445
3446 $data = $errors[0];
3447 if ( count( $errors ) > 1 ) {
3448 // Remove the primary error.
3449 array_shift( $errors );
3450 $data['additional_errors'] = $errors;
3451 }
3452
3453 return new WP_REST_Response( $data, $status );
3454}
3455
3456/**
3457 * Checks whether a REST API endpoint request is currently being handled.
3458 *
3459 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
3460 *
3461 * @since 6.5.0
3462 *
3463 * @global WP_REST_Server $wp_rest_server REST server instance.
3464 *
3465 * @return bool True if a REST endpoint request is currently being handled, false otherwise.
3466 */
3467function wp_is_rest_endpoint() {
3468 /* @var WP_REST_Server $wp_rest_server */
3469 global $wp_rest_server;
3470
3471 // Check whether this is a standalone REST request.
3472 $is_rest_endpoint = wp_is_serving_rest_request();
3473 if ( ! $is_rest_endpoint ) {
3474 // Otherwise, check whether an internal REST request is currently being handled.
3475 $is_rest_endpoint = isset( $wp_rest_server )
3476 && $wp_rest_server->is_dispatching();
3477 }
3478
3479 /**
3480 * Filters whether a REST endpoint request is currently being handled.
3481 *
3482 * This may be a standalone REST API request, or an internal request dispatched from within a regular page load.
3483 *
3484 * @since 6.5.0
3485 *
3486 * @param bool $is_request_endpoint Whether a REST endpoint request is currently being handled.
3487 */
3488 return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint );
3489}
3490
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