Completed
Push — master ( 0f5c70...3a19cf )
by Claudio
09:55
created

WC_Product::get_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/**
3
 * Abstract Product Class
4
 *
5
 * The WooCommerce product class handles individual product data.
6
 *
7
 * @class       WC_Product
8
 * @var         WP_Post
9
 * @version     2.1.0
10
 * @package     WooCommerce/Abstracts
11
 * @category    Abstract Class
12
 * @author      WooThemes
13
 *
14
 * @property    string $width Product width
15
 * @property    string $length Product length
16
 * @property    string $height Product height
17
 * @property    string $weight Product weight
18
 * @property    string $price Product price
19
 * @property    string $regular_price Product regular price
20
 * @property    string $sale_price Product sale price
21
 * @property    string $product_image_gallery String of image IDs in the gallery
22
 * @property    string $sku Product SKU
23
 * @property    string $stock Stock amount
24
 * @property    string $downloadable Shows/define if the product is downloadable
25
 * @property    string $virtual Shows/define if the product is virtual
26
 * @property    string $sold_individually Allow one item to be bought in a single order
27
 * @property    string $tax_status Tax status
28
 * @property    string $tax_class Tax class
29
 * @property    string $manage_stock Shows/define if can manage the product stock
30
 * @property    string $stock_status Stock status
31
 * @property    string $backorders Whether or not backorders are allowed
32
 * @property    string $featured Featured product
33
 * @property    string $visibility Product visibility
34
 * @property    string $variation_id Variation ID when dealing with variations
35
 */
