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