ShippingZoneMethods   B
last analyzed

Complexity

Total Complexity 50

Size/Duplication

Total Lines 537
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 297
dl 0
loc 537
rs 8.4
c 0
b 0
f 0
wmc 50

12 Methods

Rating   Name   Duplication   Size   Complexity  
A get_items() 0 16 3
B get_settings() 0 20 7
B delete_item() 0 56 7
B get_item_schema() 0 111 1
A prepare_item_for_response() 0 4 1
A create_item() 0 28 6
A update_item() 0 28 6
B update_fields() 0 45 11
A prepare_links() 0 15 1
B register_routes() 0 76 1
A get_data_for_response() 0 11 1
A get_item() 0 25 5

How to fix   Complexity   

Complex Class

Complex classes like ShippingZoneMethods often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShippingZoneMethods, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * REST API Shipping Zone Methods controller
4
 *
5
 * Handles requests to the /shipping/zones/<id>/methods endpoint.
6
 *
7
 * @package Automattic/WooCommerce/RestApi
8
 */
9
10
namespace Automattic\WooCommerce\RestApi\Controllers\Version4;
11
12
defined( 'ABSPATH' ) || exit;
13
14
use Automattic\WooCommerce\RestApi\Controllers\Version4\Utilities\SettingsTrait;
15
16
/**
17
 * REST API Shipping Zone Methods class.
18
 */
19
class ShippingZoneMethods extends AbstractShippingZonesController {
20
	use SettingsTrait;
21
22
	/**
23
	 * Register the routes for Shipping Zone Methods.
24
	 */
25
	public function register_routes() {
26
		register_rest_route(
27
			$this->namespace,
28
			'/' . $this->rest_base . '/(?P<zone_id>[\d]+)/methods',
29
			array(
30
				'args'   => array(
31
					'zone_id' => array(
32
						'description' => __( 'Unique ID for the zone.', 'woocommerce-rest-api' ),
33
						'type'        => 'integer',
34
					),
35
				),
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
				array(
42
					'methods'             => \WP_REST_Server::CREATABLE,
43
					'callback'            => array( $this, 'create_item' ),
44
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
45
					'args'                => array_merge(
46
						$this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ),
47
						array(
48
							'method_id' => array(
49
								'required'    => true,
50
								'readonly'    => false,
51
								'description' => __( 'Shipping method ID.', 'woocommerce-rest-api' ),
52
							),
53
						)
54
					),
55
				),
56
				'schema' => array( $this, 'get_public_item_schema' ),
57
			),
58
			true
59
		);
60
61
		register_rest_route(
62
			$this->namespace,
63
			'/' . $this->rest_base . '/(?P<zone_id>[\d]+)/methods/(?P<instance_id>[\d]+)',
64
			array(
65
				'args'   => array(
66
					'zone_id'     => array(
67
						'description' => __( 'Unique ID for the zone.', 'woocommerce-rest-api' ),
68
						'type'        => 'integer',
69
					),
70
					'instance_id' => array(
71
						'description' => __( 'Unique ID for the instance.', 'woocommerce-rest-api' ),
72
						'type'        => 'integer',
73
					),
74
				),
75
				array(
76
					'methods'             => \WP_REST_Server::READABLE,
77
					'callback'            => array( $this, 'get_item' ),
78
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
79
				),
80
				array(
81
					'methods'             => \WP_REST_Server::EDITABLE,
82
					'callback'            => array( $this, 'update_item' ),
83
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
84
					'args'                => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ),
85
				),
86
				array(
87
					'methods'             => \WP_REST_Server::DELETABLE,
88
					'callback'            => array( $this, 'delete_item' ),
89
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
90
					'args'                => array(
91
						'force' => array(
92
							'default'     => false,
93
							'type'        => 'boolean',
94
							'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce-rest-api' ),
95
						),
96
					),
97
				),
98
				'schema' => array( $this, 'get_public_item_schema' ),
99
			),
100
			true
101
		);
102
	}
103
104
	/**
105
	 * Get a single Shipping Zone Method.
106
	 *
107
	 * @param \WP_REST_Request $request Request data.
108
	 * @return \WP_REST_Response|\WP_Error
109
	 */