36
class WC_Product {
37
38
	/**
39
	 * The product (post) ID.
40
	 *
41
	 * @var int
42
	 */
43
	public $id = 0;
44
45
	/**
46
	 * $post Stores post data.
47
	 *
48
	 * @var $post WP_Post
49
	 */
50
	public $post = null;
51
52
	/**
53
	 * The product's type (simple, variable etc).
54
	 *
55
	 * @var string
56
	 */
57
	public $product_type = null;
58
59
	/**
60
	 * Product shipping class.
61
	 *
62
	 * @var string
63
	 */
64
	protected $shipping_class    = '';
65
66
	/**
67
	 * ID of the shipping class this product has.
68
	 *
69
	 * @var int
70
	 */
71
	protected $shipping_class_id = 0;
72
73
	/** @public string The product's total stock, including that of its children. */
74
	public $total_stock;
75
76
	/**
77
	 * Supported features such as 'ajax_add_to_cart'.
78
	 * @var array
79
	 */
80
	protected $supports = array();
81
82
	/**
83
	 * Constructor gets the post object and sets the ID for the loaded product.
84
	 *
85
	 * @param int|WC_Product|object $product Product ID, post object, or product object
86
	 */
87
	public function __construct( $product ) {
88
		if ( is_numeric( $product ) ) {
89
			$this->id   = absint( $product );
90
			$this->post = get_post( $this->id );
91
		} elseif ( $product instanceof WC_Product ) {
92
			$this->id   = absint( $product->id );
93
			$this->post = $product->post;
94
		} elseif ( isset( $product->ID ) ) {
95
			$this->id   = absint( $product->ID );
96
			$this->post = $product;
97
		}
98
	}
99
100
	/**
101
	 * __isset function.
102
	 *
103
	 * @param mixed $key
104
	 * @return bool
105
	 */
106
	public function __isset( $key ) {
107
		return metadata_exists( 'post', $this->id, '_' . $key );
108
	}
109
110
	/**
111
	 * __get function.
112
	 *
113
	 * @param string $key
114
	 * @return mixed
115
	 */
116
	public function __get( $key ) {
117
		$value = get_post_meta( $this->id, '_' . $key, true );
118
119
		// Get values or default if not set
120
		if ( in_array( $key, array( 'downloadable', 'virtual', 'backorders', 'manage_stock', 'featured', 'sold_individually' ) ) ) {
121
			$value = $value ? $value : 'no';
122
123
		} elseif ( in_array( $key, array( 'product_attributes', 'crosssell_ids', 'upsell_ids' ) ) ) {
124
			$value = $value ? $value : array();
125
126
		} elseif ( 'visibility' === $key ) {
127
			$value = $value ? $value : 'hidden';
128
129
		} elseif ( 'stock' === $key ) {
130
			$value = $value ? $value : 0;
131
132
		} elseif ( 'stock_status' === $key ) {
133
			$value = $value ? $value : 'instock';
134
135
		} elseif ( 'tax_status' === $key ) {
136
			$value = $value ? $value : 'taxable';
137
138
		}
139
140
		if ( false !== $value ) {
141
			$this->$key = $value;
142
		}
143
144
		return $value;
145
	}
146
147
	/**
148
	 * Get the product's post data.
149
	 *
150
	 * @return object
151
	 */
152
	public function get_post_data() {
153
		return $this->post;
154
	}
155
156
	/**
157
	 * Check if a product supports a given feature.
158
	 *
159
	 * Product classes should override this to declare support (or lack of support) for a feature.
160
	 *
161
	 * @param string $feature string The name of a feature to test support for.
162
	 * @return bool True if the product supports the feature, false otherwise.
163
	 * @since 2.5.0
164
	 */
165
	public function supports( $feature ) {
166
		return apply_filters( 'woocommerce_product_supports', in_array( $feature, $this->supports ) ? true : false, $feature, $this );
167
	}
168
169
	/**
170
	 * Return the product ID
171
	 *
172
	 * @since 2.5.0
173
	 * @return int product (post) ID
174
	 */
175
	public function get_id() {
176
177
		return $this->id;
178
	}
179
180
	/**
181
	 * get_gallery_attachment_ids function.
182
	 *
183
	 * @return array
184
	 */
185
	public function get_gallery_attachment_ids() {
186
		return apply_filters( 'woocommerce_product_gallery_attachment_ids', array_filter( (array) explode( ',', $this->product_image_gallery ) ), $this );
187
	}
188
189
	/**
190
	 * Wrapper for get_permalink.
191
	 *
192
	 * @return string
193
	 */
194
	public function get_permalink() {
195
		return get_permalink( $this->id );
196
	}
197
198
	/**
199
	 * Get SKU (Stock-keeping unit) - product unique ID.
200
	 *
201
	 * @return string
202
	 */
203
	public function get_sku() {
204
		return apply_filters( 'woocommerce_get_sku', $this->sku, $this );
205
	}
206
207
	/**
208
	 * Returns number of items available for sale.
209
	 *
210
	 * @return int
211
	 */
212
	public function get_stock_quantity() {
213
		return apply_filters( 'woocommerce_get_stock_quantity', $this->managing_stock() ? wc_stock_amount( $this->stock ) : null, $this );
214
	}
215
216
	/**
217
	 * Get total stock.
218
	 *
219
	 * This is the stock of parent and children combined.
220
	 *
221
	 * @return int
222
	 */
223
	public function get_total_stock() {
224
		if ( empty( $this->total_stock ) ) {
225
			$this->total_stock = max( 0, $this->get_stock_quantity() );
226
227
			if ( sizeof( $this->get_children() ) > 0 ) {
228
				foreach ( $this->get_children() as $child_id ) {
229
					if ( 'yes' === get_post_meta( $child_id, '_manage_stock', true ) ) {
230
						$stock = get_post_meta( $child_id, '_stock', true );
231
						$this->total_stock += max( 0, wc_stock_amount( $stock ) );
232
					}
233
				}
234
			}
235
		}
236
		return wc_stock_amount( $this->total_stock );
237
	}
238
239
	/**
240
	 * Check if the stock status needs changing.
241
	 */
242
	public function check_stock_status() {
243
		if ( ! $this->backorders_allowed() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
244
			if ( $this->stock_status !== 'outofstock' ) {
245
				$this->set_stock_status( 'outofstock' );
246
			}
247
		} elseif ( $this->backorders_allowed() || $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) {
248
			if ( $this->stock_status !== 'instock' ) {
249
				$this->set_stock_status( 'instock' );
250
			}
251
		}
252
	}
253
254
	/**
255
	 * Set stock level of the product.
256
	 *
257
	 * Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues).
258
	 * We cannot rely on the original loaded value in case another order was made since then.
259
	 *
260
	 * @param int $amount (default: null)
261
	 * @param string $mode can be set, add, or subtract
262
	 * @return int new stock level
263
	 */
264
	public function set_stock( $amount = null, $mode = 'set' ) {
265
		global $wpdb;
266
267
		if ( ! is_null( $amount ) && $this->managing_stock() ) {
268
269
			// Ensure key exists
270
			add_post_meta( $this->id, '_stock', 0, true );
271
272
			// Update stock in DB directly
273 View Code Duplication
			switch ( $mode ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
274
				case 'add' :
275
					$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
276
				break;
277
				case 'subtract' :
278
					$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
279
				break;
280
				default :
281
					$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", $amount, $this->id ) );
282
				break;
283
			}
284
285
			// Clear caches
286
			wp_cache_delete( $this->id, 'post_meta' );
287
			delete_transient( 'wc_low_stock_count' );
288
			delete_transient( 'wc_outofstock_count' );
289
			unset( $this->stock );
290
291
			// Stock status
292
			$this->check_stock_status();
293
294
			// Trigger action
295
			do_action( 'woocommerce_product_set_stock', $this );
296
		}
297
298
		return $this->get_stock_quantity();
299
	}
300
301
	/**
302
	 * Reduce stock level of the product.
303
	 *
304
	 * @param int $amount Amount to reduce by. Default: 1
305
	 * @return int new stock level
306
	 */
307
	public function reduce_stock( $amount = 1 ) {
308
		return $this->set_stock( $amount, 'subtract' );
309
	}
310
311
	/**
312
	 * Increase stock level of the product.
313
	 *
314
	 * @param int $amount Amount to increase by. Default 1.
315
	 * @return int new stock level
316
	 */
317
	public function increase_stock( $amount = 1 ) {
318
		return $this->set_stock( $amount, 'add' );
319
	}
320
321
	/**
322
	 * set_stock_status function.
323
	 *
324
	 * @param string $status
325
	 */
326
	public function set_stock_status( $status ) {
327
328
		$status = ( 'outofstock' === $status ) ? 'outofstock' : 'instock';
329
330
		// Sanity check
331 View Code Duplication
		if ( $this->managing_stock() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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
			if ( ! $this->backorders_allowed() && $this->get_stock_quantity() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
333
				$status = 'outofstock';
334
			}
335
		}
336
337
		if ( update_post_meta( $this->id, '_stock_status', $status ) ) {
338
			$this->stock_status = $status;
339
			do_action( 'woocommerce_product_set_stock_status', $this->id, $status );
340
		}
341
	}
342
343
	/**
344
	 * Return the product type.
345
	 *
346
	 * @return string
347
	 */
348
	public function get_type() {
349
		return is_null( $this->product_type ) ? '' : $this->product_type;
350
	}
351
352
	/**
353
	 * Checks the product type.
354
	 *
355
	 * Backwards compat with downloadable/virtual.
356
	 *
357
	 * @param string $type Array or string of types
358
	 * @return bool
359
	 */
360
	public function is_type( $type ) {
361
		return ( $this->product_type == $type || ( is_array( $type ) && in_array( $this->product_type, $type ) ) ) ? true : false;
362
	}
363
364
	/**
365
	 * Checks if a product is downloadable.
366
	 *
367
	 * @return bool
368
	 */
369
	public function is_downloadable() {
370
		return $this->downloadable == 'yes' ? true : false;
371
	}
372
373
	/**
374
	 * Check if downloadable product has a file attached.
375
	 *
376
	 * @since 1.6.2
377
	 *
378
	 * @param string $download_id file identifier
379
	 * @return bool Whether downloadable product has a file attached.
380
	 */
381
	public function has_file( $download_id = '' ) {
382
		return ( $this->is_downloadable() && $this->get_file( $download_id ) ) ? true : false;
383
	}
384
385
	/**
386
	 * Gets an array of downloadable files for this product.
387
	 *
388
	 * @since 2.1.0
389
	 *
390
	 * @return array
391
	 */
392
	public function get_files() {
393
394
		$downloadable_files = array_filter( isset( $this->downloadable_files ) ? (array) maybe_unserialize( $this->downloadable_files ) : array() );
395
396
		if ( ! empty( $downloadable_files ) ) {
397
398
			foreach ( $downloadable_files as $key => $file ) {
399
400
				if ( ! is_array( $file ) ) {
401
					$downloadable_files[ $key ] = array(
402
						'file' => $file,
403
						'name' => ''
404
					);
405
				}
406
407
				// Set default name
408
				if ( empty( $file['name'] ) ) {
409
					$downloadable_files[ $key ]['name'] = wc_get_filename_from_url( $file['file'] );
410
				}
411
412
				// Filter URL
413
				$downloadable_files[ $key ]['file'] = apply_filters( 'woocommerce_file_download_path', $downloadable_files[ $key ]['file'], $this, $key );
414
			}
415
		}
416
417
		return apply_filters( 'woocommerce_product_files', $downloadable_files, $this );
418
	}
419
420
	/**
421
	 * Get a file by $download_id.
422
	 *
423
	 * @param string $download_id file identifier
424
	 * @return array|false if not found
425
	 */
426
	public function get_file( $download_id = '' ) {
427
428
		$files = $this->get_files();
429
430
		if ( '' === $download_id ) {
431
			$file = sizeof( $files ) ? current( $files ) : false;
432
		} elseif ( isset( $files[ $download_id ] ) ) {
433
			$file = $files[ $download_id ];
434
		} else {
435
			$file = false;
436
		}
437
438
		// allow overriding based on the particular file being requested
439
		return apply_filters( 'woocommerce_product_file', $file, $this, $download_id );
440
	}
441
442
	/**
443
	 * Get file download path identified by $download_id.
444
	 *
445
	 * @param string $download_id file identifier
446
	 * @return string
447
	 */
448
	public function get_file_download_path( $download_id ) {
449
		$files = $this->get_files();
450
451
		if ( isset( $files[ $download_id ] ) ) {
452
			$file_path = $files[ $download_id ]['file'];
453
		} else {
454
			$file_path = '';
455
		}
456
457
		// allow overriding based on the particular file being requested
458
		return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id );
459
	}
