Completed
Push — master ( 31131b...6176aa )
by Mike
15:27
created

ProductRequest   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 422
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 219
dl 0
loc 422
rs 2
c 0
b 0
f 0
wmc 80

13 Methods

Rating   Name   Duplication   Size   Complexity  
B get_product_object() 0 27 7
A set_downloadable_props() 0 15 4
C set_common_props() 0 82 13
A set_variable_props() 0 5 2
A set_grouped_props() 0 5 2
B parse_attributes_field() 0 34 11
A prepare_object() 0 23 5
A parse_downloads_field() 0 14 5
C parse_default_attributes() 0 45 13
A parse_shipping_class() 0 3 1
A parse_dimensions_fields() 0 16 4
A set_external_props() 0 10 3
B parse_images_field() 0 36 10

How to fix   Complexity   

Complex Class

Complex classes like ProductRequest 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 ProductRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Convert data in the product schema format to a product object.
4
 *
5
 * @package WooCommerce/RestApi
6
 */
7
8
namespace WooCommerce\RestApi\Controllers\Version4\Requests;
9
10
defined( 'ABSPATH' ) || exit;
11
12
use \WooCommerce\RestApi\Utilities\ImageAttachment;
13
14
/**
15
 * ProductRequest class.
16
 */
17
class ProductRequest extends AbstractObjectRequest {
18
19
	/**
20
	 * Convert request to object.
21
	 *
22
	 * @return \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External
23
	 */
24
	public function prepare_object() {
25
		$object = $this->get_product_object();
26
27
		$this->set_common_props( $object );
28
		$this->set_meta_data( $object );
29
30
		switch ( $object->get_type() ) {
31
			case 'grouped':
32
				$this->set_grouped_props( $object );
33
				break;
34
			case 'variable':
35
				$this->set_variable_props( $object );
36
				break;
37
			case 'external':
38
				$this->set_external_props( $object );
39
				break;
40
		}
41
42
		if ( $object->get_downloadable() ) {
43
			$this->set_downloadable_props( $object );
44
		}
45
46
		return $object;
47
	}
48
49
	/**
50
	 * Get product object from request args.
51
	 *
52
	 * @throws \WC_REST_Exception Will throw an exception if the resulting product object is invalid.
53
	 * @return \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External
54
	 */
55
	protected function get_product_object() {
56
		$id   = (int) $this->get_param( 'id', 0 );
57
		$type = $this->get_param( 'type', '' );
58
59
		if ( $type ) {
60
			$classname = \WC_Product_Factory::get_classname_from_product_type( $type );
61
			if ( $classname && class_exists( '\\' . $classname ) ) {
62
				$classname = '\\' . $classname;
63
			} else {
64
				$classname = '\WC_Product_Simple';
65
			}
66
			$object = new $classname( $id );
67
		} elseif ( $id ) {
68
			$object = wc_get_product( $id );
69
		} else {
70
			$object = new \WC_Product_Simple();
71
		}
72
73
		if ( ! $object ) {
74
			throw new \WC_REST_Exception( 'woocommerce_rest_invalid_product_id', __( 'Invalid product.', 'woocommerce' ), 404 );
75
		}
76
77
		if ( $object->is_type( 'variation' ) ) {
78
			throw new \WC_REST_Exception( 'woocommerce_rest_invalid_product_id', __( 'To manipulate product variations you should use the /products/&lt;product_id&gt;/variations/&lt;id&gt; endpoint.', 'woocommerce' ), 404 );
79
		}
80
81
		return $object;
82
	}
83
84
	/**
85
	 * Set common product props.
86
	 *
87
	 * @param \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External $object Product object reference.
88
	 */
89
	protected function set_common_props( &$object ) {
90
		$props = [
91
			'name',
92
			'sku',
93
			'description',
94
			'short_description',
95
			'slug',
96
			'menu_order',
97
			'reviews_allowed',
98
			'virtual',
99
			'tax_status',
100
			'tax_class',
101
			'catalog_visibility',
102
			'purchase_note',
103
			'status',
104
			'featured',
105
			'regular_price',
106
			'sale_price',
107
			'date_on_sale_from',
108
			'date_on_sale_from_gmt',
109
			'date_on_sale_to',
110
			'date_on_sale_to_gmt',
111
			'parent_id',
112
			'sold_individually',
113
			'manage_stock',
114
			'backorders',
115
			'stock_status',
116
			'stock_quantity',
117
			'downloadable',
118
			'date_created',
119
			'date_created_gmt',
120
			'upsell_ids',
121
			'cross_sell_ids',
122
			'images',
123
			'categories',
124
			'tags',
125
			'attributes',
126
			'weight',
127
			'dimensions',
128
			'shipping_class',
129
		];
130
131
		$request_props = array_intersect_key( $this->request, array_flip( $props ) );
132
		$prop_values   = [];
133
134
		foreach ( $request_props as $prop => $value ) {
135
			switch ( $prop ) {
136
				case 'date_created':
137
				case 'date_created_gmt':
138
					$prop_values[ $prop ] = rest_parse_date( $value );
139
					break;
140
				case 'upsell_ids':
141
				case 'cross_sell_ids':
142
					$prop_values[ $prop ] = wp_parse_id_list( $value );
143
					break;
144
				case 'images':
145
					$images      = $this->parse_images_field( $value, $object );
146
					$prop_values = array_merge( $prop_values, $images );
147
					break;
148
				case 'categories':
149
					$prop_values['category_ids'] = wp_list_pluck( $value, 'id' );
150
					break;
151
				case 'tags':
152
					$prop_values['tag_ids'] = wp_list_pluck( $value, 'id' );
153
					break;
154
				case 'attributes':
155
					$prop_values['attributes'] = $this->parse_attributes_field( $value );
156
					break;
157
				case 'dimensions':
158
					$dimensions  = $this->parse_dimensions_fields( $value );
159
					$prop_values = array_merge( $prop_values, $dimensions );
160
					break;
161
				case 'shipping_class':
162
					$prop_values['shipping_class_id'] = $this->parse_shipping_class( $value, $object );
163
					break;
164
				default:
165
					$prop_values[ $prop ] = $value;
166
			}
167
		}
168
169
		foreach ( $prop_values as $prop => $value ) {
170
			$object->{"set_$prop"}( $value );
171
		}
172
	}
173
174
	/**
175
	 * Set grouped product props.
176
	 *
177
	 * @param \WC_Product_Grouped $object Product object reference.
178
	 */
179
	protected function set_grouped_props( &$object ) {
180
		$children = $this->get_param( 'grouped_products', null );
181
182
		if ( ! is_null( $children ) ) {
183
			$object->set_children( $children );
184
		}
185
	}
186
187
	/**
188
	 * Set variable product props.
189
	 *
190
	 * @param \WC_Product_Variable $object Product object reference.
191
	 */
192
	protected function set_variable_props( &$object ) {
193
		$default_attributes = $this->get_param( 'default_attributes', null );
194
195
		if ( ! is_null( $default_attributes ) ) {
196
			$object->set_default_attributes( $this->parse_default_attributes( $default_attributes, $object ) );
197
		}
198
	}
199
200
	/**
201
	 * Set external product props.
202
	 *
203
	 * @param \WC_Product_External $object Product object reference.
204
	 */
205
	protected function set_external_props( &$object ) {
206
		$button_text  = $this->get_param( 'button_text', null );
207
		$external_url = $this->get_param( 'external_url', null );
208
209
		if ( ! is_null( $button_text ) ) {
210
			$object->set_button_text( $button_text );
211
		}
212
213
		if ( ! is_null( $external_url ) ) {
214
			$object->set_product_url( $external_url );
215
		}
216
	}
217
218
	/**
219
	 * Set downloadable product props.
220
	 *
221
	 * @param \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External $object Product object reference.
222
	 */
223
	protected function set_downloadable_props( &$object ) {
224
		$download_limit  = $this->get_param( 'download_limit', null );
225
		$download_expiry = $this->get_param( 'download_expiry', null );
226
		$downloads       = $this->get_param( 'downloads', null );
227
228
		if ( ! is_null( $download_limit ) ) {
229
			$object->set_download_limit( $download_limit );
230
		}
231
232
		if ( ! is_null( $download_expiry ) ) {
233
			$object->set_download_expiry( $download_expiry );
234
		}
235
236
		if ( ! is_null( $downloads ) ) {
237
			$object->set_downloads( $this->parse_downloads_field( $downloads ) );
238
		}
239
	}
240
241
	/**
242
	 * Set product object's attributes.
243
	 *
244
	 * @param array $raw_attributes Attribute data from request.
245
	 * @return array
246
	 */
247
	protected function parse_attributes_field( $raw_attributes ) {
248
		$attributes = array();
249
250
		foreach ( $raw_attributes as $attribute ) {
251
			if ( ! empty( $attribute['id'] ) ) {
252
				$attribute_id   = absint( $attribute['id'] );
253
				$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
254
			} elseif ( ! empty( $attribute['name'] ) ) {
255
				$attribute_id   = 0;
256
				$attribute_name = wc_clean( $attribute['name'] );
257
			}
258
259
			if ( ! $attribute_name || ! isset( $attribute['options'] ) ) {
260
				continue;
261
			}
262
263
			if ( ! is_array( $attribute['options'] ) ) {
264
				$attribute['options'] = explode( \WC_DELIMITER, $attribute['options'] );
0 ignored issues
show
Bug introduced by
The constant WC_DELIMITER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
265
			}
266
267
			if ( $attribute_id ) {
268
				$attribute['options'] = array_filter( array_map( 'wc_sanitize_term_text_based', $attribute['options'] ), 'strlen' );
269
			}
270
271
			$attribute_object = new \WC_Product_Attribute();
272
			$attribute_object->set_id( $attribute_id );
273
			$attribute_object->set_name( $attribute_name );
274
			$attribute_object->set_options( $attribute['options'] );
275
			$attribute_object->set_position( isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
0 ignored issues
show
Bug introduced by
IssetNode ? (string)absi...bute['position']) : '0' of type string is incompatible with the type integer expected by parameter $value of WC_Product_Attribute::set_position(). ( Ignorable by Annotation )

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

275
			$attribute_object->set_position( /** @scrutinizer ignore-type */ isset( $attribute['position'] ) ? (string) absint( $attribute['position'] ) : '0' );
Loading history...
276
			$attribute_object->set_visible( ! empty( $attribute['visible'] ) ? 1 : 0 );
0 ignored issues
show
Bug introduced by
! empty($attribute['visible']) ? 1 : 0 of type integer is incompatible with the type boolean expected by parameter $value of WC_Product_Attribute::set_visible(). ( Ignorable by Annotation )

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

276
			$attribute_object->set_visible( /** @scrutinizer ignore-type */ ! empty( $attribute['visible'] ) ? 1 : 0 );
Loading history...
277
			$attribute_object->set_variation( ! empty( $attribute['variation'] ) ? 1 : 0 );
0 ignored issues
show
Bug introduced by
! empty($attribute['variation']) ? 1 : 0 of type integer is incompatible with the type boolean expected by parameter $value of WC_Product_Attribute::set_variation(). ( Ignorable by Annotation )

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

277
			$attribute_object->set_variation( /** @scrutinizer ignore-type */ ! empty( $attribute['variation'] ) ? 1 : 0 );
Loading history...
278
			$attributes[] = $attribute_object;
279
		}
280
		return $attributes;
281
	}
282
283
	/**
284
	 * Set product images.
285
	 *
286
	 * @throws \WC_REST_Exception REST API exceptions.
287
	 * @param array                                                                            $images  Images data.
288
	 * @param \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External $object Product object.
289
	 * @return array
290
	 */
291
	protected function parse_images_field( $images, $object ) {
292
		$response = [
293
			'image_id'          => '',
294
			'gallery_image_ids' => [],
295
		];
296
297
		$images = is_array( $images ) ? array_filter( $images ) : [];
0 ignored issues
show
introduced by
The condition is_array($images) is always true.
Loading history...
298
299
		if ( empty( $images ) ) {
300
			return $response;
301
		}
302
303
		foreach ( $images as $index => $image ) {
304
			$attachment_id = isset( $image['id'] ) ? absint( $image['id'] ) : 0;
305
			$attachment    = new ImageAttachment( $attachment_id, $object->get_id() );
306
307
			if ( 0 === $attachment->id && ! empty( $image['src'] ) ) {
308
				$attachment->upload_image_from_src( $image['src'] );
309
			}
310
311
			if ( ! empty( $image['alt'] ) ) {
312
				$attachment->update_alt_text( $image['alt'] );
313
			}
314
315
			if ( ! empty( $image['name'] ) ) {
316
				$attachment->update_name( $image['name'] );
317
			}
318
319
			if ( 0 === $index ) {
320
				$response['image_id'] = $attachment->id;
321
			} else {
322
				$response['gallery_image_ids'][] = $attachment->id;
323
			}
324
		}
325
326
		return $response;
327
	}
328
329
	/**
330
	 * Parse dimensions.
331
	 *
332
	 * @param array $dimensions Product dimensions.
333
	 * @return array
334
	 */
335
	protected function parse_dimensions_fields( $dimensions ) {
336
		$response = [];
337
338
		if ( isset( $dimensions['length'] ) ) {
339
			$response['length'] = $dimensions['length'];
340
		}
341
342
		if ( isset( $dimensions['width'] ) ) {
343
			$response['width'] = $dimensions['width'];
344
		}
345
346
		if ( isset( $dimensions['height'] ) ) {
347
			$response['height'] = $dimensions['height'];
348
		}
349
350
		return $response;
351
	}
352
353
	/**
354
	 * Parse shipping class.
355
	 *
356
	 * @param string                                                                           $shipping_class Shipping class slug.
357
	 * @param \WC_Product_Simple|\WC_Product_Grouped|\WC_Product_Variable|\WC_Product_External $object Product object.
358
	 * @return int
359
	 */
360
	protected function parse_shipping_class( $shipping_class, $object ) {
361
		$data_store = $object->get_data_store();
362
		return $data_store->get_shipping_class_id_by_slug( wc_clean( $shipping_class ) );
363
	}
364
365
	/**
366
	 * Parse downloadable files.
367
	 *
368
	 * @param array $downloads  Downloads data.
369
	 * @return array
370
	 */
371
	protected function parse_downloads_field( $downloads ) {
372
		$files = array();
373
		foreach ( $downloads as $key => $file ) {
374
			if ( empty( $file['file'] ) ) {
375
				continue;
376
			}
377
378
			$download = new \WC_Product_Download();
379
			$download->set_id( ! empty( $file['id'] ) ? $file['id'] : wp_generate_uuid4() );
380
			$download->set_name( $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['file'] ) );
381
			$download->set_file( $file['file'] );
382
			$files[] = $download;
383
		}
384
		return $files;
385
	}
