Completed
Pull Request — master (#11082)
by Jeff
07:58
created

WC_Rest_Settings_Options_Controller::get_items()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 17
rs 9.4285
c 1
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
/**
7
 * REST API Settings Controller.
8
 * Handles requests to the /settings/$group/$setting endpoints.
9
 *
10
 * @author   WooThemes
11
 * @category API
12
 * @package  WooCommerce/API
13
 * @version  2.7.0
14
 * @since    2.7.0
15
 */
16
class WC_Rest_Settings_Options_Controller extends WC_REST_Controller {
17
18
	/**
19
	 * WP REST API namespace/version.
20
	 */
21
	protected $namespace = 'wc/v1';
22
23
	/**
24
	 * Route base.
25
	 *
26
	 * @var string
27
	 */
28
	protected $rest_base = 'settings';
29
30
	/**
31
	 * Register routes.
32
	 * @since 2.7.0
33
	 */
34
	public function register_routes() {
35
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)', array(
36
			array(
37
				'methods'             => WP_REST_Server::READABLE,
38
				'callback'            => array( $this, 'get_items' ),
39
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
40
			),
41
			'schema' => array( $this, 'get_public_item_schema' ),
42
		) );
43
44
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)/batch', array(
45
			array(
46
				'methods'             => WP_REST_Server::EDITABLE,
47
				'callback'            => array( $this, 'batch_items' ),
48
				'permission_callback' => array( $this, 'update_items_permissions_check' ),
49
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
50
			),
51
			'schema' => array( $this, 'get_public_batch_schema' ),
52
		) );
53
54
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)/(?P<id>[\w-]+)', array(
55
			array(
56
				'methods'             => WP_REST_Server::READABLE,
57
				'callback'            => array( $this, 'get_item' ),
58
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
59
			),
60
			array(
61
				'methods'             => WP_REST_Server::EDITABLE,
62
				'callback'            => array( $this, 'update_item' ),
63
				'permission_callback' => array( $this, 'update_items_permissions_check' ),
64
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
65
			),
66
			'schema' => array( $this, 'get_public_item_schema' ),
67
		) );
68
	}
69
70
	/**
71
	 * Return a single setting.
72
	 * @since  2.7.0
73
	 * @param  WP_REST_Request $request
74
	 * @return WP_Error|WP_REST_Response
75
	 */
76
	public function get_item( $request ) {
77
		$setting = $this->get_setting( $request['group'], $request['id'] );
78
79
		if ( is_wp_error( $setting ) ) {
80
			return $setting;
81
		}
82
83
		$response = $this->prepare_item_for_response( $setting, $request );
84
85
		return rest_ensure_response( $response );
86
	}
87
88
	/**
89
	 * Return all settings in a group.
90
	 * @since  2.7.0
91
	 * @param  WP_REST_Request $request
92
	 * @return WP_Error|WP_REST_Response
93
	 */
94
	public function get_items( $request ) {
95
		$settings = $this->get_group_settings( $request['group'] );
96
97
		if ( is_wp_error( $settings ) ) {
98
			return $settings;
99
		}
100
101
		$data = array();
102
103
		foreach ( $settings as $setting_obj ) {
104
			$setting = $this->prepare_item_for_response( $setting_obj, $request );
105
			$setting = $this->prepare_response_for_collection( $setting );
106
			$data[]  = $setting;
107
		}
108
109
		return rest_ensure_response( $data );
110
	}
111
112
	/**
113
	 * Get all settings in a group.
114
	 *
115
	 * @param string $group_id Group ID.
116
	 *
117
	 * @return array|WP_Error
118
	 */
119
	public function get_group_settings( $group_id ) {
120
		if ( empty( $group_id ) ) {
121
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
122
		}
123
124
		$settings = apply_filters( 'woocommerce_settings-' . $group_id, array() );
125
126
		if ( empty( $settings ) ) {
127
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
128
		}
129
130
		$filtered_settings = array();
131
132
		foreach ( $settings as $setting ) {
133
			$setting = $this->filter_setting( $setting );
134
			if ( $this->is_setting_type_valid( $setting['type'] ) ) {
135
				$setting['value']    = WC_Admin_Settings::get_option( $setting['id'] );
136
				$filtered_settings[] = $setting;
137
			}
138
		}
139
140
		return $filtered_settings;
141
	}