460
461
	/**
462
	 * Checks if a product is virtual (has no shipping).
463
	 *
464
	 * @return bool
465
	 */
466
	public function is_virtual() {
467
		return apply_filters( 'woocommerce_is_virtual', $this->virtual == 'yes' ? true : false, $this );
468
	}
469
470
	/**
471
	 * Checks if a product needs shipping.
472
	 *
473
	 * @return bool
474
	 */
475
	public function needs_shipping() {
476
		return apply_filters( 'woocommerce_product_needs_shipping', $this->is_virtual() ? false : true, $this );
477
	}
478
479
	/**
480
	 * Check if a product is sold individually (no quantities).
481
	 *
482
	 * @return bool
483
	 */
484
	public function is_sold_individually() {
485
486
		$return = false;
487
488
		if ( 'yes' == $this->sold_individually ) {
489
			$return = true;
490
		}
491
492
		return apply_filters( 'woocommerce_is_sold_individually', $return, $this );
493
	}
494
495
	/**
496
	 * get_child function.
497
	 *
498
	 * @param mixed $child_id
499
	 * @return WC_Product WC_Product or WC_Product_variation
500
	 */
501
	public function get_child( $child_id ) {
502
		return wc_get_product( $child_id );
503
	}
504
505
	/**
506
	 * get_children function.
507
	 *
508
	 * @return array
509
	 */
510
	public function get_children() {
511
		return array();
512
	}
513
514
	/**
515
	 * Returns whether or not the product has any child product.
516
	 *
517
	 * @return bool
518
	 */
519
	public function has_child() {
520
		return false;
521
	}
522
523
	/**
524
	 * Returns whether or not the product post exists.
525
	 *
526
	 * @return bool
527
	 */
528
	public function exists() {
529
		return empty( $this->post ) ? false : true;
530
	}
531
532
	/**
533
	 * Returns whether or not the product is taxable.
534
	 *
535
	 * @return bool
536
	 */
537
	public function is_taxable() {
538
		$taxable = $this->get_tax_status() === 'taxable' && wc_tax_enabled() ? true : false;
539
		return apply_filters( 'woocommerce_product_is_taxable', $taxable, $this );
540
	}
541
542
	/**
543
	 * Returns whether or not the product shipping is taxable.
544
	 *
545
	 * @return bool
546
	 */
547
	public function is_shipping_taxable() {
548
		return $this->get_tax_status() === 'taxable' || $this->get_tax_status() === 'shipping' ? true : false;
549
	}
550
551
	/**
552
	 * Get the title of the post.
553
	 *
554
	 * @return string
555
	 */
556
	public function get_title() {
557
		return apply_filters( 'woocommerce_product_title', $this->post ? $this->post->post_title : '', $this );
558
	}
559
560
	/**
561
	 * Get the parent of the post.
562
	 *
563
	 * @return int
564
	 */
565
	public function get_parent() {
566
		return apply_filters( 'woocommerce_product_parent', absint( $this->post->post_parent ), $this );
567
	}
568
569
	/**
570
	 * Get the add to url used mainly in loops.
571
	 *
572
	 * @return string
573
	 */
574
	public function add_to_cart_url() {
575
		return apply_filters( 'woocommerce_product_add_to_cart_url', get_permalink( $this->id ), $this );
576
	}
577
578
	/**
579
	 * Get the add to cart button text for the single page.
580
	 *
581
	 * @return string
582
	 */
583
	public function single_add_to_cart_text() {
584
		return apply_filters( 'woocommerce_product_single_add_to_cart_text', __( 'Add to cart', 'woocommerce' ), $this );
585
	}
586
587
	/**
588
	 * Get the add to cart button text.
589
	 *
590
	 * @return string
591
	 */
592
	public function add_to_cart_text() {
593
		return apply_filters( 'woocommerce_product_add_to_cart_text', __( 'Read more', 'woocommerce' ), $this );
594
	}
595
596
	/**
597
	 * Returns whether or not the product is stock managed.
598
	 *
599
	 * @return bool
600
	 */
601
	public function managing_stock() {
602
		return ( ! isset( $this->manage_stock ) || $this->manage_stock == 'no' || get_option( 'woocommerce_manage_stock' ) !== 'yes' ) ? false : true;
603
	}
604
605
	/**
606
	 * Returns whether or not the product is in stock.
607
	 *
608
	 * @return bool
609
	 */
610
	public function is_in_stock() {
611
		if ( $this->managing_stock() && $this->backorders_allowed() ) {
612
			return true;
613
		} elseif ( $this->managing_stock() && $this->get_total_stock() <= get_option( 'woocommerce_notify_no_stock_amount' ) ) {
614
			return false;
615
		} else {
616
			return $this->stock_status === 'instock';
617
		}
618
	}
619
620
	/**
621
	 * Returns whether or not the product can be backordered.
622
	 *
623
	 * @return bool
624
	 */
625
	public function backorders_allowed() {
626
		return apply_filters( 'woocommerce_product_backorders_allowed', $this->backorders === 'yes' || $this->backorders === 'notify' ? true : false, $this->id );
627
	}
628
629
	/**
630
	 * Returns whether or not the product needs to notify the customer on backorder.
631
	 *
632
	 * @return bool
633
	 */
634
	public function backorders_require_notification() {
635
		return $this->managing_stock() && $this->backorders === 'notify' ? true : false;
636
	}
637
638
	/**
639
	 * Check if a product is on backorder.
640
	 *
641
	 * @param int $qty_in_cart (default: 0)
642
	 * @return bool
643
	 */
644
	public function is_on_backorder( $qty_in_cart = 0 ) {
645
		return $this->managing_stock() && $this->backorders_allowed() && ( $this->get_total_stock() - $qty_in_cart ) < 0 ? true : false;
646
	}
647
648
	/**
649
	 * Returns whether or not the product has enough stock for the order.
650
	 *
651
	 * @param mixed $quantity
652
	 * @return bool
653
	 */
654
	public function has_enough_stock( $quantity ) {
655
		return ! $this->managing_stock() || $this->backorders_allowed() || $this->get_stock_quantity() >= $quantity ? true : false;
656
	}
