Completed
Pull Request — master (#11846)
by Justin
14:20
created

WC_REST_Settings_Options_Controller   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 445
Duplicated Lines 9.89 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 44
loc 445
rs 9.3999
wmc 33
lcom 1
cbo 2

15 Methods

Rating   Name   Duplication   Size   Complexity  
B register_routes() 0 35 1
A get_item() 0 11 2
B get_group_settings() 0 23 5
B get_setting() 0 25 5
A batch_items() 0 19 3
A update_item() 0 16 2
A prepare_item_for_response() 0 14 2
A prepare_links() 13 13 1
A get_items_permissions_check() 7 7 2
A update_items_permissions_check() 7 7 2
A filter_setting() 0 12 2
A get_items() 17 17 3
A allowed_setting_keys() 0 13 1
A is_setting_type_valid() 0 16 1
B get_item_schema() 0 82 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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
	 *
33
	 * @since 2.7.0
34
	 */
35
	public function register_routes() {
36
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)', array(
37
			array(
38
				'methods'             => WP_REST_Server::READABLE,
39
				'callback'            => array( $this, 'get_items' ),
40
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
41
			),
42
			'schema' => array( $this, 'get_public_item_schema' ),
43
		) );
44
45
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)/batch', array(
46
			array(
47
				'methods'             => WP_REST_Server::EDITABLE,
48
				'callback'            => array( $this, 'batch_items' ),
49
				'permission_callback' => array( $this, 'update_items_permissions_check' ),
50
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
51
			),
52
			'schema' => array( $this, 'get_public_batch_schema' ),
53
		) );
54
55
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<group>[\w-]+)/(?P<id>[\w-]+)', array(
56
			array(
57
				'methods'             => WP_REST_Server::READABLE,
58
				'callback'            => array( $this, 'get_item' ),
59
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
60
			),
61
			array(
62
				'methods'             => WP_REST_Server::EDITABLE,
63
				'callback'            => array( $this, 'update_item' ),
64
				'permission_callback' => array( $this, 'update_items_permissions_check' ),
65
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
66
			),
67
			'schema' => array( $this, 'get_public_item_schema' ),
68
		) );
69
	}
70
71
	/**
72
	 * Return a single setting.
73
	 *
74
	 * @since  2.7.0
75
	 * @param  WP_REST_Request $request
76
	 * @return WP_Error|WP_REST_Response
77
	 */
78
	public function get_item( $request ) {
79
		$setting = $this->get_setting( $request['group'], $request['id'] );
80
81
		if ( is_wp_error( $setting ) ) {
82
			return $setting;
83
		}
84
85
		$response = $this->prepare_item_for_response( $setting, $request );
86
87
		return rest_ensure_response( $response );
88
	}
89
90
	/**
91
	 * Return all settings in a group.
92
	 *
93
	 * @since  2.7.0
94
	 * @param  WP_REST_Request $request
95
	 * @return WP_Error|WP_REST_Response
96
	 */
97 View Code Duplication
	public function get_items( $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...
98
		$settings = $this->get_group_settings( $request['group'] );
99
100
		if ( is_wp_error( $settings ) ) {
101
			return $settings;
102
		}
103
104
		$data = array();
105
106
		foreach ( $settings as $setting_obj ) {
107
			$setting = $this->prepare_item_for_response( $setting_obj, $request );
108
			$setting = $this->prepare_response_for_collection( $setting );
109
			$data[]  = $setting;
110
		}
111
112
		return rest_ensure_response( $data );
113
	}
114
115
	/**
116
	 * Get all settings in a group.
117
	 *
118
	 * @since  2.7.0
119
	 * @param string $group_id Group ID.
120
	 * @return array|WP_Error
121
	 */
122
	public function get_group_settings( $group_id ) {
123
		if ( empty( $group_id ) ) {
124
			return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
125
		}
126
127
		$settings = apply_filters( 'woocommerce_settings-' . $group_id, array() );
128
129
		if ( empty( $settings ) ) {
130
			return new WP_Error( 'rest_setting_setting_group_invalid', __( 'Invalid setting group.', 'woocommerce' ), array( 'status' => 404 ) );
131
		}
132
133
		$filtered_settings = array();
134
135
		foreach ( $settings as $setting ) {
136
			$setting = $this->filter_setting( $setting );
137
			if ( $this->is_setting_type_valid( $setting['type'] ) ) {
138
				$setting['value']    = WC_Admin_Settings::get_option( $setting['id'] );
139
				$filtered_settings[] = $setting;
140
			}
141
		}
142
143
		return $filtered_settings;
144
	}
145
146
	/**
147
	 * Get setting data.
148
	 *
149
	 * @since  2.7.0
150
	 * @param string $group_id Group ID.
151
	 * @param string $setting_id Setting ID.
152
	 * @return stdClass|WP_Error
153
	 */