142
143
	/**
144
	 * Get setting data.
145
	 *
146
	 * @param string $group_id Group ID.
147
	 * @param string $setting_id Setting ID.
148
	 *
149
	 * @return stdClass|WP_Error
150
	 */
151
	public function get_setting( $group_id, $setting_id ) {
152
		if ( empty( $setting_id ) ) {
153
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
154
		}
155
156
		$settings = $this->get_group_settings( $group_id );
157
158
		if ( is_wp_error( $settings ) ) {
159
			return $settings;
160
		}
161
162
		$array_key = array_keys( wp_list_pluck( $settings, 'id' ), $setting_id );
163
164
		if ( empty( $array_key ) ) {
165
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
166
		}
167
168
		$setting = $settings[ $array_key[0] ];
169
170
		if ( ! $this->is_setting_type_valid( $setting['type'] ) ) {
171
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
172
		}
173
174
		return $setting;
175
	}
176
177
	/**
178
	 * Bulk create, update and delete items.
179
	 *
180
	 * @param WP_REST_Request $request Full details about the request.
181
	 * @return array Of WP_Error or WP_REST_Response.
182
	 */
183
	public function batch_items( $request ) {
184
		// Get the request params.
185
		$items = array_filter( $request->get_params() );
186
187
		/*
188
		 * Since our batch settings update is group-specific and matches based on the route,
189
		 * we inject the URL parameters (containing group) into the batch items
190
		 */
191
		if ( ! empty( $items['update'] ) ) {
192
			$to_update = array();
193
			foreach ( $items['update'] as $item ) {
194
				$to_update[] = array_merge( $request->get_url_params(), $item );
195
			}
196
			$request = new WP_REST_Request( $request->get_method() );
197
			$request->set_body_params( array( 'update' => $to_update ) );
198
		}
199
200
		return parent::batch_items( $request );
201
	}
202
203
	/**
204
	 * Update a single setting in a group.
205
	 * @since  2.7.0
206
	 * @param  WP_REST_Request $request
207
	 * @return WP_Error|WP_REST_Response
208
	 */
209
	public function update_item( $request ) {
210
		$setting = $this->get_setting( $request['group'], $request['id'] );
211
212
		if ( is_wp_error( $setting ) ) {
213
			return $setting;
214
		}
215
216
		$update_data = array();
217
		$update_data[ $setting['id'] ] = $request['value'];
218
219
		WC_Admin_Settings::save_fields( array( $setting ), $update_data );
220
221
		$response = $this->prepare_item_for_response( $setting, $request );
222
223
		return rest_ensure_response( $response );
224
	}
225
226
	/**
227
	 * Prepare a single setting object for response.
228
	 *
229
	 * @param object $item Setting object.
230
	 * @param WP_REST_Request $request Request object.
231
	 * @return WP_REST_Response $response Response data.
232
	 */
233
	public function prepare_item_for_response( $item, $request ) {
234
		$data          = $this->filter_setting( $item );
0 ignored issues
show
Documentation introduced by
$item is of type object, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235
		$data['value'] = WC_Admin_Settings::get_option( $data['id'] );
236
237
		$context = empty( $request['context'] ) ? 'view' : $request['context'];
238
		$data    = $this->add_additional_fields_to_object( $data, $request );
239
		$data    = $this->filter_response_by_context( $data, $context );
240
241
		$response = rest_ensure_response( $data );
242
243
		$response->add_links( $this->prepare_links( $data['id'], $request['group'] ) );
244
245
		return $response;
246
	}
247
248
	/**
249
	 * Prepare links for the request.
250
	 *
251
	 * @param string $setting_id Setting ID.
252
	 * @param string $group_id Group ID.
253
	 * @return array Links for the given setting.
254
	 */
255 View Code Duplication
	protected function prepare_links( $setting_id, $group_id ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
		$base  = '/' . $this->namespace . '/' . $this->rest_base . '/' . $group_id;
257
		$links = array(
258
			'self' => array(
259
				'href' => rest_url( trailingslashit( $base ) . $setting_id ),
260
			),
261
			'collection' => array(
262
				'href' => rest_url( $base ),
263
			),
264
		);
265
266
		return $links;
267
	}
268
269
	/**
270
	 * Makes sure the current user has access to READ the settings APIs.
271
	 *
272
	 * @since  2.7.0
273
	 * @param WP_REST_Request $request Full data about the request.
274
	 * @return WP_Error|boolean
275
	 */
276 View Code Duplication
	public function get_items_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
277
		if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
278
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
279
		}
280
281
		return true;
