3 * REST API: WP_REST_Settings_Controller class
11 * Core class used to manage a site's settings via the REST API.
15 * @see WP_REST_Controller
17 class WP_REST_Settings_Controller extends WP_REST_Controller {
25 public function __construct() {
26 $this->namespace = 'wp/v2';
27 $this->rest_base = 'settings';
31 * Registers the routes for the objects of the controller.
36 * @see register_rest_route()
38 public function register_routes() {
40 register_rest_route( $this->namespace, '/' . $this->rest_base, array(
42 'methods' => WP_REST_Server::READABLE,
43 'callback' => array( $this, 'get_item' ),
45 'permission_callback' => array( $this, 'get_item_permissions_check' ),
48 'methods' => WP_REST_Server::EDITABLE,
49 'callback' => array( $this, 'update_item' ),
50 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
51 'permission_callback' => array( $this, 'get_item_permissions_check' ),
53 'schema' => array( $this, 'get_public_item_schema' ),
59 * Checks if a given request has access to read and manage settings.
64 * @param WP_REST_Request $request Full details about the request.
65 * @return bool True if the request has read access for the item, otherwise false.
67 public function get_item_permissions_check( $request ) {
68 return current_user_can( 'manage_options' );
72 * Retrieves the settings.
77 * @param WP_REST_Request $request Full details about the request.
78 * @return array|WP_Error Array on success, or WP_Error object on failure.
80 public function get_item( $request ) {
81 $options = $this->get_registered_options();
84 foreach ( $options as $name => $args ) {
86 * Filters the value of a setting recognized by the REST API.
88 * Allow hijacking the setting value and overriding the built-in behavior by returning a
89 * non-null value. The returned value will be presented as the setting value instead.
93 * @param mixed $result Value to use for the requested setting. Can be a scalar
94 * matching the registered schema for the setting, or null to
95 * follow the default get_option() behavior.
96 * @param string $name Setting name (as shown in REST API responses).
97 * @param array $args Arguments passed to register_setting() for this setting.
99 $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
101 if ( is_null( $response[ $name ] ) ) {
102 // Default to a null value as "null" in the response means "not set".
103 $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
107 * Because get_option() is lossy, we have to
108 * cast values to the type they are registered with.
110 $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
117 * Prepares a value for output based off a schema array.
122 * @param mixed $value Value to prepare.
123 * @param array $schema Schema to match.
124 * @return mixed The prepared value.
126 protected function prepare_value( $value, $schema ) {
127 // If the value is not a scalar, it's not possible to cast it to anything.
128 if ( ! is_scalar( $value ) ) {
132 switch ( $schema['type'] ) {
134 return (string) $value;
138 return (float) $value;
140 return (bool) $value;
147 * Updates settings for the settings object.
152 * @param WP_REST_Request $request Full details about the request.
153 * @return array|WP_Error Array on success, or error object on failure.
155 public function update_item( $request ) {
156 $options = $this->get_registered_options();
157 $params = $request->get_params();
159 foreach ( $options as $name => $args ) {
160 if ( ! array_key_exists( $name, $params ) ) {
165 * Filters whether to preempt a setting value update.
167 * Allows hijacking the setting update logic and overriding the built-in behavior by
172 * @param bool $result Whether to override the default behavior for updating the
173 * value of a setting.
174 * @param string $name Setting name (as shown in REST API responses).
175 * @param mixed $value Updated setting value.
176 * @param array $args Arguments passed to register_setting() for this setting.
178 $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
185 * A null value for an option would have the same effect as
186 * deleting the option from the database, and relying on the
189 if ( is_null( $request[ $name ] ) ) {
191 * A null value is returned in the response for any option
192 * that has a non-scalar value.
194 * To protect clients from accidentally including the null
195 * values from a response object in a request, we do not allow
196 * options with non-scalar values to be updated to null.
197 * Without this added protection a client could mistakenly
198 * delete all options that have non-scalar values from the
201 if ( ! is_scalar( get_option( $args['option_name'], false ) ) ) {
203 'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 )
207 delete_option( $args['option_name'] );
209 update_option( $args['option_name'], $request[ $name ] );
213 return $this->get_item( $request );
217 * Retrieves all of the registered options for the Settings API.
222 * @return array Array of registered options.
224 protected function get_registered_options() {
225 $rest_options = array();
227 foreach ( get_registered_settings() as $name => $args ) {
228 if ( empty( $args['show_in_rest'] ) ) {
232 $rest_args = array();
234 if ( is_array( $args['show_in_rest'] ) ) {
235 $rest_args = $args['show_in_rest'];
239 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
243 $rest_args = array_merge( $defaults, $rest_args );
245 $default_schema = array(
246 'type' => empty( $args['type'] ) ? null : $args['type'],
247 'description' => empty( $args['description'] ) ? '' : $args['description'],
248 'default' => isset( $args['default'] ) ? $args['default'] : null,
251 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
252 $rest_args['option_name'] = $name;
254 // Skip over settings that don't have a defined type in the schema.
255 if ( empty( $rest_args['schema']['type'] ) ) {
260 * Whitelist the supported types for settings, as we don't want invalid types
261 * to be updated with arbitrary values that we can't do decent sanitizing for.
263 if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean' ), true ) ) {
267 $rest_options[ $rest_args['name'] ] = $rest_args;
270 return $rest_options;
274 * Retrieves the site setting schema, conforming to JSON Schema.
279 * @return array Item schema data.
281 public function get_item_schema() {
282 $options = $this->get_registered_options();
285 '$schema' => 'http://json-schema.org/schema#',
286 'title' => 'settings',
288 'properties' => array(),
291 foreach ( $options as $option_name => $option ) {
292 $schema['properties'][ $option_name ] = $option['schema'];
293 $schema['properties'][ $option_name ]['arg_options'] = array(
294 'sanitize_callback' => array( $this, 'sanitize_callback' ),
298 return $this->add_additional_fields_schema( $schema );
302 * Custom sanitize callback used for all options to allow the use of 'null'.
304 * By default, the schema of settings will throw an error if a value is set to
305 * `null` as it's not a valid value for something like "type => string". We
306 * provide a wrapper sanitizer to whitelist the use of `null`.
308 * @param mixed $value The value for the setting.
309 * @param WP_REST_Request $request The request object.
310 * @param string $param The parameter name.
311 * @return mixed|WP_Error
313 public function sanitize_callback( $value, $request, $param ) {
314 if ( is_null( $value ) ) {
317 return rest_parse_request_arg( $value, $request, $param );