657
658
	/**
659
	 * Returns the availability of the product.
660
	 *
661
	 * @return string
662
	 */
663 View Code Duplication
	public function get_availability() {
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...
664
		$availability = $class = '';
665
666
		if ( $this->managing_stock() ) {
667
668
			if ( $this->is_in_stock() && $this->get_total_stock() > get_option( 'woocommerce_notify_no_stock_amount' ) ) {
669
670
				switch ( get_option( 'woocommerce_stock_format' ) ) {
671
672
					case 'no_amount' :
673
						$availability = __( 'In stock', 'woocommerce' );
674
					break;
675
676
					case 'low_amount' :
677
						if ( $this->get_total_stock() <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
678
							$availability = sprintf( __( 'Only %s left in stock', 'woocommerce' ), $this->get_total_stock() );
679
680
							if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
681
								$availability .= ' ' . __( '(can be backordered)', 'woocommerce' );
682
							}
683
						} else {
684
							$availability = __( 'In stock', 'woocommerce' );
685
						}
686
					break;
687
688
					default :
689
						$availability = sprintf( __( '%s in stock', 'woocommerce' ), $this->get_total_stock() );
690
691
						if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
692
							$availability .= ' ' . __( '(can be backordered)', 'woocommerce' );
693
						}
694
					break;
695
				}
696
697
				$class        = 'in-stock';
698
699
			} elseif ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
700
701
				$availability = __( 'Available on backorder', 'woocommerce' );
702
				$class        = 'available-on-backorder';
703
704
			} elseif ( $this->backorders_allowed() ) {
705
706
				$availability = __( 'In stock', 'woocommerce' );
707
				$class        = 'in-stock';
708
709
			} else {
710
711
				$availability = __( 'Out of stock', 'woocommerce' );
712
				$class        = 'out-of-stock';
713
			}
714
715
		} elseif ( ! $this->is_in_stock() ) {
716
717
			$availability = __( 'Out of stock', 'woocommerce' );
718
			$class        = 'out-of-stock';
719
		}
720
721
		return apply_filters( 'woocommerce_get_availability', array( 'availability' => $availability, 'class' => $class ), $this );
722
	}
723
724
	/**
725
	 * Returns whether or not the product is featured.
726
	 *
727
	 * @return bool
728
	 */
729
	public function is_featured() {
730
		return $this->featured === 'yes' ? true : false;
731
	}
732
733
	/**
734
	 * Returns whether or not the product is visible in the catalog.
735
	 *
736
	 * @return bool
737
	 */
738
	public function is_visible() {
739
		if ( ! $this->post ) {
740
			$visible = false;
741
742
		// Published/private
743 View Code Duplication
		} elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
744
			$visible = false;
745
746
		// Out of stock visibility
747
		} elseif ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $this->is_in_stock() ) {
748
			$visible = false;
749
750
		// visibility setting
751
		} elseif ( 'hidden' === $this->visibility ) {
752
			$visible = false;
753
		} elseif ( 'visible' === $this->visibility ) {
754
			$visible = true;
755
756
		// Visibility in loop
757
		} elseif ( is_search() ) {
758
			$visible = 'search' === $this->visibility;
759
		} else {
760
			$visible = 'catalog' === $this->visibility;
761
		}
762
763
		return apply_filters( 'woocommerce_product_is_visible', $visible, $this->id );
764
	}
765
766
	/**
767
	 * Returns whether or not the product is on sale.
768
	 *
769
	 * @return bool
770
	 */
771
	public function is_on_sale() {
772
		return apply_filters( 'woocommerce_product_is_on_sale', ( $this->get_sale_price() !== $this->get_regular_price() && $this->get_sale_price() === $this->get_price() ), $this );
773
	}
774
775
	/**
776
	 * Returns false if the product cannot be bought.
777
	 *
778
	 * @return bool
779
	 */
780
	public function is_purchasable() {
781
782
		$purchasable = true;
783
784
		// Products must exist of course
785
		if ( ! $this->exists() ) {
786
			$purchasable = false;
787
788
		// Other products types need a price to be set
789
		} elseif ( $this->get_price() === '' ) {
790
			$purchasable = false;
791
792
		// Check the product is published
793 View Code Duplication
		} elseif ( $this->post->post_status !== 'publish' && ! current_user_can( 'edit_post', $this->id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
794
			$purchasable = false;
795
		}
796
797
		return apply_filters( 'woocommerce_is_purchasable', $purchasable, $this );
798
	}
799
800
	/**
801
	 * Set a products price dynamically.
802
	 *
803
	 * @param float $price Price to set.
804
	 */
805
	public function set_price( $price ) {
806
		$this->price = $price;
807
	}
808
809
	/**
810
	 * Adjust a products price dynamically.
811
	 *
812
	 * @param mixed $price
813
	 */
814
	public function adjust_price( $price ) {
815
		$this->price = $this->price + $price;
816
	}
817
818
	/**
819
	 * Returns the product's sale price.
820
	 *
821
	 * @return string price
822
	 */
823
	public function get_sale_price() {
824
		return apply_filters( 'woocommerce_get_sale_price', $this->sale_price, $this );
825
	}
826
827
	/**
828
	 * Returns the product's regular price.
829
	 *
830
	 * @return string price
831
	 */
832
	public function get_regular_price() {
833
		return apply_filters( 'woocommerce_get_regular_price', $this->regular_price, $this );
834
	}
835
836
	/**
837
	 * Returns the product's active price.
838
	 *
839
	 * @return string price
840
	 */
841
	public function get_price() {
842
		return apply_filters( 'woocommerce_get_price', $this->price, $this );
843
	}
844
845
	/**
846
	 * Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes.
847
	 *
848
	 * @param  string $price to calculate, left blank to just use get_price()
849
	 * @return string
850
	 */
851
	public function get_price_including_tax( $qty = 1, $price = '' ) {
852
853
		if ( $price === '' ) {
854
			$price = $this->get_price();
855
		}
856
857
		if ( $this->is_taxable() ) {
858
859
			if ( get_option( 'woocommerce_prices_include_tax' ) === 'no' ) {
860
861
				$tax_rates  = WC_Tax::get_rates( $this->get_tax_class() );
862
				$taxes      = WC_Tax::calc_tax( $price * $qty, $tax_rates, false );
863
				$tax_amount = WC_Tax::get_tax_total( $taxes );
864
				$price      = round( $price * $qty + $tax_amount, wc_get_price_decimals() );
865
866
			} else {
867
868
				$tax_rates      = WC_Tax::get_rates( $this->get_tax_class() );
869
				$base_tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class );
870
871
				if ( ! empty( WC()->customer ) && WC()->customer->is_vat_exempt() ) {
872
873
					$base_taxes         = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
874
					$base_tax_amount    = array_sum( $base_taxes );
875
					$price              = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() );
876
877
				/**
878
				 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
879
				 * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
880
				 * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
881
				 */
882
				} elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
883
884
					$base_taxes         = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true );
885
					$modded_taxes       = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false );