282
	}
283
284
	/**
285
	 * Makes sure the current user has access to WRITE the settings APIs.
286
	 *
287
	 * @since  2.7.0
288
	 * @param WP_REST_Request $request Full data about the request.
289
	 * @return WP_Error|boolean
290
	 */
291 View Code Duplication
	public function update_items_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
292
		if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
293
			return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
294
		}
295
296
		return true;
297
	}
298
299
	/**
300
	 * Filters out bad values from the settings array/filter so we
301
	 * only return known values via the API.
302
	 *
303
	 * @since 2.7.0
304
	 * @param  array $setting
305
	 * @return array
306
	 */
307
	public function filter_setting( $setting ) {
308
		$setting = array_intersect_key(
309
			$setting,
310
			array_flip( array_filter( array_keys( $setting ), array( $this, 'allowed_setting_keys' ) ) )
311
		);
312
313
		if ( empty( $setting['options'] ) ) {
314
			unset( $setting['options'] );
315
		}
316
317
		return $setting;
318
	}
319
320
	/**
321
	 * Callback for allowed keys for each setting response.
322
	 *
323
	 * @since  2.7.0
324
	 * @param  string $key Key to check
325
	 * @return boolean
326
	 */
327
	public function allowed_setting_keys( $key ) {
328
		return in_array( $key, array(
329
			'id', 'label', 'description', 'default', 'tip',
330
			'placeholder', 'type', 'options', 'value',
331
		) );
332
	}
333
334
	/**
335
	 * Boolean for if a setting type is a valid supported setting type.
336
	 *
337
	 * @since  2.7.0
338
	 * @param  string  $type
339
	 * @return boolean
340
	 */
341
	public function is_setting_type_valid( $type ) {
342
		return in_array( $type, array(
343
			'text', 'email', 'number', 'color', 'password',
344
			'textarea', 'select', 'multiselect', 'radio', 'checkbox',
345
			'multi_select_countries', 'image_width',
346
		) );
347
	}
348
349
	/**
350
	 * Get the settings schema, conforming to JSON Schema.
351
	 *
352
	 * @since 2.7.0
353
	 * @return array
354
	 */
355
	public function get_item_schema() {
356
		$schema = array(
357
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
358
			'title'                => 'settings',
359
			'type'                 => 'object',
360
			'properties'           => array(
361
				'id'               => array(
362
					'description'  => __( 'A unique identifier for the setting.', 'woocommerce' ),
363
					'type'         => 'string',
364
					'arg_options'  => array(
365
						'sanitize_callback' => 'sanitize_title',
366
					),
367
				),
368
				'label'            => array(
369
					'description'  => __( 'A human readable label. This is a translated string that can be used in interfaces.', 'woocommerce' ),
370
					'type'         => 'string',
371
					'arg_options'  => array(
372
						'sanitize_callback' => 'sanitize_text_field',
373
					),
374
				),
375
				'description'      => array(
376
					'description'  => __( 'A human readable description. This is a translated string that can be used in interfaces.', 'woocommerce' ),
377
					'type'         => 'string',
378
					'arg_options'  => array(
379
						'sanitize_callback' => 'sanitize_text_field',
380
					),
381
				),
382
				'default'          => array(
383
					'description'  => __( 'Default value for the setting.', 'woocommerce' ),
384
					'type'         => 'mixed',
385
				),
386
				'tip'              => array(
387
					'description'  => __( 'Extra help text explaining the setting.', 'woocommerce' ),
388
					'type'         => 'string',
389
					'arg_options'  => array(
390
						'sanitize_callback' => 'sanitize_text_field',
391
					),
392
				),
393
				'placeholder'      => array(
394
					'description'  => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
395
					'type'         => 'string',
396
					'arg_options'  => array(
397
						'sanitize_callback' => 'sanitize_text_field',
398
					),
399
				),
400
				'type'             => array(
401
					'description'  => __( 'Type of setting. Allowed values: text, email, number, color, password, textarea, select, multiselect, radio, image_width, checkbox.', 'woocommerce' ),
402
					'type'         => 'string',
403
					'arg_options'  => array(
404
						'sanitize_callback' => 'sanitize_text_field',
405
					),
406
				),
407
				'options'          => array(
408
					'description'  => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'woocommerce' ),
409
					'type'         => 'array',
410
				),
411
			),
412
		);
413
414
		return $this->add_additional_fields_schema( $schema );
415
	}
416
}
417