110
	public function get_item( $request ) {
111
		$zone = $this->get_zone( $request['zone_id'] );
112
113
		if ( is_wp_error( $zone ) ) {
114
			return $zone;
115
		}
116
117
		$instance_id = (int) $request['instance_id'];
118
		$methods     = $zone->get_shipping_methods();
0 ignored issues
show
Bug introduced by
The method get_shipping_methods() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

118
		/** @scrutinizer ignore-call */ 
119
  $methods     = $zone->get_shipping_methods();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
119
		$method      = false;
120
121
		foreach ( $methods as $method_obj ) {
122
			if ( $instance_id === $method_obj->instance_id ) {
123
				$method = $method_obj;
124
				break;
125
			}
126
		}
127
128
		if ( false === $method ) {
129
			return new \WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
130
		}
131
132
		$data = $this->prepare_item_for_response( $method, $request );
133
134
		return rest_ensure_response( $data );
135
	}
136
137
	/**
138
	 * Get all Shipping Zone Methods.
139
	 *
140
	 * @param \WP_REST_Request $request Request data.
141
	 * @return \WP_REST_Response|\WP_Error
142
	 */
143
	public function get_items( $request ) {
144
		$zone = $this->get_zone( $request['zone_id'] );
145
146
		if ( is_wp_error( $zone ) ) {
147
			return $zone;
148
		}
149
150
		$methods = $zone->get_shipping_methods();
151
		$data    = array();
152
153
		foreach ( $methods as $method_obj ) {
154
			$method = $this->prepare_item_for_response( $method_obj, $request );
155
			$data[] = $method;
156
		}
157
158
		return rest_ensure_response( $data );
159
	}
160
161
	/**
162
	 * Create a new shipping zone method instance.
163
	 *
164
	 * @param \WP_REST_Request $request Full details about the request.
165
	 * @return \WP_REST_Request|\WP_Error
166
	 */
167
	public function create_item( $request ) {
168
		$method_id = $request['method_id'];
169
		$zone      = $this->get_zone( $request['zone_id'] );
170
		if ( is_wp_error( $zone ) ) {
171
			return $zone;
172
		}
173
174
		$instance_id = $zone->add_shipping_method( $method_id );
0 ignored issues
show
Bug introduced by
The method add_shipping_method() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
		/** @scrutinizer ignore-call */ 
175
  $instance_id = $zone->add_shipping_method( $method_id );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
175
		$methods     = $zone->get_shipping_methods();
176
		$method      = false;
177
		foreach ( $methods as $method_obj ) {
178
			if ( $instance_id === $method_obj->instance_id ) {
179
				$method = $method_obj;
180
				break;
181
			}
182
		}
183
184
		if ( false === $method ) {
185
			return new \WP_Error( 'woocommerce_rest_shipping_zone_not_created', __( 'Resource cannot be created.', 'woocommerce-rest-api' ), array( 'status' => 500 ) );
186
		}
187
188
		$method = $this->update_fields( $instance_id, $method, $request );
189
		if ( is_wp_error( $method ) ) {
190
			return $method;
191
		}
192
193
		$data = $this->prepare_item_for_response( $method, $request );
194
		return rest_ensure_response( $data );
195
	}
196
197
	/**
198
	 * Delete a shipping method instance.
199
	 *
200
	 * @param \WP_REST_Request $request Full details about the request.
201
	 * @return \WP_Error|boolean
202
	 */
203
	public function delete_item( $request ) {
204
		$zone = $this->get_zone( $request['zone_id'] );
205
		if ( is_wp_error( $zone ) ) {
206
			return $zone;
207
		}
208
209
		$instance_id = (int) $request['instance_id'];
210
		$force       = $request['force'];
211
212
		// We don't support trashing for this type, error out.
213
		if ( ! $force ) {
214
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Shipping methods do not support trashing.', 'woocommerce-rest-api' ), array( 'status' => 501 ) );
0 ignored issues
show
Bug introduced by
The type Automattic\WooCommerce\R...llers\Version4\WP_Error was not found. Did you mean WP_Error? If so, make sure to prefix the type with \.
Loading history...
215
		}
216
217
		$methods = $zone->get_shipping_methods();
218
		$method  = false;
219
220
		foreach ( $methods as $method_obj ) {
221
			if ( $instance_id === $method_obj->instance_id ) {
222
				$method = $method_obj;
223
				break;
224
			}
225
		}
226
227
		if ( false === $method ) {
228
			return new \WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
229
		}
230
231
		$method = $this->update_fields( $instance_id, $method, $request );
232
		if ( is_wp_error( $method ) ) {
233
			return $method;
234
		}
235
236
		$request->set_param( 'context', 'view' );
237
		$previous = $this->prepare_item_for_response( $method, $request );
238
239
		// Actually delete.
240
		$zone->delete_shipping_method( $instance_id );
0 ignored issues
show
Bug introduced by
The method delete_shipping_method() does not exist on WP_Error. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

240
		$zone->/** @scrutinizer ignore-call */ 
241
         delete_shipping_method( $instance_id );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
241
		$response = new \WP_REST_Response();
242
		$response->set_data(
243
			array(
244
				'deleted'  => true,
245
				'previous' => $previous,
246
			)
247
		);
248
249
		/**
250
		 * Fires after a method is deleted via the REST API.
251
		 *
252
		 * @param object           $method
253
		 * @param \WP_REST_Response $response        The response data.
254
		 * @param \WP_REST_Request  $request         The request sent to the API.
255
		 */
256
		do_action( 'woocommerce_rest_delete_shipping_zone_method', $method, $response, $request );
257
258
		return $response;
259
	}