886
					$price              = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() );
887
888
				} else {
889
890
					$price = $price * $qty;
891
892
				}
893
894
			}
895
896
		} else {
897
			$price = $price * $qty;
898
		}
899
900
		return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $this );
901
	}
902
903
	/**
904
	 * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting.
905
	 * Uses store base tax rates. Can work for a specific $qty for more accurate taxes.
906
	 *
907
	 * @param  string $price to calculate, left blank to just use get_price()
908
	 * @return string
909
	 */
910
	public function get_price_excluding_tax( $qty = 1, $price = '' ) {
911
912
		if ( $price === '' ) {
913
			$price = $this->get_price();
914
		}
915
916
		if ( $this->is_taxable() && 'yes' === get_option( 'woocommerce_prices_include_tax' ) ) {
917
			$tax_rates  = WC_Tax::get_base_tax_rates( $this->tax_class );
918
			$taxes      = WC_Tax::calc_tax( $price * $qty, $tax_rates, true );
919
			$price      = WC_Tax::round( $price * $qty - array_sum( $taxes ) );
920
		} else {
921
			$price = $price * $qty;
922
		}
923
924
		return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $this );
925
	}
926
927
	/**
928
	 * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting.
929
	 *
930
	 * @param  string  $price to calculate, left blank to just use get_price()
931
	 * @param  integer $qty   passed on to get_price_including_tax() or get_price_excluding_tax()
932
	 * @return string
933
	 */
934
	public function get_display_price( $price = '', $qty = 1 ) {
935
936
		if ( $price === '' ) {
937
			$price = $this->get_price();
938
		}
939
940
		$tax_display_mode = get_option( 'woocommerce_tax_display_shop' );
941
		$display_price    = $tax_display_mode == 'incl' ? $this->get_price_including_tax( $qty, $price ) : $this->get_price_excluding_tax( $qty, $price );
942
943
		return $display_price;
944
	}
945
946
	/**
947
	 * Get the suffix to display after prices > 0.
948
	 *
949
	 * @param  string  $price to calculate, left blank to just use get_price()
950
	 * @param  integer $qty   passed on to get_price_including_tax() or get_price_excluding_tax()
951
	 * @return string
952
	 */
953
	public function get_price_suffix( $price = '', $qty = 1 ) {
954
955
		if ( $price === '' ) {
956
			$price = $this->get_price();
957
		}
958
959
		$price_display_suffix  = get_option( 'woocommerce_price_display_suffix' );
960
961
		if ( $price_display_suffix ) {
962
963
			$price_display_suffix = ' <small class="woocommerce-price-suffix">' . $price_display_suffix . '</small>';
964
965
			$find = array(
966
				'{price_including_tax}',
967
				'{price_excluding_tax}'
968
			);
969
970
			$replace = array(
971
				wc_price( $this->get_price_including_tax( $qty, $price ) ),
972
				wc_price( $this->get_price_excluding_tax( $qty, $price ) )
973
			);
974
975
			$price_display_suffix = str_replace( $find, $replace, $price_display_suffix );
976
		}
977
978
		return apply_filters( 'woocommerce_get_price_suffix', $price_display_suffix, $this );
979
	}
980
981
	/**
982
	 * Returns the price in html format.
983
	 *
984
	 * @param string $price (default: '')
985
	 * @return string
986
	 */
987
	public function get_price_html( $price = '' ) {
988
989
		$display_price         = $this->get_display_price();
990
		$display_regular_price = $this->get_display_price( $this->get_regular_price() );
991
992
		if ( $this->get_price() > 0 ) {
993
994
			if ( $this->is_on_sale() && $this->get_regular_price() ) {
995
996
				$price .= $this->get_price_html_from_to( $display_regular_price, $display_price ) . $this->get_price_suffix();
997
998
				$price = apply_filters( 'woocommerce_sale_price_html', $price, $this );
999
1000
			} else {
1001
1002
				$price .= wc_price( $display_price ) . $this->get_price_suffix();
1003
1004
				$price = apply_filters( 'woocommerce_price_html', $price, $this );
1005
1006
			}
1007
1008
		} elseif ( $this->get_price() === '' ) {
1009
1010
			$price = apply_filters( 'woocommerce_empty_price_html', '', $this );
1011
1012
		} elseif ( $this->get_price() == 0 ) {
1013
1014
			if ( $this->is_on_sale() && $this->get_regular_price() ) {
1015
1016
				$price .= $this->get_price_html_from_to( $display_regular_price, __( 'Free!', 'woocommerce' ) );
1017
1018
				$price = apply_filters( 'woocommerce_free_sale_price_html', $price, $this );
1019
1020
			} else {
1021
1022
				$price = '<span class="amount">' . __( 'Free!', 'woocommerce' ) . '</span>';
1023
1024
				$price = apply_filters( 'woocommerce_free_price_html', $price, $this );
1025
1026
			}
1027
		}
1028
1029
		return apply_filters( 'woocommerce_get_price_html', $price, $this );
1030
	}
1031
1032
	/**
1033
	 * Functions for getting parts of a price, in html, used by get_price_html.
1034
	 *
1035
	 * @return string
1036
	 */
1037
	public function get_price_html_from_text() {
1038
		$from = '<span class="from">' . _x( 'From:', 'min_price', 'woocommerce' ) . ' </span>';
1039
1040
		return apply_filters( 'woocommerce_get_price_html_from_text', $from, $this );
1041
	}
1042
1043
	/**
1044
	 * Functions for getting parts of a price, in html, used by get_price_html.
1045
	 *
1046
	 * @param  string $from String or float to wrap with 'from' text
1047
	 * @param  mixed $to String or float to wrap with 'to' text
1048
	 * @return string
1049
	 */
1050
	public function get_price_html_from_to( $from, $to ) {
1051
		$price = '<del>' . ( ( is_numeric( $from ) ) ? wc_price( $from ) : $from ) . '</del> <ins>' . ( ( is_numeric( $to ) ) ? wc_price( $to ) : $to ) . '</ins>';
1052
1053
		return apply_filters( 'woocommerce_get_price_html_from_to', $price, $from, $to, $this );
1054
	}
1055
1056
	/**
1057
	 * Returns the tax class.
1058
	 *
1059
	 * @return string
1060
	 */
1061
	public function get_tax_class() {
1062
		return apply_filters( 'woocommerce_product_tax_class', $this->tax_class, $this );
1063
	}
1064
1065
	/**
1066
	 * Returns the tax status.
1067
	 *
1068
	 * @return string
1069
	 */
1070
	public function get_tax_status() {
1071
		return $this->tax_status;
1072
	}