154
	public function get_setting( $group_id, $setting_id ) {
155
		if ( empty( $setting_id ) ) {
156
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
157
		}
158
159
		$settings = $this->get_group_settings( $group_id );
160
161
		if ( is_wp_error( $settings ) ) {
162
			return $settings;
163
		}
164
165
		$array_key = array_keys( wp_list_pluck( $settings, 'id' ), $setting_id );
166
167
		if ( empty( $array_key ) ) {
168
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
169
		}
170
171
		$setting = $settings[ $array_key[0] ];
172
173
		if ( ! $this->is_setting_type_valid( $setting['type'] ) ) {
174
			return new WP_Error( 'rest_setting_setting_invalid', __( 'Invalid setting.', 'woocommerce' ), array( 'status' => 404 ) );
175
		}
176
177
		return $setting;
178
	}
179
180
	/**
181
	 * Bulk create, update and delete items.
182
	 *
183
	 * @since  2.7.0
184
	 * @param WP_REST_Request $request Full details about the request.
185
	 * @return array Of WP_Error or WP_REST_Response.
186
	 */
187
	public function batch_items( $request ) {
188
		// Get the request params.
189
		$items = array_filter( $request->get_params() );
190
191
		/*
192
		 * Since our batch settings update is group-specific and matches based on the route,
193
		 * we inject the URL parameters (containing group) into the batch items
194
		 */
195
		if ( ! empty( $items['update'] ) ) {
196
			$to_update = array();
197
			foreach ( $items['update'] as $item ) {
198
				$to_update[] = array_merge( $request->get_url_params(), $item );
199
			}
200
			$request = new WP_REST_Request( $request->get_method() );
201
			$request->set_body_params( array( 'update' => $to_update ) );
202
		}
203
204
		return parent::batch_items( $request );
205
	}
206
207
	/**
208
	 * Update a single setting in a group.
209
210
	 * @since  2.7.0
211
	 * @param  WP_REST_Request $request
212
	 * @return WP_Error|WP_REST_Response
213
	 */
214
	public function update_item( $request ) {
215
		$setting = $this->get_setting( $request['group'], $request['id'] );
216
217
		if ( is_wp_error( $setting ) ) {
218
			return $setting;
219
		}
220
221
		$update_data = array();
222
		$update_data[ $setting['id'] ] = $request['value'];
223
224
		WC_Admin_Settings::save_fields( array( $setting ), $update_data );
225
226
		$response = $this->prepare_item_for_response( $setting, $request );
227
228
		return rest_ensure_response( $response );
229
	}
230
231
	/**
232
	 * Prepare a single setting object for response.
233
	 *
234
	 * @since  2.7.0
235
	 * @param object $item Setting object.
236
	 * @param WP_REST_Request $request Request object.
237
	 * @return WP_REST_Response $response Response data.
238
	 */
239
	public function prepare_item_for_response( $item, $request ) {
240
		$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...
241
		$data['value'] = WC_Admin_Settings::get_option( $data['id'] );
242
243
		$context = empty( $request['context'] ) ? 'view' : $request['context'];
244
		$data    = $this->add_additional_fields_to_object( $data, $request );
245
		$data    = $this->filter_response_by_context( $data, $context );
246
247
		$response = rest_ensure_response( $data );
248
249
		$response->add_links( $this->prepare_links( $data['id'], $request['group'] ) );
250
251
		return $response;
252
	}
253
254
	/**
255
	 * Prepare links for the request.
256
	 *
257
	 * @since  2.7.0
258
	 * @param string $setting_id Setting ID.
259
	 * @param string $group_id Group ID.
260
	 * @return array Links for the given setting.
261
	 */
262 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...
263
		$base  = '/' . $this->namespace . '/' . $this->rest_base . '/' . $group_id;
264
		$links = array(
265
			'self' => array(
266
				'href' => rest_url( trailingslashit( $base ) . $setting_id ),
267
			),
268
			'collection' => array(
269
				'href' => rest_url( $base ),
270
			),
271
		);
272
273
		return $links;
274
	}
275
276
	/**
277
	 * Makes sure the current user has access to READ the settings APIs.
278
	 *
279
	 * @since  2.7.0
280
	 * @param WP_REST_Request $request Full data about the request.
281
	 * @return WP_Error|boolean
282
	 */
283 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...
284
		if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
285
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
286
		}
287
288
		return true;
289
	}
290
291
	/**
292
	 * Makes sure the current user has access to WRITE the settings APIs.
293
	 *
294
	 * @since  2.7.0
295
	 * @param WP_REST_Request $request Full data about the request.
296
	 * @return WP_Error|boolean
297
	 */
298 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...
299
		if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
300
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
301
		}
302
303
		return true;
304
	}
305
306
	/**
307
	 * Filters out bad values from the settings array/filter so we
308
	 * only return known values via the API.
309
	 *
310
	 * @since 2.7.0
311
	 * @param  array $setting
312
	 * @return array
313
	 */