260
261
	/**
262
	 * Update A Single Shipping Zone Method.
263
	 *
264
	 * @param \WP_REST_Request $request Request data.
265
	 * @return \WP_REST_Response|\WP_Error
266
	 */
267
	public function update_item( $request ) {
268
		$zone = $this->get_zone( $request['zone_id'] );
269
		if ( is_wp_error( $zone ) ) {
270
			return $zone;
271
		}
272
273
		$instance_id = (int) $request['instance_id'];
274
		$methods     = $zone->get_shipping_methods();
275
		$method      = false;
276
277
		foreach ( $methods as $method_obj ) {
278
			if ( $instance_id === $method_obj->instance_id ) {
279
				$method = $method_obj;
280
				break;
281
			}
282
		}
283
284
		if ( false === $method ) {
285
			return new \WP_Error( 'woocommerce_rest_shipping_zone_method_invalid', __( 'Resource does not exist.', 'woocommerce-rest-api' ), array( 'status' => 404 ) );
286
		}
287
288
		$method = $this->update_fields( $instance_id, $method, $request );
289
		if ( is_wp_error( $method ) ) {
290
			return $method;
291
		}
292
293
		$data = $this->prepare_item_for_response( $method, $request );
294
		return rest_ensure_response( $data );
295
	}
296
297
	/**
298
	 * Updates settings, order, and enabled status on create.
299
	 *
300
	 * @param int                 $instance_id Instance ID.
301
	 * @param \WC_Shipping_Method $method      Shipping method data.
302
	 * @param \WP_REST_Request    $request     Request data.
303
	 *
304
	 * @return WC_Shipping_Method
0 ignored issues
show
Bug introduced by
The type Automattic\WooCommerce\R...ion4\WC_Shipping_Method was not found. Did you mean WC_Shipping_Method? If so, make sure to prefix the type with \.
Loading history...
305
	 */
306
	public function update_fields( $instance_id, $method, $request ) {
307
		global $wpdb;
308
309
		// Update settings if present.
310
		if ( isset( $request['settings'] ) ) {
311
			$method->init_instance_settings();
312
			$instance_settings = $method->instance_settings;
313
			$errors_found      = false;
314
			foreach ( $method->get_instance_form_fields() as $key => $field ) {
315
				if ( isset( $request['settings'][ $key ] ) ) {
316
					if ( is_callable( array( $this, 'validate_setting_' . $field['type'] . '_field' ) ) ) {
317
						$value = $this->{'validate_setting_' . $field['type'] . '_field'}( $request['settings'][ $key ], $field );
318
					} else {
319
						$value = $this->validate_setting_text_field( $request['settings'][ $key ], $field );
320
					}
321
					if ( is_wp_error( $value ) ) {
322
						$errors_found = true;
323
						break;
324
					}
325
					$instance_settings[ $key ] = $value;
326
				}
327
			}
328
329
			if ( $errors_found ) {
330
				return new \WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce-rest-api' ), array( 'status' => 400 ) );
331
			}
332
333
			update_option( $method->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $method->id . '_instance_settings_values', $instance_settings, $method ) );
334
		}
335
336
		// Update order.
337
		if ( isset( $request['order'] ) ) {
338
			$wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'method_order' => absint( $request['order'] ) ), array( 'instance_id' => absint( $instance_id ) ) );
339
			$method->method_order = absint( $request['order'] );
0 ignored issues
show
Bug introduced by
The property method_order does not exist on WC_Shipping_Method. Did you mean method_title?
Loading history...
340
		}
341
342
		// Update if this method is enabled or not.
