wp/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
changeset 7 cf61fcea0001
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
       
     1 <?php
       
     2 /**
       
     3  * REST API: WP_REST_Controller class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage REST_API
       
     7  * @since 4.7.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Core base controller for managing and interacting with REST API items.
       
    12  *
       
    13  * @since 4.7.0
       
    14  */
       
    15 abstract class WP_REST_Controller {
       
    16 
       
    17 	/**
       
    18 	 * The namespace of this controller's route.
       
    19 	 *
       
    20 	 * @since 4.7.0
       
    21 	 * @var string
       
    22 	 */
       
    23 	protected $namespace;
       
    24 
       
    25 	/**
       
    26 	 * The base of this controller's route.
       
    27 	 *
       
    28 	 * @since 4.7.0
       
    29 	 * @var string
       
    30 	 */
       
    31 	protected $rest_base;
       
    32 
       
    33 	/**
       
    34 	 * Registers the routes for the objects of the controller.
       
    35 	 *
       
    36 	 * @since 4.7.0
       
    37 	 */
       
    38 	public function register_routes() {
       
    39 		/* translators: %s: register_routes() */
       
    40 		_doing_it_wrong( 'WP_REST_Controller::register_routes', sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ), '4.7' );
       
    41 	}
       
    42 
       
    43 	/**
       
    44 	 * Checks if a given request has access to get items.
       
    45 	 *
       
    46 	 * @since 4.7.0
       
    47 	 *
       
    48 	 * @param WP_REST_Request $request Full data about the request.
       
    49 	 * @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
       
    50 	 */
       
    51 	public function get_items_permissions_check( $request ) {
       
    52 		/* translators: %s: method name */
       
    53 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
    54 	}
       
    55 
       
    56 	/**
       
    57 	 * Retrieves a collection of items.
       
    58 	 *
       
    59 	 * @since 4.7.0
       
    60 	 *
       
    61 	 * @param WP_REST_Request $request Full data about the request.
       
    62 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
    63 	 */
       
    64 	public function get_items( $request ) {
       
    65 		/* translators: %s: method name */
       
    66 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
    67 	}
       
    68 
       
    69 	/**
       
    70 	 * Checks if a given request has access to get a specific item.
       
    71 	 *
       
    72 	 * @since 4.7.0
       
    73 	 *
       
    74 	 * @param WP_REST_Request $request Full data about the request.
       
    75 	 * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
       
    76 	 */
       
    77 	public function get_item_permissions_check( $request ) {
       
    78 		/* translators: %s: method name */
       
    79 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
    80 	}
       
    81 
       
    82 	/**
       
    83 	 * Retrieves one item from the collection.
       
    84 	 *
       
    85 	 * @since 4.7.0
       
    86 	 *
       
    87 	 * @param WP_REST_Request $request Full data about the request.
       
    88 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
    89 	 */
       
    90 	public function get_item( $request ) {
       
    91 		/* translators: %s: method name */
       
    92 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
    93 	}
       
    94 
       
    95 	/**
       
    96 	 * Checks if a given request has access to create items.
       
    97 	 *
       
    98 	 * @since 4.7.0
       
    99 	 *
       
   100 	 * @param WP_REST_Request $request Full data about the request.
       
   101 	 * @return WP_Error|bool True if the request has access to create items, WP_Error object otherwise.
       
   102 	 */
       
   103 	public function create_item_permissions_check( $request ) {
       
   104 		/* translators: %s: method name */
       
   105 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   106 	}
       
   107 
       
   108 	/**
       
   109 	 * Creates one item from the collection.
       
   110 	 *
       
   111 	 * @since 4.7.0
       
   112 	 *
       
   113 	 * @param WP_REST_Request $request Full data about the request.
       
   114 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
   115 	 */
       
   116 	public function create_item( $request ) {
       
   117 		/* translators: %s: method name */
       
   118 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   119 	}
       
   120 
       
   121 	/**
       
   122 	 * Checks if a given request has access to update a specific item.
       
   123 	 *
       
   124 	 * @since 4.7.0
       
   125 	 *
       
   126 	 * @param WP_REST_Request $request Full data about the request.
       
   127 	 * @return WP_Error|bool True if the request has access to update the item, WP_Error object otherwise.
       
   128 	 */
       
   129 	public function update_item_permissions_check( $request ) {
       
   130 		/* translators: %s: method name */
       
   131 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   132 	}
       
   133 
       
   134 	/**
       
   135 	 * Updates one item from the collection.
       
   136 	 *
       
   137 	 * @since 4.7.0
       
   138 	 *
       
   139 	 * @param WP_REST_Request $request Full data about the request.
       
   140 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
   141 	 */
       
   142 	public function update_item( $request ) {
       
   143 		/* translators: %s: method name */
       
   144 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   145 	}
       
   146 
       
   147 	/**
       
   148 	 * Checks if a given request has access to delete a specific item.
       
   149 	 *
       
   150 	 * @since 4.7.0
       
   151 	 *
       
   152 	 * @param WP_REST_Request $request Full data about the request.
       
   153 	 * @return WP_Error|bool True if the request has access to delete the item, WP_Error object otherwise.
       
   154 	 */
       
   155 	public function delete_item_permissions_check( $request ) {
       
   156 		/* translators: %s: method name */
       
   157 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   158 	}
       
   159 
       
   160 	/**
       
   161 	 * Deletes one item from the collection.
       
   162 	 *
       
   163 	 * @since 4.7.0
       
   164 	 *
       
   165 	 * @param WP_REST_Request $request Full data about the request.
       
   166 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
   167 	 */
       
   168 	public function delete_item( $request ) {
       
   169 		/* translators: %s: method name */
       
   170 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   171 	}
       
   172 
       
   173 	/**
       
   174 	 * Prepares one item for create or update operation.
       
   175 	 *
       
   176 	 * @since 4.7.0
       
   177 	 *
       
   178 	 * @param WP_REST_Request $request Request object.
       
   179 	 * @return WP_Error|object The prepared item, or WP_Error object on failure.
       
   180 	 */
       
   181 	protected function prepare_item_for_database( $request ) {
       
   182 		/* translators: %s: method name */
       
   183 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   184 	}
       
   185 
       
   186 	/**
       
   187 	 * Prepares the item for the REST response.
       
   188 	 *
       
   189 	 * @since 4.7.0
       
   190 	 *
       
   191 	 * @param mixed           $item    WordPress representation of the item.
       
   192 	 * @param WP_REST_Request $request Request object.
       
   193 	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
       
   194 	 */
       
   195 	public function prepare_item_for_response( $item, $request ) {
       
   196 		/* translators: %s: method name */
       
   197 		return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
       
   198 	}
       
   199 
       
   200 	/**
       
   201 	 * Prepares a response for insertion into a collection.
       
   202 	 *
       
   203 	 * @since 4.7.0
       
   204 	 *
       
   205 	 * @param WP_REST_Response $response Response object.
       
   206 	 * @return array|mixed Response data, ready for insertion into collection data.
       
   207 	 */
       
   208 	public function prepare_response_for_collection( $response ) {
       
   209 		if ( ! ( $response instanceof WP_REST_Response ) ) {
       
   210 			return $response;
       
   211 		}
       
   212 
       
   213 		$data   = (array) $response->get_data();
       
   214 		$server = rest_get_server();
       
   215 
       
   216 		if ( method_exists( $server, 'get_compact_response_links' ) ) {
       
   217 			$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
       
   218 		} else {
       
   219 			$links = call_user_func( array( $server, 'get_response_links' ), $response );
       
   220 		}
       
   221 
       
   222 		if ( ! empty( $links ) ) {
       
   223 			$data['_links'] = $links;
       
   224 		}
       
   225 
       
   226 		return $data;
       
   227 	}
       
   228 
       
   229 	/**
       
   230 	 * Filters a response based on the context defined in the schema.
       
   231 	 *
       
   232 	 * @since 4.7.0
       
   233 	 *
       
   234 	 * @param array  $data    Response data to fiter.
       
   235 	 * @param string $context Context defined in the schema.
       
   236 	 * @return array Filtered response.
       
   237 	 */
       
   238 	public function filter_response_by_context( $data, $context ) {
       
   239 
       
   240 		$schema = $this->get_item_schema();
       
   241 
       
   242 		foreach ( $data as $key => $value ) {
       
   243 			if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
       
   244 				continue;
       
   245 			}
       
   246 
       
   247 			if ( ! in_array( $context, $schema['properties'][ $key ]['context'], true ) ) {
       
   248 				unset( $data[ $key ] );
       
   249 				continue;
       
   250 			}
       
   251 
       
   252 			if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
       
   253 				foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
       
   254 					if ( empty( $details['context'] ) ) {
       
   255 						continue;
       
   256 					}
       
   257 
       
   258 					if ( ! in_array( $context, $details['context'], true ) ) {
       
   259 						if ( isset( $data[ $key ][ $attribute ] ) ) {
       
   260 							unset( $data[ $key ][ $attribute ] );
       
   261 						}
       
   262 					}
       
   263 				}
       
   264 			}
       
   265 		}
       
   266 
       
   267 		return $data;
       
   268 	}
       
   269 
       
   270 	/**
       
   271 	 * Retrieves the item's schema, conforming to JSON Schema.
       
   272 	 *
       
   273 	 * @since 4.7.0
       
   274 	 *
       
   275 	 * @return array Item schema data.
       
   276 	 */
       
   277 	public function get_item_schema() {
       
   278 		return $this->add_additional_fields_schema( array() );
       
   279 	}
       
   280 
       
   281 	/**
       
   282 	 * Retrieves the item's schema for display / public consumption purposes.
       
   283 	 *
       
   284 	 * @since 4.7.0
       
   285 	 *
       
   286 	 * @return array Public item schema data.
       
   287 	 */
       
   288 	public function get_public_item_schema() {
       
   289 
       
   290 		$schema = $this->get_item_schema();
       
   291 
       
   292 		foreach ( $schema['properties'] as &$property ) {
       
   293 			unset( $property['arg_options'] );
       
   294 		}
       
   295 
       
   296 		return $schema;
       
   297 	}
       
   298 
       
   299 	/**
       
   300 	 * Retrieves the query params for the collections.
       
   301 	 *
       
   302 	 * @since 4.7.0
       
   303 	 *
       
   304 	 * @return array Query parameters for the collection.
       
   305 	 */
       
   306 	public function get_collection_params() {
       
   307 		return array(
       
   308 			'context'                => $this->get_context_param(),
       
   309 			'page'                   => array(
       
   310 				'description'        => __( 'Current page of the collection.' ),
       
   311 				'type'               => 'integer',
       
   312 				'default'            => 1,
       
   313 				'sanitize_callback'  => 'absint',
       
   314 				'validate_callback'  => 'rest_validate_request_arg',
       
   315 				'minimum'            => 1,
       
   316 			),
       
   317 			'per_page'               => array(
       
   318 				'description'        => __( 'Maximum number of items to be returned in result set.' ),
       
   319 				'type'               => 'integer',
       
   320 				'default'            => 10,
       
   321 				'minimum'            => 1,
       
   322 				'maximum'            => 100,
       
   323 				'sanitize_callback'  => 'absint',
       
   324 				'validate_callback'  => 'rest_validate_request_arg',
       
   325 			),
       
   326 			'search'                 => array(
       
   327 				'description'        => __( 'Limit results to those matching a string.' ),
       
   328 				'type'               => 'string',
       
   329 				'sanitize_callback'  => 'sanitize_text_field',
       
   330 				'validate_callback'  => 'rest_validate_request_arg',
       
   331 			),
       
   332 		);
       
   333 	}
       
   334 
       
   335 	/**
       
   336 	 * Retrieves the magical context param.
       
   337 	 *
       
   338 	 * Ensures consistent descriptions between endpoints, and populates enum from schema.
       
   339 	 *
       
   340 	 * @since 4.7.0
       
   341 	 *
       
   342 	 * @param array $args Optional. Additional arguments for context parameter. Default empty array.
       
   343 	 * @return array Context parameter details.
       
   344 	 */
       
   345 	public function get_context_param( $args = array() ) {
       
   346 		$param_details = array(
       
   347 			'description'        => __( 'Scope under which the request is made; determines fields present in response.' ),
       
   348 			'type'               => 'string',
       
   349 			'sanitize_callback'  => 'sanitize_key',
       
   350 			'validate_callback'  => 'rest_validate_request_arg',
       
   351 		);
       
   352 
       
   353 		$schema = $this->get_item_schema();
       
   354 
       
   355 		if ( empty( $schema['properties'] ) ) {
       
   356 			return array_merge( $param_details, $args );
       
   357 		}
       
   358 
       
   359 		$contexts = array();
       
   360 
       
   361 		foreach ( $schema['properties'] as $attributes ) {
       
   362 			if ( ! empty( $attributes['context'] ) ) {
       
   363 				$contexts = array_merge( $contexts, $attributes['context'] );
       
   364 			}
       
   365 		}
       
   366 
       
   367 		if ( ! empty( $contexts ) ) {
       
   368 			$param_details['enum'] = array_unique( $contexts );
       
   369 			rsort( $param_details['enum'] );
       
   370 		}
       
   371 
       
   372 		return array_merge( $param_details, $args );
       
   373 	}
       
   374 
       
   375 	/**
       
   376 	 * Adds the values from additional fields to a data object.
       
   377 	 *
       
   378 	 * @since 4.7.0
       
   379 	 *
       
   380 	 * @param array           $object  Data object.
       
   381 	 * @param WP_REST_Request $request Full details about the request.
       
   382 	 * @return array Modified data object with additional fields.
       
   383 	 */
       
   384 	protected function add_additional_fields_to_object( $object, $request ) {
       
   385 
       
   386 		$additional_fields = $this->get_additional_fields();
       
   387 
       
   388 		foreach ( $additional_fields as $field_name => $field_options ) {
       
   389 
       
   390 			if ( ! $field_options['get_callback'] ) {
       
   391 				continue;
       
   392 			}
       
   393 
       
   394 			$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
       
   395 		}
       
   396 
       
   397 		return $object;
       
   398 	}
       
   399 
       
   400 	/**
       
   401 	 * Updates the values of additional fields added to a data object.
       
   402 	 *
       
   403 	 * @since 4.7.0
       
   404 	 *
       
   405 	 * @param array           $object  Data Object.
       
   406 	 * @param WP_REST_Request $request Full details about the request.
       
   407 	 * @return bool|WP_Error True on success, WP_Error object if a field cannot be updated.
       
   408 	 */
       
   409 	protected function update_additional_fields_for_object( $object, $request ) {
       
   410 		$additional_fields = $this->get_additional_fields();
       
   411 
       
   412 		foreach ( $additional_fields as $field_name => $field_options ) {
       
   413 			if ( ! $field_options['update_callback'] ) {
       
   414 				continue;
       
   415 			}
       
   416 
       
   417 			// Don't run the update callbacks if the data wasn't passed in the request.
       
   418 			if ( ! isset( $request[ $field_name ] ) ) {
       
   419 				continue;
       
   420 			}
       
   421 
       
   422 			$result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
       
   423 
       
   424 			if ( is_wp_error( $result ) ) {
       
   425 				return $result;
       
   426 			}
       
   427 		}
       
   428 
       
   429 		return true;
       
   430 	}
       
   431 
       
   432 	/**
       
   433 	 * Adds the schema from additional fields to a schema array.
       
   434 	 *
       
   435 	 * The type of object is inferred from the passed schema.
       
   436 	 *
       
   437 	 * @since 4.7.0
       
   438 	 *
       
   439 	 * @param array $schema Schema array.
       
   440 	 * @return array Modified Schema array.
       
   441 	 */
       
   442 	protected function add_additional_fields_schema( $schema ) {
       
   443 		if ( empty( $schema['title'] ) ) {
       
   444 			return $schema;
       
   445 		}
       
   446 
       
   447 		// Can't use $this->get_object_type otherwise we cause an inf loop.
       
   448 		$object_type = $schema['title'];
       
   449 
       
   450 		$additional_fields = $this->get_additional_fields( $object_type );
       
   451 
       
   452 		foreach ( $additional_fields as $field_name => $field_options ) {
       
   453 			if ( ! $field_options['schema'] ) {
       
   454 				continue;
       
   455 			}
       
   456 
       
   457 			$schema['properties'][ $field_name ] = $field_options['schema'];
       
   458 		}
       
   459 
       
   460 		return $schema;
       
   461 	}
       
   462 
       
   463 	/**
       
   464 	 * Retrieves all of the registered additional fields for a given object-type.
       
   465 	 *
       
   466 	 * @since 4.7.0
       
   467 	 *
       
   468 	 * @param  string $object_type Optional. The object type.
       
   469 	 * @return array Registered additional fields (if any), empty array if none or if the object type could
       
   470 	 *               not be inferred.
       
   471 	 */
       
   472 	protected function get_additional_fields( $object_type = null ) {
       
   473 
       
   474 		if ( ! $object_type ) {
       
   475 			$object_type = $this->get_object_type();
       
   476 		}
       
   477 
       
   478 		if ( ! $object_type ) {
       
   479 			return array();
       
   480 		}
       
   481 
       
   482 		global $wp_rest_additional_fields;
       
   483 
       
   484 		if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
       
   485 			return array();
       
   486 		}
       
   487 
       
   488 		return $wp_rest_additional_fields[ $object_type ];
       
   489 	}
       
   490 
       
   491 	/**
       
   492 	 * Retrieves the object type this controller is responsible for managing.
       
   493 	 *
       
   494 	 * @since 4.7.0
       
   495 	 *
       
   496 	 * @return string Object type for the controller.
       
   497 	 */
       
   498 	protected function get_object_type() {
       
   499 		$schema = $this->get_item_schema();
       
   500 
       
   501 		if ( ! $schema || ! isset( $schema['title'] ) ) {
       
   502 			return null;
       
   503 		}
       
   504 
       
   505 		return $schema['title'];
       
   506 	}
       
   507 
       
   508 	/**
       
   509 	 * Gets an array of fields to be included on the response.
       
   510 	 *
       
   511 	 * Included fields are based on item schema and `_fields=` request argument.
       
   512 	 *
       
   513 	 * @since 4.9.6
       
   514 	 *
       
   515 	 * @param WP_REST_Request $request Full details about the request.
       
   516 	 * @return array Fields to be included in the response.
       
   517 	 */
       
   518 	public function get_fields_for_response( $request ) {
       
   519 		$schema = $this->get_item_schema();
       
   520 		$fields = isset( $schema['properties'] ) ? array_keys( $schema['properties'] ) : array();
       
   521 		if ( ! isset( $request['_fields'] ) ) {
       
   522 			return $fields;
       
   523 		}
       
   524 		$requested_fields = is_array( $request['_fields'] ) ? $request['_fields'] : preg_split( '/[\s,]+/', $request['_fields'] );
       
   525 		if ( 0 === count( $requested_fields ) ) {
       
   526 			return $fields;
       
   527 		}
       
   528 		// Trim off outside whitespace from the comma delimited list.
       
   529 		$requested_fields = array_map( 'trim', $requested_fields );
       
   530 		// Always persist 'id', because it can be needed for add_additional_fields_to_object().
       
   531 		if ( in_array( 'id', $fields, true ) ) {
       
   532 			$requested_fields[] = 'id';
       
   533 		}
       
   534 		return array_intersect( $fields, $requested_fields );
       
   535 	}
       
   536 
       
   537 	/**
       
   538 	 * Retrieves an array of endpoint arguments from the item schema for the controller.
       
   539 	 *
       
   540 	 * @since 4.7.0
       
   541 	 *
       
   542 	 * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are
       
   543 	 *                       checked for required values and may fall-back to a given default, this is not done
       
   544 	 *                       on `EDITABLE` requests. Default WP_REST_Server::CREATABLE.
       
   545 	 * @return array Endpoint arguments.
       
   546 	 */
       
   547 	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
       
   548 
       
   549 		$schema            = $this->get_item_schema();
       
   550 		$schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
       
   551 		$endpoint_args     = array();
       
   552 
       
   553 		foreach ( $schema_properties as $field_id => $params ) {
       
   554 
       
   555 			// Arguments specified as `readonly` are not allowed to be set.
       
   556 			if ( ! empty( $params['readonly'] ) ) {
       
   557 				continue;
       
   558 			}
       
   559 
       
   560 			$endpoint_args[ $field_id ] = array(
       
   561 				'validate_callback' => 'rest_validate_request_arg',
       
   562 				'sanitize_callback' => 'rest_sanitize_request_arg',
       
   563 			);
       
   564 
       
   565 			if ( isset( $params['description'] ) ) {
       
   566 				$endpoint_args[ $field_id ]['description'] = $params['description'];
       
   567 			}
       
   568 
       
   569 			if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
       
   570 				$endpoint_args[ $field_id ]['default'] = $params['default'];
       
   571 			}
       
   572 
       
   573 			if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
       
   574 				$endpoint_args[ $field_id ]['required'] = true;
       
   575 			}
       
   576 
       
   577 			foreach ( array( 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties' ) as $schema_prop ) {
       
   578 				if ( isset( $params[ $schema_prop ] ) ) {
       
   579 					$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
       
   580 				}
       
   581 			}
       
   582 
       
   583 			// Merge in any options provided by the schema property.
       
   584 			if ( isset( $params['arg_options'] ) ) {
       
   585 
       
   586 				// Only use required / default from arg_options on CREATABLE endpoints.
       
   587 				if ( WP_REST_Server::CREATABLE !== $method ) {
       
   588 					$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
       
   589 				}
       
   590 
       
   591 				$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
       
   592 			}
       
   593 		}
       
   594 
       
   595 		return $endpoint_args;
       
   596 	}
       
   597 
       
   598 	/**
       
   599 	 * Sanitizes the slug value.
       
   600 	 *
       
   601 	 * @since 4.7.0
       
   602 	 *
       
   603 	 * @internal We can't use sanitize_title() directly, as the second
       
   604 	 * parameter is the fallback title, which would end up being set to the
       
   605 	 * request object.
       
   606 	 *
       
   607 	 * @see https://github.com/WP-API/WP-API/issues/1585
       
   608 	 *
       
   609 	 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
       
   610 	 *
       
   611 	 * @param string $slug Slug value passed in request.
       
   612 	 * @return string Sanitized value for the slug.
       
   613 	 */
       
   614 	public function sanitize_slug( $slug ) {
       
   615 		return sanitize_title( $slug );
       
   616 	}
       
   617 }