386
387
	/**
388
	 * Save default attributes.
389
	 *
390
	 * @param array                $raw_default_attributes Default attributes.
391
	 * @param \WC_Product_Variable $object Product object reference.
392
	 * @return array
393
	 */
394
	protected function parse_default_attributes( $raw_default_attributes, $object ) {
395
		$attributes         = $object->get_attributes();
396
		$default_attributes = array();
397
398
		foreach ( $raw_default_attributes as $attribute ) {
399
			$attribute_id   = 0;
400
			$attribute_name = '';
401
402
			// Check ID for global attributes or name for product attributes.
403
			if ( ! empty( $attribute['id'] ) ) {
404
				$attribute_id   = absint( $attribute['id'] );
405
				$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
406
			} elseif ( ! empty( $attribute['name'] ) ) {
407
				$attribute_name = sanitize_title( $attribute['name'] );
408
			}
409
410
			if ( ! $attribute_id && ! $attribute_name ) {
411
				continue;
412
			}
413
414
			if ( isset( $attributes[ $attribute_name ] ) ) {
415
				$_attribute = $attributes[ $attribute_name ];
416
417
				if ( $_attribute['is_variation'] ) {
418
					$value = isset( $attribute['option'] ) ? wc_clean( stripslashes( $attribute['option'] ) ) : '';
419
420
					if ( ! empty( $_attribute['is_taxonomy'] ) ) {
421
						// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
422
						$term = get_term_by( 'name', $value, $attribute_name );
423
424
						if ( $term && ! is_wp_error( $term ) ) {
425
							$value = $term->slug;
426
						} else {
427
							$value = sanitize_title( $value );
428
						}
429
					}
430
431
					if ( $value ) {
432
						$default_attributes[ $attribute_name ] = $value;
433
					}
434
				}
435
			}
436
		}
437
438
		return $default_attributes;
439
	}
440
}
441