343
		if ( isset( $request['enabled'] ) ) {
344
			if ( $wpdb->update( "{$wpdb->prefix}woocommerce_shipping_zone_methods", array( 'is_enabled' => $request['enabled'] ), array( 'instance_id' => absint( $instance_id ) ) ) ) {
345
				do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method->id, $request['zone_id'], $request['enabled'] );
346
				$method->enabled = ( true === $request['enabled'] ? 'yes' : 'no' );
347
			}
348
		}
349
350
		return $method;
351
	}
352
353
	/**
354
	 * Get data for this object in the format of this endpoint's schema.
355
	 *
356
	 * @param array            $object Object to prepare.
357
	 * @param \WP_REST_Request $request Request object.
358
	 * @return array Array of data in the correct format.
359
	 */
360
	protected function get_data_for_response( $object, $request ) {
361
		return array(
362
			'id'                 => $object->instance_id,
363
			'instance_id'        => $object->instance_id,
364
			'title'              => $object->instance_settings['title'],
365
			'order'              => $object->method_order,
366
			'enabled'            => ( 'yes' === $object->enabled ),
367
			'method_id'          => $object->id,
368
			'method_title'       => $object->method_title,
369
			'method_description' => $object->method_description,
370
			'settings'           => $this->get_settings( $object ),
0 ignored issues
show
Bug introduced by
$object of type array is incompatible with the type Automattic\WooCommerce\R...ion4\WC_Shipping_Method expected by parameter $item of Automattic\WooCommerce\R...Methods::get_settings(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

370
			'settings'           => $this->get_settings( /** @scrutinizer ignore-type */ $object ),
Loading history...
371
		);
372
	}
373
374
	/**
375
	 * Prepare a single item for response.
376
	 *
377
	 * @param mixed            $item Object used to create response.
378
	 * @param \WP_REST_Request $request Request object.
379
	 * @return \WP_REST_Response $response Response data.
380
	 */
381
	public function prepare_item_for_response( $item, $request ) {
382
		$response = parent::prepare_item_for_response( $item, $request );
383
		$response = $this->prepare_response_for_collection( $response );
384
		return $response;
385
	}
386
387
	/**
388
	 * Return settings associated with this shipping zone method instance.
389
	 *
390
	 * @param WC_Shipping_Method $item Shipping method data.
391
	 *
392
	 * @return array
393
	 */
394
	public function get_settings( $item ) {
395
		$item->init_instance_settings();
396
		$settings = array();
397
		foreach ( $item->get_instance_form_fields() as $id => $field ) {
398
			$data = array(
399
				'id'          => $id,
400
				'label'       => $field['title'],
401
				'description' => empty( $field['description'] ) ? '' : $field['description'],
402
				'type'        => $field['type'],
403
				'value'       => $item->instance_settings[ $id ],
404
				'default'     => empty( $field['default'] ) ? '' : $field['default'],
405
				'tip'         => empty( $field['description'] ) ? '' : $field['description'],
406
				'placeholder' => empty( $field['placeholder'] ) ? '' : $field['placeholder'],
407
			);
408
			if ( ! empty( $field['options'] ) ) {
409
				$data['options'] = $field['options'];
410
			}
411
			$settings[ $id ] = $data;
412
		}
413
		return $settings;
414
	}
415
416
	/**
417
	 * Prepare links for the request.
418
	 *
419
	 * @param mixed            $item Object to prepare.
420
	 * @param \WP_REST_Request $request Request object.
421
	 * @return array
422
	 */
423
	protected function prepare_links( $item, $request ) {
424
		$base  = '/' . $this->namespace . '/' . $this->rest_base . '/' . $request['zone_id'];
425
		$links = array(
426
			'self'       => array(
427
				'href' => rest_url( $base . '/methods/' . $item->instance_id ),
428
			),
429
			'collection' => array(
430
				'href' => rest_url( $base . '/methods' ),
431
			),
432
			'describes'  => array(
433
				'href' => rest_url( $base ),
434
			),
435
		);
436
437
		return $links;
438
	}
439
440
	/**
441
	 * Get the Shipping Zone Methods schema, conforming to JSON Schema
442
	 *
443
	 * @return array
444
	 */
445
	public function get_item_schema() {
446
		$schema = array(
447
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
448
			'title'      => 'shipping_zone_method',
449
			'type'       => 'object',
450
			'properties' => array(
451
				'id'                 => array(
452
					'description' => __( 'Shipping method instance ID.', 'woocommerce-rest-api' ),
453
					'type'        => 'integer',
454
					'context'     => array( 'view', 'edit' ),
455
					'readonly'    => true,
456
				),
457
				'instance_id'        => array(
458
					'description' => __( 'Shipping method instance ID.', 'woocommerce-rest-api' ),
459
					'type'        => 'integer',
460
					'context'     => array( 'view', 'edit' ),
461
					'readonly'    => true,
462
				),
463
				'title'              => array(
464
					'description' => __( 'Shipping method customer facing title.', 'woocommerce-rest-api' ),
465
					'type'        => 'string',
466
					'context'     => array( 'view', 'edit' ),
467
					'readonly'    => true,
468
				),
469
				'order'              => array(
470
					'description' => __( 'Shipping method sort order.', 'woocommerce-rest-api' ),
471
					'type'        => 'integer',
472
					'context'     => array( 'view', 'edit' ),
473
				),
474
				'enabled'            => array(
475
					'description' => __( 'Shipping method enabled status.', 'woocommerce-rest-api' ),
476
					'type'        => 'boolean',
477
					'context'     => array( 'view', 'edit' ),
478
				),
479
				'method_id'          => array(
480
					'description' => __( 'Shipping method ID.', 'woocommerce-rest-api' ),
481
					'type'        => 'string',
482
					'context'     => array( 'view', 'edit' ),
483
					'readonly'    => true,
484
				),
485
				'method_title'       => array(
486
					'description' => __( 'Shipping method title.', 'woocommerce-rest-api' ),
487
					'type'        => 'string',
488
					'context'     => array( 'view', 'edit' ),
489
					'readonly'    => true,
490
				),
491
				'method_description' => array(
492
					'description' => __( 'Shipping method description.', 'woocommerce-rest-api' ),
493
					'type'        => 'string',
494
					'context'     => array( 'view', 'edit' ),
495
					'readonly'    => true,
496
				),
497
				'settings'           => array(
498
					'description' => __( 'Shipping method settings.', 'woocommerce-rest-api' ),
499
					'type'        => 'object',
500
					'context'     => array( 'view', 'edit' ),
501
					'properties'  => array(
502
						'id'          => array(
503
							'description' => __( 'A unique identifier for the setting.', 'woocommerce-rest-api' ),
504
							'type'        => 'string',
505
							'context'     => array( 'view', 'edit' ),
506
							'readonly'    => true,
507
						),
508
						'label'       => array(
509
							'description' => __( 'A human readable label for the setting used in interfaces.', 'woocommerce-rest-api' ),
510
							'type'        => 'string',
511
							'context'     => array( 'view', 'edit' ),
512
							'readonly'    => true,
513
						),
514
						'description' => array(
515
							'description' => __( 'A human readable description for the setting used in interfaces.', 'woocommerce-rest-api' ),
516
							'type'        => 'string',
517
							'context'     => array( 'view', 'edit' ),
518
							'readonly'    => true,
519
						),
520
						'type'        => array(
521
							'description' => __( 'Type of setting.', 'woocommerce-rest-api' ),
522
							'type'        => 'string',
523
							'context'     => array( 'view', 'edit' ),
524
							'enum'        => array( 'text', 'email', 'number', 'color', 'password', 'textarea', 'select', 'multiselect', 'radio', 'image_width', 'checkbox' ),
525
							'readonly'    => true,
526
						),
527
						'value'       => array(
528
							'description' => __( 'Setting value.', 'woocommerce-rest-api' ),
529
							'type'        => 'string',
530
							'context'     => array( 'view', 'edit' ),
531
						),
532
						'default'     => array(
533
							'description' => __( 'Default value for the setting.', 'woocommerce-rest-api' ),
534
							'type'        => 'string',
535
							'context'     => array( 'view', 'edit' ),
536
							'readonly'    => true,
537
						),
538
						'tip'         => array(
539
							'description' => __( 'Additional help text shown to the user about the setting.', 'woocommerce-rest-api' ),
540
							'type'        => 'string',
541
							'context'     => array( 'view', 'edit' ),
542
							'readonly'    => true,
543
						),
544
						'placeholder' => array(
545
							'description' => __( 'Placeholder text to be displayed in text inputs.', 'woocommerce-rest-api' ),
546
							'type'        => 'string',
547
							'context'     => array( 'view', 'edit' ),
548
							'readonly'    => true,
549
						),
550
					),
551
				),
552
			),
553
		);
554
555
		return $this->add_additional_fields_schema( $schema );
556
	}
557
}
558