1073
1074
	/**
1075
	 * Get the average rating of product. This is calculated once and stored in postmeta.
1076
	 * @return string
1077
	 */
1078
	public function get_average_rating() {
1079
		global $wpdb;
1080
1081
		// No meta date? Do the calculation
1082
		if ( ! metadata_exists( 'post', $this->id, '_wc_average_rating' ) ) {
1083
			if ( $count = $this->get_rating_count() ) {
1084
				$ratings = $wpdb->get_var( $wpdb->prepare("
1085
					SELECT SUM(meta_value) FROM $wpdb->commentmeta
1086
					LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
1087
					WHERE meta_key = 'rating'
1088
					AND comment_post_ID = %d
1089
					AND comment_approved = '1'
1090
					AND meta_value > 0
1091
				", $this->id ) );
1092
				$average = number_format( $ratings / $count, 2, '.', '' );
1093
			} else {
1094
				$average = 0;
1095
			}
1096
			update_post_meta( $this->id, '_wc_average_rating', $average );
1097
		} else {
1098
			$average = get_post_meta( $this->id, '_wc_average_rating', true );
1099
		}
1100
1101
		return (string) floatval( $average );
1102
	}
1103
1104
	/**
1105
	 * Get the total amount (COUNT) of ratings.
1106
	 * @param  int $value Optional. Rating value to get the count for. By default returns the count of all rating values.
1107
	 * @return int
1108
	 */
1109
	public function get_rating_count( $value = null ) {
1110
		global $wpdb;
1111
1112
		// No meta date? Do the calculation
1113
		if ( ! metadata_exists( 'post', $this->id, '_wc_rating_count' ) ) {
1114
			$counts     = array();
1115
			$raw_counts = $wpdb->get_results( $wpdb->prepare("
1116
				SELECT meta_value, COUNT( * ) as meta_value_count FROM $wpdb->commentmeta
1117
				LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
1118
				WHERE meta_key = 'rating'
1119
				AND comment_post_ID = %d
1120
				AND comment_approved = '1'
1121
				AND meta_value > 0
1122
				GROUP BY meta_value
1123
			", $this->id ) );
1124
1125
			foreach ( $raw_counts as $count ) {
1126
				$counts[ $count->meta_value ] = $count->meta_value_count;
1127
			}
1128
1129
			update_post_meta( $this->id, '_wc_rating_count', $counts );
1130
		} else {
1131
			$counts = get_post_meta( $this->id, '_wc_rating_count', true );
1132
		}
1133
1134
		if ( is_null( $value ) ) {
1135
			return array_sum( $counts );
1136
		} else {
1137
			return isset( $counts[ $value ] ) ? $counts[ $value ] : 0;
1138
		}
1139
	}
1140
1141
	/**
1142
	 * Returns the product rating in html format.
1143
	 *
1144
	 * @param string $rating (default: '')
1145
	 *
1146
	 * @return string
1147
	 */
1148
	public function get_rating_html( $rating = null ) {
1149
		$rating_html = '';
1150
1151
		if ( ! is_numeric( $rating ) ) {
1152
			$rating = $this->get_average_rating();
1153
		}
1154
1155
		if ( $rating > 0 ) {
1156
1157
			$rating_html  = '<div class="star-rating" title="' . sprintf( __( 'Rated %s out of 5', 'woocommerce' ), $rating ) . '">';
1158
1159
			$rating_html .= '<span style="width:' . ( ( $rating / 5 ) * 100 ) . '%"><strong class="rating">' . $rating . '</strong> ' . __( 'out of 5', 'woocommerce' ) . '</span>';
1160
1161
			$rating_html .= '</div>';
1162
		}
1163
1164
		return apply_filters( 'woocommerce_product_get_rating_html', $rating_html, $rating );
1165
	}
1166
1167
	/**
1168
	 * Get the total amount (COUNT) of reviews.
1169
	 *
1170
	 * @since 2.3.2
1171
	 * @return int The total numver of product reviews
1172
	 */
1173
	public function get_review_count() {
1174
		global $wpdb;
1175
1176
		// No meta date? Do the calculation
1177
		if ( ! metadata_exists( 'post', $this->id, '_wc_review_count' ) ) {
1178
			$count = $wpdb->get_var( $wpdb->prepare("
1179
				SELECT COUNT(*) FROM $wpdb->comments
1180
				WHERE comment_parent = 0
1181
				AND comment_post_ID = %d
1182
				AND comment_approved = '1'
1183
			", $this->id ) );
1184
1185
			update_post_meta( $this->id, '_wc_review_count', $count );
1186
		} else {
1187
			$count = get_post_meta( $this->id, '_wc_review_count', true );
1188
		}
1189
1190
		return apply_filters( 'woocommerce_product_review_count', $count, $this );
1191
	}
1192
1193
	/**
1194
	 * Returns the upsell product ids.
1195
	 *
1196
	 * @return array
1197
	 */
1198
	public function get_upsells() {
1199
		return apply_filters( 'woocommerce_product_upsell_ids', (array) maybe_unserialize( $this->upsell_ids ), $this );
1200
	}
1201
1202
	/**
1203
	 * Returns the cross sell product ids.
1204
	 *
1205
	 * @return array
1206
	 */
1207
	public function get_cross_sells() {
1208
		return apply_filters( 'woocommerce_product_crosssell_ids', (array) maybe_unserialize( $this->crosssell_ids ), $this );
1209
	}
1210
1211
	/**
1212
	 * Returns the product categories.
1213
	 *
1214
	 * @param string $sep (default: ', ')
1215
	 * @param string $before (default: '')
1216
	 * @param string $after (default: '')
1217
	 * @return string
1218
	 */
1219
	public function get_categories( $sep = ', ', $before = '', $after = '' ) {
1220
		return get_the_term_list( $this->id, 'product_cat', $before, $sep, $after );
1221
	}
1222
1223
	/**
1224
	 * Returns the product tags.
1225
	 *
1226
	 * @param string $sep (default: ', ')
1227
	 * @param string $before (default: '')
1228
	 * @param string $after (default: '')
1229
	 * @return array
1230
	 */
1231
	public function get_tags( $sep = ', ', $before = '', $after = '' ) {
1232
		return get_the_term_list( $this->id, 'product_tag', $before, $sep, $after );
1233
	}
1234
1235
	/**
1236
	 * Returns the product shipping class.
1237
	 *
1238
	 * @return string
1239
	 */
1240 View Code Duplication
	public function get_shipping_class() {
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...
1241
1242
		if ( ! $this->shipping_class ) {
1243
1244
			$classes = get_the_terms( $this->id, 'product_shipping_class' );
1245
1246
			if ( $classes && ! is_wp_error( $classes ) ) {
1247
				$this->shipping_class = current( $classes )->slug;
1248
			} else {
1249
				$this->shipping_class = '';
1250
			}
1251
1252
		}
1253
1254
		return $this->shipping_class;
1255
	}
1256
1257
	/**
1258
	 * Returns the product shipping class ID.
1259
	 *
1260
	 * @return int
1261
	 */
1262 View Code Duplication
	public function get_shipping_class_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...
1263
1264
		if ( ! $this->shipping_class_id ) {
1265
1266
			$classes = get_the_terms( $this->id, 'product_shipping_class' );
1267
1268
			if ( $classes && ! is_wp_error( $classes ) ) {
1269
				$this->shipping_class_id = current( $classes )->term_id;
1270
			} else {
1271
				$this->shipping_class_id = 0;
1272
			}
1273
		}
1274
1275
		return absint( $this->shipping_class_id );
1276
	}
1277
1278
	/**
1279
	 * Get and return related products.
1280
	 *
1281
	 * Notes:
1282
	 * 	- Results are cached in a transient for faster queries.
1283
	 *  - To make results appear random, we query and extra 10 products and shuffle them.
1284
	 *  - To ensure we always have enough results, it will check $limit before returning the cached result, if not recalc.
1285
	 *  - This used to rely on transient version to invalidate cache, but to avoid multiple transients we now just expire daily.
1286
	 *  	This means if a related product is edited and no longer related, it won't be removed for 24 hours. Acceptable trade-off for performance.
1287
	 *  - Saving a product will flush caches for that product.
1288
	 *
1289
	 * @param int $limit (default: 5)
1290
	 * @return array Array of post IDs
1291
	 */
1292
	public function get_related( $limit = 5 ) {
1293
		global $wpdb;
1294
1295
		$transient_name = 'wc_related_' . $this->id;
1296
		$related_posts  = get_transient( $transient_name );
1297
1298
		// We want to query related posts if they are not cached, or we don't have enough
1299
		if ( false === $related_posts || sizeof( $related_posts ) < $limit ) {
1300
			// Related products are found from category and tag
1301
			$tags_array = $this->get_related_terms( 'product_tag' );
1302
			$cats_array = $this->get_related_terms( 'product_cat' );
1303
1304
			// Don't bother if none are set
1305
			if ( 1 === sizeof( $cats_array ) && 1 === sizeof( $tags_array )) {
1306
				$related_posts = array();
1307
			} else {
1308
				// Sanitize
1309
				$exclude_ids = array_map( 'absint', array_merge( array( 0, $this->id ), $this->get_upsells() ) );
1310
1311
				// Generate query - but query an extra 10 results to give the appearance of random results
1312
				$query = $this->build_related_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 );
1313
1314
				// Get the posts
1315
				$related_posts = $wpdb->get_col( implode( ' ', $query ) );
1316
			}
1317
1318
			set_transient( $transient_name, $related_posts, DAY_IN_SECONDS );
1319
		}
1320
1321
		// Randomise the results
1322
		shuffle( $related_posts );
1323
1324
		// Limit the returned results
1325
		return array_slice( $related_posts, 0, $limit );
1326
	}
1327
1328
	/**
1329
	 * Returns a single product attribute.
1330
	 *
1331
	 * @param mixed $attr
1332
	 * @return string
1333
	 */
1334
	public function get_attribute( $attr ) {
1335
1336
		$attributes = $this->get_attributes();
1337
1338
		$attr = sanitize_title( $attr );
1339
1340
		if ( isset( $attributes[ $attr ] ) || isset( $attributes[ 'pa_' . $attr ] ) ) {
1341
1342
			$attribute = isset( $attributes[ $attr ] ) ? $attributes[ $attr ] : $attributes[ 'pa_' . $attr ];
1343
1344
			if ( $attribute['is_taxonomy'] ) {
1345
1346
				return implode( ', ', wc_get_product_terms( $this->id, $attribute['name'], array( 'fields' => 'names' ) ) );
1347
1348
			} else {
1349
1350
				return $attribute['value'];
1351
			}
1352
1353
		}
1354
1355
		return '';
1356
	}
1357
1358
	/**
1359
	 * Returns product attributes.
1360
	 *
1361
	 * @return array
1362
	 */
1363
	public function get_attributes() {
1364
		$attributes = array_filter( (array) maybe_unserialize( $this->product_attributes ) );
1365
		$taxonomies = wp_list_pluck( wc_get_attribute_taxonomies(), 'attribute_name' );
1366
1367
		// Check for any attributes which have been removed globally
1368
		foreach ( $attributes as $key => $attribute ) {
1369
			if ( $attribute['is_taxonomy'] ) {
1370
				if ( ! in_array( substr( $attribute['name'], 3 ), $taxonomies ) ) {
1371
					unset( $attributes[ $key ] );
1372
				}
1373
			}
1374
		}
1375
1376
		return apply_filters( 'woocommerce_get_product_attributes', $attributes );
1377
	}
1378
1379
	/**
1380
	 * Returns whether or not the product has any attributes set.
1381
	 *
1382
	 * @return boolean
1383
	 */
1384
	public function has_attributes() {
1385
1386
		if ( sizeof( $this->get_attributes() ) > 0 ) {
1387
1388
			foreach ( $this->get_attributes() as $attribute ) {
1389
1390
				if ( isset( $attribute['is_visible'] ) && $attribute['is_visible'] ) {
1391
					return true;
1392
				}
1393
			}
1394
		}
1395
1396
		return false;
1397
	}
1398
1399
	/**
1400
	 * Returns whether or not we are showing dimensions on the product page.
1401
	 *
1402
	 * @return bool
1403
	 */
1404
	public function enable_dimensions_display() {
1405
		return apply_filters( 'wc_product_enable_dimensions_display', true );
1406
	}
1407
1408
	/**
1409
	 * Returns whether or not the product has dimensions set.
1410
	 *
1411
	 * @return bool
1412
	 */
1413
	public function has_dimensions() {
1414
		return $this->get_dimensions() ? true : false;
1415
	}
1416
1417
	/**
1418
	 * Returns the product length.
1419
	 * @return string
1420
	 */
1421
	public function get_length() {
1422
		return apply_filters( 'woocommerce_product_length', $this->length ? $this->length : '', $this );
1423
	}
1424
1425
	/**
1426
	 * Returns the product width.
1427
	 * @return string
1428
	 */
1429
	public function get_width() {
1430
		return apply_filters( 'woocommerce_product_width', $this->width ? $this->width : '', $this );
1431
	}
1432
1433
	/**
1434
	 * Returns the product height.
1435
	 * @return string
1436
	 */
1437
	public function get_height() {
1438
		return apply_filters( 'woocommerce_product_height', $this->height ? $this->height : '', $this );
1439
	}
1440
1441
	/**
1442
	 * Returns the product's weight.
1443
	 * @todo   refactor filters in this class to naming woocommerce_product_METHOD
1444
	 * @return string
1445
	 */
1446
	public function get_weight() {
1447
		return apply_filters( 'woocommerce_product_weight', apply_filters( 'woocommerce_product_get_weight', $this->weight ? $this->weight : '' ), $this );
1448
	}
1449
1450
	/**
1451
	 * Returns whether or not the product has weight set.
1452
	 *
1453
	 * @return bool
1454
	 */
1455
	public function has_weight() {
1456
		return $this->get_weight() ? true : false;
1457
	}
1458
1459
	/**
1460
	 * Returns formatted dimensions.
1461
	 * @return string
1462
	 */
1463
	public function get_dimensions() {
1464
		$dimensions = implode( ' x ', array_filter( array(
1465
			$this->get_length(),
1466
			$this->get_width(),
1467
			$this->get_height(),
1468
		) ) );
1469
1470
		if ( ! empty( $dimensions ) ) {
1471
			$dimensions .= ' ' . get_option( 'woocommerce_dimension_unit' );
1472
		}
1473
1474
		return  apply_filters( 'woocommerce_product_dimensions', $dimensions, $this );
1475
	}
1476
1477
	/**
1478
	 * Lists a table of attributes for the product page.
1479
	 */
1480
	public function list_attributes() {
1481
		wc_get_template( 'single-product/product-attributes.php', array(
1482
			'product'    => $this
1483
		) );
1484
	}
1485
1486
	/**
1487
	 * Gets the main product image ID.
1488
	 *
1489
	 * @return int
1490
	 */
1491
	public function get_image_id() {
1492
1493
		if ( has_post_thumbnail( $this->id ) ) {
1494
			$image_id = get_post_thumbnail_id( $this->id );
1495 View Code Duplication
		} elseif ( ( $parent_id = wp_get_post_parent_id( $this->id ) ) && has_post_thumbnail( $parent_id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1496
			$image_id = get_post_thumbnail_id( $parent_id );
1497
		} else {
1498
			$image_id = 0;
1499
		}
1500
1501
		return $image_id;
1502
	}
1503
1504
	/**
1505
	 * Returns the main product image.
1506
	 *
1507
	 * @param string $size (default: 'shop_thumbnail')
1508
	 * @return string
1509
	 */
1510
	public function get_image( $size = 'shop_thumbnail', $attr = array() ) {
1511
		if ( has_post_thumbnail( $this->id ) ) {
1512
			$image = get_the_post_thumbnail( $this->id, $size, $attr );
1513 View Code Duplication
		} elseif ( ( $parent_id = wp_get_post_parent_id( $this->id ) ) && has_post_thumbnail( $parent_id ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1514
			$image = get_the_post_thumbnail( $parent_id, $size, $attr );
1515
		} else {
1516
			$image = wc_placeholder_img( $size );
1517
		}
1518
1519
		return $image;
1520
	}
1521
1522
	/**
1523
	 * Get product name with SKU or ID. Used within admin.
1524
	 *
1525
	 * @return string Formatted product name
1526
	 */
1527
	public function get_formatted_name() {
1528
1529
		if ( $this->get_sku() ) {
1530
			$identifier = $this->get_sku();
1531
		} else {
1532
			$identifier = '#' . $this->id;
1533
		}
1534
1535
		return sprintf( __( '%s &ndash; %s', 'woocommerce' ), $identifier, $this->get_title() );
1536
	}
1537
1538
	/**
1539
	 * Retrieves related product terms.
1540
	 *
1541
	 * @param string $term
1542
	 * @return array
1543
	 */
1544
	protected function get_related_terms( $term ) {
1545
		$terms_array = array(0);
1546
1547
		$terms = apply_filters( 'woocommerce_get_related_' . $term . '_terms', wp_get_post_terms( $this->id, $term ), $this->id );
1548
		foreach ( $terms as $term ) {
1549
			$terms_array[] = $term->term_id;
1550
		}
1551
1552
		return array_map( 'absint', $terms_array );
1553
	}
1554
1555
	/**
1556
	 * Builds the related posts query.
1557
	 *
1558
	 * @param array $cats_array
1559
	 * @param array $tags_array
1560
	 * @param array $exclude_ids
1561
	 * @param int   $limit
1562
	 * @return string
1563
	 */
1564
	protected function build_related_query( $cats_array, $tags_array, $exclude_ids, $limit ) {
1565
		global $wpdb;
1566
1567
		$limit = absint( $limit );
1568
1569
		$query           = array();
1570
		$query['fields'] = "SELECT DISTINCT ID FROM {$wpdb->posts} p";
1571
		$query['join']   = " INNER JOIN {$wpdb->postmeta} pm ON ( pm.post_id = p.ID AND pm.meta_key='_visibility' )";
1572
		$query['join']  .= " INNER JOIN {$wpdb->term_relationships} tr ON (p.ID = tr.object_id)";
1573
		$query['join']  .= " INNER JOIN {$wpdb->term_taxonomy} tt ON (tr.term_taxonomy_id = tt.term_taxonomy_id)";
1574
		$query['join']  .= " INNER JOIN {$wpdb->terms} t ON (t.term_id = tt.term_id)";
1575
1576
		if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) {
1577
			$query['join'] .= " INNER JOIN {$wpdb->postmeta} pm2 ON ( pm2.post_id = p.ID AND pm2.meta_key='_stock_status' )";
1578
		}
1579
1580
		$query['where']  = " WHERE 1=1";
1581
		$query['where'] .= " AND p.post_status = 'publish'";
1582
		$query['where'] .= " AND p.post_type = 'product'";
1583
		$query['where'] .= " AND p.ID NOT IN ( " . implode( ',', $exclude_ids ) . " )";
1584
		$query['where'] .= " AND pm.meta_value IN ( 'visible', 'catalog' )";
1585
1586
		if ( get_option( 'woocommerce_hide_out_of_stock_items' ) === 'yes' ) {
1587
			$query['where'] .= " AND pm2.meta_value = 'instock'";
1588
		}
1589
1590
		if ( apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $this->id ) ) {
1591
			$query['where'] .= " AND ( tt.taxonomy = 'product_cat' AND t.term_id IN ( " . implode( ',', $cats_array ) . " ) )";
1592
			$andor = 'OR';
1593
		} else {
1594
			$andor = 'AND';
1595
		}
1596
1597
		// when query is OR - need to check against excluded ids again
1598
		if ( apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $this->id ) ) {
1599
			$query['where'] .= " {$andor} ( ( tt.taxonomy = 'product_tag' AND t.term_id IN ( " . implode( ',', $tags_array ) . " ) )";
1600
			$query['where'] .= " AND p.ID NOT IN ( " . implode( ',', $exclude_ids ) . " ) )";
1601
		}
1602
1603
		$query['limits'] = " LIMIT {$limit} ";
1604
		$query           = apply_filters( 'woocommerce_product_related_posts_query', $query, $this->id );
1605
1606
		return $query;
1607
	}
1608
}
1609