314
	public function filter_setting( $setting ) {
315
		$setting = array_intersect_key(
316
			$setting,
317
			array_flip( array_filter( array_keys( $setting ), array( $this, 'allowed_setting_keys' ) ) )
318
		);
319
320
		if ( empty( $setting['options'] ) ) {
321
			unset( $setting['options'] );
322
		}
323
324
		return $setting;
325
	}
326
327
	/**
328
	 * Callback for allowed keys for each setting response.
329
	 *
330
	 * @since  2.7.0
331
	 * @param  string $key Key to check
332
	 * @return boolean
333
	 */
334
	public function allowed_setting_keys( $key ) {
335
		return in_array( $key, array(
336
			'id',
337
			'label',
338
			'description',
339
			'default',
340
			'tip',
341
			'placeholder',
342
			'type',
343
			'options',
344
			'value',
345
		) );
346
	}
347
348
	/**
349
	 * Boolean for if a setting type is a valid supported setting type.
350
	 *
351
	 * @since  2.7.0
352
	 * @param  string  $type
353
	 * @return boolean
354
	 */
355
	public function is_setting_type_valid( $type ) {
356
		return in_array( $type, array(
357
			'text',
358
			'email',
359
			'number',
360
			'color',
361
			'password',
362
			'textarea',
363
			'select',
364
			'multiselect',
365
			'radio',
366
			'checkbox',
367
			'multi_select_countries',
368
			'image_width',
369
		) );
370
	}
371
372
	/**
373
	 * Get the settings schema, conforming to JSON Schema.
374
	 *
375
	 * @since 2.7.0
376
	 * @return array
377
	 */
378
	public function get_item_schema() {
379
		$schema = array(
380
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
381
			'title'                => 'settings',
382
			'type'                 => 'object',
383
			'properties'           => array(
384
				'id'               => array(
385
					'description'  => __( 'A unique identifier for the setting.', 'woocommerce' ),
386
					'type'         => 'string',
387
					'arg_options'  => array(
388
						'sanitize_callback' => 'sanitize_title',
389
					),
390
					'context'      => array( 'view', 'edit' ),
391
					'readonly'     => true,
392
				),
393
				'label'            => array(
394
					'description'  => __( 'A human readable translation wrapped label. Meant to be used in interfaces.', 'woocommerce' ),
395
					'type'         => 'string',
396
					'arg_options'  => array(
397
						'sanitize_callback' => 'sanitize_text_field',
398
					),
399
					'context'      => array( 'view', 'edit' ),
400
					'readonly'     => true,
401
				),
402
				'description'      => array(
403
					'description'  => __( 'A human readable translation wrapped description. Meant to be used in interfaces.', 'woocommerce' ),
404
					'type'         => 'string',
405
					'arg_options'  => array(
406
						'sanitize_callback' => 'sanitize_text_field',
407
					),
408
					'context'      => array( 'view', 'edit' ),
409
					'readonly'     => true,
410
				),
411
				'value'          => array(
412
					'description'  => __( 'Setting value.', 'woocommerce' ),
413
					'type'         => 'mixed',
414
					'context'      => array( 'view', 'edit' ),
415
				),
416
				'default'          => array(
417
					'description'  => __( 'Default value for the setting.', 'woocommerce' ),
418
					'type'         => 'mixed',
419
					'context'      => array( 'view', 'edit' ),
420
					'readonly'     => true,
421
				),
422
				'tip'              => array(
423
					'description'  => __( 'Extra help text explaining the setting.', 'woocommerce' ),
424
					'type'         => 'string',
425
					'arg_options'  => array(
426
						'sanitize_callback' => 'sanitize_text_field',
427
					),
428
					'context'      => array( 'view', 'edit' ),
429
					'readonly'     => true,
430
				),
431
				'placeholder'      => array(
432
					'description'  => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce' ),
433
					'type'         => 'string',
434
					'arg_options'  => array(
435
						'sanitize_callback' => 'sanitize_text_field',
436
					),
437
					'context'      => array( 'view', 'edit' ),
438
					'readonly'     => true,
439
				),
440
				'type'             => array(
441
					'description'  => __( 'Type of setting. Allowed values: text, email, number, color, password, textarea, select, multiselect, radio, image_width, checkbox.', 'woocommerce' ),
442
					'type'         => 'string',
443
					'arg_options'  => array(
444
						'sanitize_callback' => 'sanitize_text_field',
445
					),
446
					'context'      => array( 'view', 'edit' ),
447
					'readonly'     => true,
448
				),
449
				'options'          => array(
450
					'description'  => __( 'Array of options (key value pairs) for inputs such as select, multiselect, and radio buttons.', 'woocommerce' ),
451
					'type'         => 'array',
452
					'context'      => array( 'view', 'edit' ),
453
					'readonly'     => true,
454
				),
455
			),
456
		);
457
458
		return $this->add_additional_fields_schema( $schema );
459
	}
460
}
461