Completed
Push — master ( c63cd8...c4c8fb )
by Claudio
10:23
created

WC_Product_Variation::get_stock_quantity()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 9
loc 9
ccs 4
cts 5
cp 0.8
crap 3.072
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Product Variation
4
 *
5
 * The WooCommerce product variation class handles product variation data.
6
 *
7
 * @package WooCommerce/Classes
8
 * @version 3.0.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * Product variation class.
15
 */
16
class WC_Product_Variation extends WC_Product_Simple {
17
18
	/**
19
	 * Post type.
20
	 *
21
	 * @var string
22
	 */
23
	protected $post_type = 'product_variation';
24
25
	/**
26
	 * Parent data.
27
	 *
28
	 * @var array
29
	 */
30
	protected $parent_data = array(
31
		'title'             => '',
32
		'sku'               => '',
33
		'manage_stock'      => '',
34
		'backorders'        => '',
35
		'stock_quantity'    => '',
36
		'weight'            => '',
37
		'length'            => '',
38
		'width'             => '',
39
		'height'            => '',
40
		'tax_class'         => '',
41
		'shipping_class_id' => '',
42
		'image_id'          => '',
43
		'purchase_note'     => '',
44
	);
45
46
	/**
47
	 * Override the default constructor to set custom defaults.
48
	 *
49
	 * @param int|WC_Product|object $product Product to init.
50
	 */
51 31
	public function __construct( $product = 0 ) {
52 31
		$this->data['tax_class']         = 'parent';
53 31
		$this->data['attribute_summary'] = '';
54 31
		parent::__construct( $product );
55
	}
56
57
	/**
58
	 * Prefix for action and filter hooks on data.
59
	 *
60
	 * @since  3.0.0
61
	 * @return string
62
	 */
63 31
	protected function get_hook_prefix() {
64 31
		return 'woocommerce_product_variation_get_';
65
	}
66
67
	/**
68
	 * Get internal type.
69
	 *
70
	 * @return string
71
	 */
72 31
	public function get_type() {
73 31
		return 'variation';
74
	}
75
76
	/**
77
	 * If the stock level comes from another product ID.
78
	 *
79
	 * @since  3.0.0
80
	 * @return int
81
	 */
82
	public function get_stock_managed_by_id() {
83
		return 'parent' === $this->get_manage_stock() ? $this->get_parent_id() : $this->get_id();
84
	}
85
86
	/**
87
	 * Get the product's title. For variations this is the parent product name.
88
	 *
89
	 * @return string
90
	 */
91
	public function get_title() {
92
		return apply_filters( 'woocommerce_product_title', $this->parent_data['title'], $this );
93
	}
94
95
	/**
96
	 * Get product name with SKU or ID. Used within admin.
97
	 *
98
	 * @return string Formatted product name
99
	 */
100
	public function get_formatted_name() {
101
		if ( $this->get_sku() ) {
102
			$identifier = $this->get_sku();
103
		} else {
104
			$identifier = '#' . $this->get_id();
105
		}
106
107
		$formatted_variation_list = wc_get_formatted_variation( $this, true, true, true );
108
109
		return sprintf( '%2$s (%1$s)', $identifier, $this->get_name() ) . '<span class="description">' . $formatted_variation_list . '</span>';
110
	}
111
112
	/**
113
	 * Get variation attribute values. Keys are prefixed with attribute_, as stored.
114
	 *
115
	 * @return array of attributes and their values for this variation
116
	 */
117 29
	public function get_variation_attributes() {
118 29
		$attributes           = $this->get_attributes();
119 29
		$variation_attributes = array();
120 29
		foreach ( $attributes as $key => $value ) {
121 20
			$variation_attributes[ 'attribute_' . $key ] = $value;
122
		}
123 29
		return $variation_attributes;
124
	}
125
126
	/**
127
	 * Returns a single product attribute as a string.
128
	 *
129
	 * @param  string $attribute to get.
130
	 * @return string
131
	 */
132
	public function get_attribute( $attribute ) {
133
		$attributes = $this->get_attributes();
134
		$attribute  = sanitize_title( $attribute );
135
136 View Code Duplication
		if ( isset( $attributes[ $attribute ] ) ) {
137
			$value = $attributes[ $attribute ];
138
			$term  = taxonomy_exists( $attribute ) ? get_term_by( 'slug', $value, $attribute ) : false;
139
			return ! is_wp_error( $term ) && $term ? $term->name : $value;
140
		}
141
142
		$att_str = 'pa_' . $attribute;
143 View Code Duplication
		if ( isset( $attributes[ $att_str ] ) ) {
144
			$value = $attributes[ $att_str ];
145
			$term  = taxonomy_exists( $att_str ) ? get_term_by( 'slug', $value, $att_str ) : false;
146
			return ! is_wp_error( $term ) && $term ? $term->name : $value;
147
		}
148
149
		return '';
150
	}
151
152
	/**
153
	 * Wrapper for get_permalink. Adds this variations attributes to the URL.
154
	 *
155
	 * @param  array|null $item_object item array If a cart or order item is passed, we can get a link containing the exact attributes selected for the variation, rather than the default attributes.
156
	 * @return string
157
	 */
158 29
	public function get_permalink( $item_object = null ) {
159 29
		$url = get_permalink( $this->get_parent_id() );
160
161 29
		if ( ! empty( $item_object['variation'] ) ) {
162
			$data = $item_object['variation'];
163 29
		} elseif ( ! empty( $item_object['item_meta_array'] ) ) {
164
			$data_keys   = array_map( 'wc_variation_attribute_name', wp_list_pluck( $item_object['item_meta_array'], 'key' ) );
165
			$data_values = wp_list_pluck( $item_object['item_meta_array'], 'value' );
166
			$data        = array_intersect_key( array_combine( $data_keys, $data_values ), $this->get_variation_attributes() );
167
		} else {
168 29
			$data = $this->get_variation_attributes();
169
		}
170
171 29
		$data = array_filter( $data, 'wc_array_filter_default_attributes' );
172
173 29
		if ( empty( $data ) ) {
174 29
			return $url;
175
		}
176
177
		// Filter and encode keys and values so this is not broken by add_query_arg.
178
		$data = array_map( 'urlencode', $data );
179
		$keys = array_map( 'urlencode', array_keys( $data ) );
180
181
		return add_query_arg( array_combine( $keys, $data ), $url );
182
	}
183
184
	/**
185
	 * Get the add to url used mainly in loops.
186
	 *
187
	 * @return string
188
	 */
189
	public function add_to_cart_url() {
190
		$url = $this->is_purchasable() ? remove_query_arg(
191
			'added-to-cart',
192
			add_query_arg(
193
				array(
194
					'variation_id' => $this->get_id(),
195
					'add-to-cart'  => $this->get_parent_id(),
196
				),
197
				$this->get_permalink()
198
			)
199
		) : $this->get_permalink();
200
		return apply_filters( 'woocommerce_product_add_to_cart_url', $url, $this );
201
	}
202
203
	/**
204
	 * Get SKU (Stock-keeping unit) - product unique ID.
205
	 *
206
	 * @param  string $context What the value is for. Valid values are view and edit.
207
	 * @return string
208
	 */
209 31
	public function get_sku( $context = 'view' ) {
210 31
		$value = $this->get_prop( 'sku', $context );
211
212
		// Inherit value from parent.
213 31
		if ( 'view' === $context && empty( $value ) ) {
214
			$value = apply_filters( $this->get_hook_prefix() . 'sku', $this->parent_data['sku'], $this );
215
		}
216 31
		return $value;
217
	}
218
219
	/**
220
	 * Returns the product's weight.
221
	 *
222
	 * @param  string $context What the value is for. Valid values are view and edit.
223
	 * @return string
224
	 */
225 31
	public function get_weight( $context = 'view' ) {
226 31
		$value = $this->get_prop( 'weight', $context );
227
228
		// Inherit value from parent.
229 31
		if ( 'view' === $context && empty( $value ) ) {
230 6
			$value = apply_filters( $this->get_hook_prefix() . 'weight', $this->parent_data['weight'], $this );
231
		}
232 31
		return $value;
233
	}
234
235
	/**
236
	 * Returns the product length.
237
	 *
238
	 * @param  string $context What the value is for. Valid values are view and edit.
239
	 * @return string
240
	 */
241 31
	public function get_length( $context = 'view' ) {
242 31
		$value = $this->get_prop( 'length', $context );
243
244
		// Inherit value from parent.
245 31
		if ( 'view' === $context && empty( $value ) ) {
246 6
			$value = apply_filters( $this->get_hook_prefix() . 'length', $this->parent_data['length'], $this );
247
		}
248 31
		return $value;
249
	}
250
251
	/**
252
	 * Returns the product width.
253
	 *
254
	 * @param  string $context What the value is for. Valid values are view and edit.
255
	 * @return string
256
	 */
257 31
	public function get_width( $context = 'view' ) {
258 31
		$value = $this->get_prop( 'width', $context );
259
260
		// Inherit value from parent.
261 31
		if ( 'view' === $context && empty( $value ) ) {
262 6
			$value = apply_filters( $this->get_hook_prefix() . 'width', $this->parent_data['width'], $this );
263
		}
264 31
		return $value;
265
	}
266
267
	/**
268
	 * Returns the product height.
269
	 *
270
	 * @param  string $context What the value is for. Valid values are view and edit.
271
	 * @return string
272
	 */
273 31
	public function get_height( $context = 'view' ) {
274 31
		$value = $this->get_prop( 'height', $context );
275
276
		// Inherit value from parent.
277 31
		if ( 'view' === $context && empty( $value ) ) {
278 6
			$value = apply_filters( $this->get_hook_prefix() . 'height', $this->parent_data['height'], $this );
279
		}
280 31
		return $value;
281
	}
282
283
	/**
284
	 * Returns the tax class.
285
	 *
286
	 * Does not use get_prop so it can handle 'parent' Inheritance correctly.
287
	 *
288
	 * @param  string $context view, edit, or unfiltered.
289
	 * @return string
290
	 */
291 31
	public function get_tax_class( $context = 'view' ) {
292 31
		$value = null;
293
294 31
		if ( array_key_exists( 'tax_class', $this->data ) ) {
295 31
			$value = array_key_exists( 'tax_class', $this->changes ) ? $this->changes['tax_class'] : $this->data['tax_class'];
296
297 31
			if ( 'edit' !== $context && 'parent' === $value ) {
298 8
				$value = $this->parent_data['tax_class'];
299
			}
300
301 31
			if ( 'view' === $context ) {
302 8
				$value = apply_filters( $this->get_hook_prefix() . 'tax_class', $value, $this );
303
			}
304
		}
305 31
		return $value;
306
	}
307
308
	/**
309
	 * Return if product manage stock.
310
	 *
311
	 * @since 3.0.0
312
	 * @param  string $context What the value is for. Valid values are view and edit.
313
	 * @return boolean|string true, false, or parent.
314
	 */
315 31
	public function get_manage_stock( $context = 'view' ) {
316 31
		$value = $this->get_prop( 'manage_stock', $context );
317
318
		// Inherit value from parent.
319 31
		if ( 'view' === $context && false === $value && true === wc_string_to_bool( $this->parent_data['manage_stock'] ) ) {
320
			$value = 'parent';
321
		}
322 31
		return $value;
323
	}
324
325
	/**
326
	 * Returns number of items available for sale.
327
	 *
328
	 * @param  string $context What the value is for. Valid values are view and edit.
329
	 * @return int|null
330
	 */
331 31 View Code Duplication
	public function get_stock_quantity( $context = 'view' ) {
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...
332 31
		$value = $this->get_prop( 'stock_quantity', $context );
333
334
		// Inherit value from parent.
335 31
		if ( 'view' === $context && 'parent' === $this->get_manage_stock() ) {
336
			$value = apply_filters( $this->get_hook_prefix() . 'stock_quantity', $this->parent_data['stock_quantity'], $this );
337
		}
338 31
		return $value;
339
	}
340
341
	/**
342
	 * Get backorders.
343
	 *
344
	 * @param  string $context What the value is for. Valid values are view and edit.
345
	 * @since 3.0.0
346
	 * @return string yes no or notify
347
	 */
348 31 View Code Duplication
	public function get_backorders( $context = 'view' ) {
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...
349 31
		$value = $this->get_prop( 'backorders', $context );
350
351
		// Inherit value from parent.
352 31
		if ( 'view' === $context && 'parent' === $this->get_manage_stock() ) {
353
			$value = apply_filters( $this->get_hook_prefix() . 'backorders', $this->parent_data['backorders'], $this );
354
		}
355 31
		return $value;
356
	}
357
358
	/**
359
	 * Get main image ID.
360
	 *
361
	 * @since 3.0.0
362
	 * @param  string $context What the value is for. Valid values are view and edit.
363
	 * @return string
364
	 */
365 31
	public function get_image_id( $context = 'view' ) {
366 31
		$image_id = $this->get_prop( 'image_id', $context );
367
368 31
		if ( 'view' === $context && ! $image_id ) {
369 6
			$image_id = apply_filters( $this->get_hook_prefix() . 'image_id', $this->parent_data['image_id'], $this );
370
		}
371
372 31
		return $image_id;
373
	}
374
375
	/**
376
	 * Get purchase note.
377
	 *
378
	 * @since 3.0.0
379
	 * @param  string $context What the value is for. Valid values are view and edit.
380
	 * @return string
381
	 */
382 31
	public function get_purchase_note( $context = 'view' ) {
383 31
		$value = $this->get_prop( 'purchase_note', $context );
384
385
		// Inherit value from parent.
386 31
		if ( 'view' === $context && empty( $value ) ) {
387
			$value = apply_filters( $this->get_hook_prefix() . 'purchase_note', $this->parent_data['purchase_note'], $this );
388
		}
389 31
		return $value;
390
	}
391
392
	/**
393
	 * Get shipping class ID.
394
	 *
395
	 * @since 3.0.0
396
	 * @param  string $context What the value is for. Valid values are view and edit.
397
	 * @return int
398
	 */
399 31
	public function get_shipping_class_id( $context = 'view' ) {
400 31
		$shipping_class_id = $this->get_prop( 'shipping_class_id', $context );
401
402 31
		if ( 'view' === $context && ! $shipping_class_id ) {
403 4
			$shipping_class_id = apply_filters( $this->get_hook_prefix() . 'shipping_class_id', $this->parent_data['shipping_class_id'], $this );
404
		}
405
406 31
		return $shipping_class_id;
407
	}
408
409
	/**
410
	 * Get catalog visibility.
411
	 *
412
	 * @param  string $context What the value is for. Valid values are view and edit.
413
	 * @return string
414
	 */
415 1
	public function get_catalog_visibility( $context = 'view' ) {
416 1
		return apply_filters( $this->get_hook_prefix() . 'catalog_visibility', $this->parent_data['catalog_visibility'], $this );
417
	}
418
419
	/**
420
	 * Get attribute summary.
421
	 *
422
	 * By default, attribute summary contains comma-delimited 'attribute_name: attribute_value' pairs for all attributes.
423
	 *
424
	 * @param string $context What the value is for. Valid values are view and edit.
425
	 *
426
	 * @since 3.6.0
427
	 * @return string
428
	 */
429 31
	public function get_attribute_summary( $context = 'view' ) {
430 31
		return $this->get_prop( 'attribute_summary', $context );
431
	}
432
433
434
	/**
435
	 * Set attribute summary.
436
	 *
437
	 * By default, attribute summary contains comma-delimited 'attribute_name: attribute_value' pairs for all attributes.
438
	 *
439
	 * @since 3.6.0
440
	 * @param string $attribute_summary Summary of attribute names and values assigned to the variation.
441
	 */
442 31
	public function set_attribute_summary( $attribute_summary ) {
443 31
		$this->set_prop( 'attribute_summary', $attribute_summary );
444
	}
445
446
	/*
447
	|--------------------------------------------------------------------------
448
	| CRUD methods
449
	|--------------------------------------------------------------------------
450
	*/
451
452
	/**
453
	 * Set the parent data array for this variation.
454
	 *
455
	 * @since 3.0.0
456
	 * @param array $parent_data parent data array for this variation.
457
	 */
458 31
	public function set_parent_data( $parent_data ) {
459 31
		$parent_data = wp_parse_args(
460 31
			$parent_data,
461
			array(
462 31
				'title'              => '',
463
				'status'             => '',
464
				'sku'                => '',
465
				'manage_stock'       => 'no',
466
				'backorders'         => 'no',
467
				'stock_quantity'     => '',
468
				'weight'             => '',
469
				'length'             => '',
470
				'width'              => '',
471
				'height'             => '',
472
				'tax_class'          => '',
473
				'shipping_class_id'  => 0,
474
				'image_id'           => 0,
475
				'purchase_note'      => '',
476
				'catalog_visibility' => 'visible',
477
			)
478
		);
479
480
		// Normalize tax class.
481 31
		$parent_data['tax_class'] = sanitize_title( $parent_data['tax_class'] );
482 31
		$parent_data['tax_class'] = 'standard' === $parent_data['tax_class'] ? '' : $parent_data['tax_class'];
483 31
		$valid_classes            = $this->get_valid_tax_classes();
484
485 31
		if ( ! in_array( $parent_data['tax_class'], $valid_classes, true ) ) {
486 31
			$parent_data['tax_class'] = '';
487
		}
488
489 31
		$this->parent_data = $parent_data;
490
	}
491
492
	/**
493
	 * Get the parent data array for this variation.
494
	 *
495
	 * @since  3.0.0
496
	 * @return array
497
	 */
498
	public function get_parent_data() {
499
		return $this->parent_data;
500
	}
501
502
	/**
503
	 * Set attributes. Unlike the parent product which uses terms, variations are assigned
504
	 * specific attributes using name value pairs.
505
	 *
506
	 * @param array $raw_attributes array of raw attributes.
507
	 */
508 31
	public function set_attributes( $raw_attributes ) {
509 31
		$raw_attributes = (array) $raw_attributes;
510 31
		$attributes     = array();
511
512 31
		foreach ( $raw_attributes as $key => $value ) {
513
			// Remove attribute prefix which meta gets stored with.
514 23
			if ( 0 === strpos( $key, 'attribute_' ) ) {
515 19
				$key = substr( $key, 10 );
516
			}
517 23
			$attributes[ $key ] = $value;
518
		}
519 31
		$this->set_prop( 'attributes', $attributes );
520
	}
521
522
	/**
523
	 * Returns whether or not the product has any visible attributes.
524
	 *
525
	 * Variations are mapped to specific attributes unlike products, and the return
526
	 * value of ->get_attributes differs. Therefore this returns false.
527
	 *
528
	 * @return boolean
529
	 */
530
	public function has_attributes() {
531
		return false;
532
	}
533
534
	/*
535
	|--------------------------------------------------------------------------
536
	| Conditionals
537
	|--------------------------------------------------------------------------
538
	*/
539
540
	/**
541
	 * Returns false if the product cannot be bought.
542
	 * Override abstract method so that: i) Disabled variations are not be purchasable by admins. ii) Enabled variations are not purchasable if the parent product is not purchasable.
543
	 *
544
	 * @return bool
545
	 */
546 5
	public function is_purchasable() {
547 5
		return apply_filters( 'woocommerce_variation_is_purchasable', $this->variation_is_visible() && parent::is_purchasable() && ( 'publish' === $this->parent_data['status'] || current_user_can( 'edit_post', $this->get_parent_id() ) ), $this );
548
	}
549
550
	/**
551
	 * Controls whether this particular variation will appear greyed-out (inactive) or not (active).
552
	 * Used by extensions to make incompatible variations appear greyed-out, etc.
553
	 * Other possible uses: prevent out-of-stock variations from being selected.
554
	 *
555
	 * @return bool
556
	 */
557 5
	public function variation_is_active() {
558 5
		return apply_filters( 'woocommerce_variation_is_active', true, $this );
559
	}
560
561
	/**
562
	 * Checks if this particular variation is visible. Invisible variations are enabled and can be selected, but no price / stock info is displayed.
563
	 * Instead, a suitable 'unavailable' message is displayed.
564
	 * Invisible by default: Disabled variations and variations with an empty price.
565
	 *
566
	 * @return bool
567
	 */
568 5
	public function variation_is_visible() {
569 5
		return apply_filters( 'woocommerce_variation_is_visible', 'publish' === get_post_status( $this->get_id() ) && '' !== $this->get_price(), $this->get_id(), $this->get_parent_id(), $this );
570
	}
571
572
	/**
573
	 * Return valid tax classes. Adds 'parent' to the default list of valid tax classes.
574
	 *
575
	 * @return array valid tax classes
576
	 */
577 31
	protected function get_valid_tax_classes() {
578 31
		$valid_classes   = WC_Tax::get_tax_class_slugs();
579 31
		$valid_classes[] = 'parent';
580
581 31
		return $valid_classes;
582
	}
583
}
584