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