WordPress 4.7
[autoinstalls/wordpress.git] / wp-includes / rest-api / endpoints / class-wp-rest-settings-controller.php
1 <?php
2 /**
3  * REST API: WP_REST_Settings_Controller class
4  *
5  * @package WordPress
6  * @subpackage REST_API
7  * @since 4.7.0
8  */
9
10 /**
11  * Core class used to manage a site's settings via the REST API.
12  *
13  * @since 4.7.0
14  *
15  * @see WP_REST_Controller
16  */
17 class WP_REST_Settings_Controller extends WP_REST_Controller {
18
19         /**
20          * Constructor.
21          *
22          * @since 4.7.0
23          * @access public
24          */
25         public function __construct() {
26                 $this->namespace = 'wp/v2';
27                 $this->rest_base = 'settings';
28         }
29
30         /**
31          * Registers the routes for the objects of the controller.
32          *
33          * @since 4.7.0
34          * @access public
35          *
36          * @see register_rest_route()
37          */
38         public function register_routes() {
39
40                 register_rest_route( $this->namespace, '/' . $this->rest_base, array(
41                         array(
42                                 'methods'             => WP_REST_Server::READABLE,
43                                 'callback'            => array( $this, 'get_item' ),
44                                 'args'                => array(),
45                                 'permission_callback' => array( $this, 'get_item_permissions_check' ),
46                         ),
47                         array(
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' ),
52                         ),
53                         'schema' => array( $this, 'get_public_item_schema' ),
54                 ) );
55
56         }
57
58         /**
59          * Checks if a given request has access to read and manage settings.
60          *
61          * @since 4.7.0
62          * @access public
63          *
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.
66          */
67         public function get_item_permissions_check( $request ) {
68                 return current_user_can( 'manage_options' );
69         }
70
71         /**
72          * Retrieves the settings.
73          *
74          * @since 4.7.0
75          * @access public
76          *
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.
79          */
80         public function get_item( $request ) {
81                 $options  = $this->get_registered_options();
82                 $response = array();
83
84                 foreach ( $options as $name => $args ) {
85                         /**
86                          * Filters the value of a setting recognized by the REST API.
87                          *
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.
90                          *
91                          * @since 4.7.0
92                          *
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.
98                          */
99                         $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
100
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'] );
104                         }
105
106                         /*
107                          * Because get_option() is lossy, we have to
108                          * cast values to the type they are registered with.
109                          */
110                         $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
111                 }
112
113                 return $response;
114         }
115
116         /**
117          * Prepares a value for output based off a schema array.
118          *
119          * @since 4.7.0
120          * @access protected
121          *
122          * @param mixed $value  Value to prepare.
123          * @param array $schema Schema to match.
124          * @return mixed The prepared value.
125          */
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 ) ) {
129                         return null;
130                 }
131
132                 switch ( $schema['type'] ) {
133                         case 'string':
134                                 return (string) $value;
135                         case 'integer':
136                                 return (int) $value;
137                         case 'number':
138                                 return (float) $value;
139                         case 'boolean':
140                                 return (bool) $value;
141                         default:
142                                 return null;
143                 }
144         }
145
146         /**
147          * Updates settings for the settings object.
148          *
149          * @since 4.7.0
150          * @access public
151          *
152          * @param WP_REST_Request $request Full details about the request.
153          * @return array|WP_Error Array on success, or error object on failure.
154          */
155         public function update_item( $request ) {
156                 $options = $this->get_registered_options();
157                 $params  = $request->get_params();
158
159                 foreach ( $options as $name => $args ) {
160                         if ( ! array_key_exists( $name, $params ) ) {
161                                 continue;
162                         }
163
164                         /**
165                          * Filters whether to preempt a setting value update.
166                          *
167                          * Allows hijacking the setting update logic and overriding the built-in behavior by
168                          * returning true.
169                          *
170                          * @since 4.7.0
171                          *
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.
177                          */
178                         $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
179
180                         if ( $updated ) {
181                                 continue;
182                         }
183
184                         /*
185                          * A null value for an option would have the same effect as
186                          * deleting the option from the database, and relying on the
187                          * default value.
188                          */
189                         if ( is_null( $request[ $name ] ) ) {
190                                 /*
191                                  * A null value is returned in the response for any option
192                                  * that has a non-scalar value.
193                                  *
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
199                                  * database.
200                                  */
201                                 if ( ! is_scalar( get_option( $args['option_name'], false ) ) ) {
202                                         return new WP_Error(
203                                                 'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 )
204                                         );
205                                 }
206
207                                 delete_option( $args['option_name'] );
208                         } else {
209                                 update_option( $args['option_name'], $request[ $name ] );
210                         }
211                 }
212
213                 return $this->get_item( $request );
214         }
215
216         /**
217          * Retrieves all of the registered options for the Settings API.
218          *
219          * @since 4.7.0
220          * @access protected
221          *
222          * @return array Array of registered options.
223          */
224         protected function get_registered_options() {
225                 $rest_options = array();
226
227                 foreach ( get_registered_settings() as $name => $args ) {
228                         if ( empty( $args['show_in_rest'] ) ) {
229                                 continue;
230                         }
231
232                         $rest_args = array();
233
234                         if ( is_array( $args['show_in_rest'] ) ) {
235                                 $rest_args = $args['show_in_rest'];
236                         }
237
238                         $defaults = array(
239                                 'name'   => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
240                                 'schema' => array(),
241                         );
242
243                         $rest_args = array_merge( $defaults, $rest_args );
244
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,
249                         );
250
251                         $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
252                         $rest_args['option_name'] = $name;
253
254                         // Skip over settings that don't have a defined type in the schema.
255                         if ( empty( $rest_args['schema']['type'] ) ) {
256                                 continue;
257                         }
258
259                         /*
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.
262                          */
263                         if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean' ), true ) ) {
264                                 continue;
265                         }
266
267                         $rest_options[ $rest_args['name'] ] = $rest_args;
268                 }
269
270                 return $rest_options;
271         }
272
273         /**
274          * Retrieves the site setting schema, conforming to JSON Schema.
275          *
276          * @since 4.7.0
277          * @access public
278          *
279          * @return array Item schema data.
280          */
281         public function get_item_schema() {
282                 $options = $this->get_registered_options();
283
284                 $schema = array(
285                         '$schema'    => 'http://json-schema.org/schema#',
286                         'title'      => 'settings',
287                         'type'       => 'object',
288                         'properties' => array(),
289                 );
290
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' ),
295                         );
296                 }
297
298                 return $this->add_additional_fields_schema( $schema );
299         }
300
301         /**
302          * Custom sanitize callback used for all options to allow the use of 'null'.
303          *
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`.
307          *
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
312          */
313         public function sanitize_callback( $value, $request, $param ) {
314                 if ( is_null( $value ) ) {
315                         return $value;
316                 }
317                 return rest_parse_request_arg( $value, $request, $param );
318         }
319 }