This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | if ( ! defined( 'ABSPATH' ) ) { |
||
4 | exit; // Exit if accessed directly |
||
5 | } |
||
6 | |||
7 | /** |
||
8 | * WooCommerce cart |
||
9 | * |
||
10 | * The WooCommerce cart class stores cart data and active coupons as well as handling customer sessions and some cart related urls. |
||
11 | * The cart class also has a price calculation function which calls upon other classes to calculate totals. |
||
12 | * |
||
13 | * @class WC_Cart |
||
14 | * @version 2.1.0 |
||
15 | * @package WooCommerce/Classes |
||
16 | * @category Class |
||
17 | * @author WooThemes |
||
18 | */ |
||
19 | class WC_Cart { |
||
20 | |||
21 | /** @var array Contains an array of cart items. */ |
||
22 | public $cart_contents = array(); |
||
23 | |||
24 | /** @var array Contains an array of removed cart items. */ |
||
25 | public $removed_cart_contents = array(); |
||
26 | |||
27 | /** @var array Contains an array of coupon codes applied to the cart. */ |
||
28 | public $applied_coupons = array(); |
||
29 | |||
30 | /** @var array Contains an array of coupon code discounts after they have been applied. */ |
||
31 | public $coupon_discount_amounts = array(); |
||
32 | |||
33 | /** @var array Contains an array of coupon code discount taxes. Used for tax incl pricing. */ |
||
34 | public $coupon_discount_tax_amounts = array(); |
||
35 | |||
36 | /** @var array Contains an array of coupon usage counts after they have been applied. */ |
||
37 | public $coupon_applied_count = array(); |
||
38 | |||
39 | /** @var array Array of coupons */ |
||
40 | public $coupons = array(); |
||
41 | |||
42 | /** @var float The total cost of the cart items. */ |
||
43 | public $cart_contents_total; |
||
44 | |||
45 | /** @var float Cart grand total. */ |
||
46 | public $total; |
||
47 | |||
48 | /** @var float Cart subtotal. */ |
||
49 | public $subtotal; |
||
50 | |||
51 | /** @var float Cart subtotal without tax. */ |
||
52 | public $subtotal_ex_tax; |
||
53 | |||
54 | /** @var float Total cart tax. */ |
||
55 | public $tax_total; |
||
56 | |||
57 | /** @var array An array of taxes/tax rates for the cart. */ |
||
58 | public $taxes; |
||
59 | |||
60 | /** @var array An array of taxes/tax rates for the shipping. */ |
||
61 | public $shipping_taxes; |
||
62 | |||
63 | /** @var float Discount amount before tax */ |
||
64 | public $discount_cart; |
||
65 | |||
66 | /** @var float Discounted tax amount. Used predominantly for displaying tax inclusive prices correctly */ |
||
67 | public $discount_cart_tax; |
||
68 | |||
69 | /** @var float Total for additional fees. */ |
||
70 | public $fee_total; |
||
71 | |||
72 | /** @var float Shipping cost. */ |
||
73 | public $shipping_total; |
||
74 | |||
75 | /** @var float Shipping tax. */ |
||
76 | public $shipping_tax_total; |
||
77 | |||
78 | /** @var array cart_session_data. Array of data the cart calculates and stores in the session with defaults */ |
||
79 | public $cart_session_data = array( |
||
80 | 'cart_contents_total' => 0, |
||
81 | 'total' => 0, |
||
82 | 'subtotal' => 0, |
||
83 | 'subtotal_ex_tax' => 0, |
||
84 | 'tax_total' => 0, |
||
85 | 'taxes' => array(), |
||
86 | 'shipping_taxes' => array(), |
||
87 | 'discount_cart' => 0, |
||
88 | 'discount_cart_tax' => 0, |
||
89 | 'shipping_total' => 0, |
||
90 | 'shipping_tax_total' => 0, |
||
91 | 'coupon_discount_amounts' => array(), |
||
92 | 'coupon_discount_tax_amounts' => array(), |
||
93 | 'fee_total' => 0, |
||
94 | 'fees' => array() |
||
95 | ); |
||
96 | |||
97 | /** |
||
98 | * An array of fees. |
||
99 | * |
||
100 | * @var array |
||
101 | */ |
||
102 | public $fees = array(); |
||
103 | |||
104 | /** |
||
105 | * Constructor for the cart class. Loads options and hooks in the init method. |
||
106 | */ |
||
107 | public function __construct() { |
||
108 | add_action( 'wp_loaded', array( $this, 'init' ) ); // Get cart after WP and plugins are loaded. |
||
109 | add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies |
||
110 | add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing |
||
111 | add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 ); |
||
112 | add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 ); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Auto-load in-accessible properties on demand. |
||
117 | * |
||
118 | * @param mixed $key |
||
119 | * @return mixed |
||
120 | */ |
||
121 | public function __get( $key ) { |
||
122 | switch ( $key ) { |
||
123 | case 'prices_include_tax' : |
||
124 | return wc_prices_include_tax(); |
||
125 | break; |
||
0 ignored issues
–
show
|
|||
126 | case 'round_at_subtotal' : |
||
127 | return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ); |
||
128 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
129 | case 'tax_display_cart' : |
||
130 | return get_option( 'woocommerce_tax_display_cart' ); |
||
131 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
132 | case 'dp' : |
||
133 | return wc_get_price_decimals(); |
||
134 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
135 | case 'display_totals_ex_tax' : |
||
136 | case 'display_cart_ex_tax' : |
||
137 | return $this->tax_display_cart === 'excl'; |
||
138 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
139 | case 'cart_contents_weight' : |
||
140 | return $this->get_cart_contents_weight(); |
||
141 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
142 | case 'cart_contents_count' : |
||
143 | return $this->get_cart_contents_count(); |
||
144 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
145 | case 'tax' : |
||
146 | _deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' ); |
||
147 | $this->tax = new WC_Tax(); |
||
148 | return $this->tax; |
||
149 | case 'discount_total': |
||
150 | _deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' ); |
||
151 | return 0; |
||
152 | } |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Loads the cart data from the PHP session during WordPress init and hooks in other methods. |
||
157 | */ |
||
158 | public function init() { |
||
159 | $this->get_cart_from_session(); |
||
160 | |||
161 | add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 ); |
||
162 | add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 ); |
||
163 | add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 ); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Will set cart cookies if needed, once, during WP hook. |
||
168 | */ |
||
169 | public function maybe_set_cart_cookies() { |
||
170 | if ( ! headers_sent() && did_action( 'wp_loaded' ) ) { |
||
171 | if ( ! $this->is_empty() ) { |
||
172 | $this->set_cart_cookies( true ); |
||
173 | } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) { |
||
174 | $this->set_cart_cookies( false ); |
||
175 | } |
||
176 | } |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Set cart hash cookie and items in cart. |
||
181 | * |
||
182 | * @access private |
||
183 | * @param bool $set (default: true) |
||
184 | */ |
||
185 | private function set_cart_cookies( $set = true ) { |
||
186 | if ( $set ) { |
||
187 | wc_setcookie( 'woocommerce_items_in_cart', 1 ); |
||
188 | wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $this->get_cart_for_session() ) ) ); |
||
189 | } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) { |
||
190 | wc_setcookie( 'woocommerce_items_in_cart', 0, time() - HOUR_IN_SECONDS ); |
||
191 | wc_setcookie( 'woocommerce_cart_hash', '', time() - HOUR_IN_SECONDS ); |
||
192 | } |
||
193 | do_action( 'woocommerce_set_cart_cookies', $set ); |
||
194 | } |
||
195 | |||
196 | /*-----------------------------------------------------------------------------------*/ |
||
197 | /* Cart Session Handling */ |
||
198 | /*-----------------------------------------------------------------------------------*/ |
||
199 | |||
200 | /** |
||
201 | * Get the cart data from the PHP session and store it in class variables. |
||
202 | */ |
||
203 | public function get_cart_from_session() { |
||
204 | // Load cart session data from session |
||
205 | foreach ( $this->cart_session_data as $key => $default ) { |
||
206 | $this->$key = WC()->session->get( $key, $default ); |
||
207 | } |
||
208 | |||
209 | $update_cart_session = false; |
||
210 | $this->removed_cart_contents = array_filter( WC()->session->get( 'removed_cart_contents', array() ) ); |
||
211 | $this->applied_coupons = array_filter( WC()->session->get( 'applied_coupons', array() ) ); |
||
212 | |||
213 | /** |
||
214 | * Load the cart object. This defaults to the persistent cart if null. |
||
215 | */ |
||
216 | $cart = WC()->session->get( 'cart', null ); |
||
217 | |||
218 | if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', true ) ) ) { |
||
219 | $cart = $saved_cart['cart']; |
||
220 | $update_cart_session = true; |
||
221 | } elseif ( is_null( $cart ) ) { |
||
222 | $cart = array(); |
||
223 | } |
||
224 | |||
225 | if ( is_array( $cart ) ) { |
||
226 | // Prime meta cache to reduce future queries |
||
227 | update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) ); |
||
228 | |||
229 | foreach ( $cart as $key => $values ) { |
||
230 | $_product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] ); |
||
231 | |||
232 | if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) { |
||
233 | |||
234 | if ( ! $_product->is_purchasable() ) { |
||
235 | |||
236 | // Flag to indicate the stored cart should be update |
||
237 | $update_cart_session = true; |
||
238 | wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $_product->get_title() ), 'error' ); |
||
239 | do_action( 'woocommerce_remove_cart_item_from_session', $key, $values ); |
||
240 | |||
241 | } else { |
||
242 | |||
243 | // Put session data into array. Run through filter so other plugins can load their own session data |
||
244 | $session_data = array_merge( $values, array( 'data' => $_product ) ); |
||
245 | $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key ); |
||
246 | |||
247 | } |
||
248 | } |
||
249 | } |
||
250 | } |
||
251 | |||
252 | // Trigger action |
||
253 | do_action( 'woocommerce_cart_loaded_from_session', $this ); |
||
254 | |||
255 | if ( $update_cart_session ) { |
||
256 | WC()->session->cart = $this->get_cart_for_session(); |
||
257 | } |
||
258 | |||
259 | // Queue re-calc if subtotal is not set |
||
260 | if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) { |
||
261 | $this->calculate_totals(); |
||
262 | } |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Sets the php session data for the cart and coupons. |
||
267 | */ |
||
268 | public function set_session() { |
||
269 | // Set cart and coupon session data |
||
270 | $cart_session = $this->get_cart_for_session(); |
||
271 | |||
272 | WC()->session->set( 'cart', $cart_session ); |
||
273 | WC()->session->set( 'applied_coupons', $this->applied_coupons ); |
||
274 | WC()->session->set( 'coupon_discount_amounts', $this->coupon_discount_amounts ); |
||
275 | WC()->session->set( 'coupon_discount_tax_amounts', $this->coupon_discount_tax_amounts ); |
||
276 | WC()->session->set( 'removed_cart_contents', $this->removed_cart_contents ); |
||
277 | |||
278 | foreach ( $this->cart_session_data as $key => $default ) { |
||
279 | WC()->session->set( $key, $this->$key ); |
||
280 | } |
||
281 | |||
282 | if ( get_current_user_id() ) { |
||
283 | $this->persistent_cart_update(); |
||
284 | } |
||
285 | |||
286 | do_action( 'woocommerce_cart_updated' ); |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Empties the cart and optionally the persistent cart too. |
||
291 | * |
||
292 | * @param bool $clear_persistent_cart (default: true) |
||
293 | */ |
||
294 | public function empty_cart( $clear_persistent_cart = true ) { |
||
295 | $this->cart_contents = array(); |
||
296 | $this->reset( true ); |
||
297 | |||
298 | unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart ); |
||
299 | |||
300 | if ( $clear_persistent_cart && get_current_user_id() ) { |
||
301 | $this->persistent_cart_destroy(); |
||
302 | } |
||
303 | |||
304 | do_action( 'woocommerce_cart_emptied' ); |
||
305 | } |
||
306 | |||
307 | /*-----------------------------------------------------------------------------------*/ |
||
308 | /* Persistent cart handling */ |
||
309 | /*-----------------------------------------------------------------------------------*/ |
||
310 | |||
311 | /** |
||
312 | * Save the persistent cart when the cart is updated. |
||
313 | */ |
||
314 | public function persistent_cart_update() { |
||
315 | update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', array( |
||
316 | 'cart' => WC()->session->get( 'cart' ) |
||
317 | ) ); |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Delete the persistent cart permanently. |
||
322 | */ |
||
323 | public function persistent_cart_destroy() { |
||
324 | delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart' ); |
||
325 | } |
||
326 | |||
327 | /*-----------------------------------------------------------------------------------*/ |
||
328 | /* Cart Data Functions */ |
||
329 | /*-----------------------------------------------------------------------------------*/ |
||
330 | |||
331 | /** |
||
332 | * Coupons enabled function. Filterable. |
||
333 | * |
||
334 | * @deprecated 2.5.0 in favor to wc_coupons_enabled() |
||
335 | * |
||
336 | * @return bool |
||
337 | */ |
||
338 | public function coupons_enabled() { |
||
339 | return wc_coupons_enabled(); |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Get number of items in the cart. |
||
344 | * @return int |
||
345 | */ |
||
346 | public function get_cart_contents_count() { |
||
347 | return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) ); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Get weight of items in the cart. |
||
352 | * @since 2.5.0 |
||
353 | * @return int |
||
354 | */ |
||
355 | public function get_cart_contents_weight() { |
||
356 | $weight = 0; |
||
357 | |||
358 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
359 | $weight += $values['data']->get_weight() * $values['quantity']; |
||
360 | } |
||
361 | |||
362 | return apply_filters( 'woocommerce_cart_contents_weight', $weight ); |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * Checks if the cart is empty. |
||
367 | * |
||
368 | * @return bool |
||
369 | */ |
||
370 | public function is_empty() { |
||
371 | return 0 === sizeof( $this->get_cart() ); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Check all cart items for errors. |
||
376 | */ |
||
377 | public function check_cart_items() { |
||
378 | |||
379 | // Result |
||
380 | $return = true; |
||
381 | |||
382 | // Check cart item validity |
||
383 | $result = $this->check_cart_item_validity(); |
||
384 | |||
385 | if ( is_wp_error( $result ) ) { |
||
386 | wc_add_notice( $result->get_error_message(), 'error' ); |
||
387 | $return = false; |
||
388 | } |
||
389 | |||
390 | // Check item stock |
||
391 | $result = $this->check_cart_item_stock(); |
||
392 | |||
393 | if ( is_wp_error( $result ) ) { |
||
394 | wc_add_notice( $result->get_error_message(), 'error' ); |
||
395 | $return = false; |
||
396 | } |
||
397 | |||
398 | return $return; |
||
399 | |||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Check cart coupons for errors. |
||
404 | */ |
||
405 | public function check_cart_coupons() { |
||
406 | foreach ( $this->applied_coupons as $code ) { |
||
407 | $coupon = new WC_Coupon( $code ); |
||
408 | |||
409 | View Code Duplication | if ( ! $coupon->is_valid() ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
410 | // Error message |
||
411 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED ); |
||
412 | |||
413 | // Remove the coupon |
||
414 | $this->remove_coupon( $code ); |
||
415 | |||
416 | // Flag totals for refresh |
||
417 | WC()->session->set( 'refresh_totals', true ); |
||
418 | } |
||
419 | } |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines. |
||
424 | * |
||
425 | * @return array |
||
426 | */ |
||
427 | public function get_cart_item_quantities() { |
||
428 | $quantities = array(); |
||
429 | |||
430 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
431 | $_product = $values['data']; |
||
432 | |||
433 | if ( $_product->is_type( 'variation' ) && true === $_product->managing_stock() ) { |
||
434 | // Variation has stock levels defined so its handled individually |
||
435 | $quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity']; |
||
436 | } else { |
||
437 | $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity']; |
||
438 | } |
||
439 | } |
||
440 | |||
441 | return $quantities; |
||
442 | } |
||
443 | |||
444 | /** |
||
445 | * Looks through cart items and checks the posts are not trashed or deleted. |
||
446 | * |
||
447 | * @return bool|WP_Error |
||
448 | */ |
||
449 | public function check_cart_item_validity() { |
||
450 | $return = true; |
||
451 | |||
452 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
453 | $_product = $values['data']; |
||
454 | |||
455 | if ( ! $_product || ! $_product->exists() || 'trash' === $_product->post->post_status ) { |
||
456 | $this->set_quantity( $cart_item_key, 0 ); |
||
457 | $return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) ); |
||
458 | } |
||
459 | } |
||
460 | |||
461 | return $return; |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Looks through the cart to check each item is in stock. If not, add an error. |
||
466 | * |
||
467 | * @return bool|WP_Error |
||
468 | */ |
||
469 | public function check_cart_item_stock() { |
||
470 | global $wpdb; |
||
471 | |||
472 | $error = new WP_Error(); |
||
473 | $product_qty_in_cart = $this->get_cart_item_quantities(); |
||
474 | |||
475 | // First stock check loop |
||
476 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
477 | $_product = $values['data']; |
||
478 | |||
479 | /** |
||
480 | * Check stock based on stock-status. |
||
481 | */ |
||
482 | if ( ! $_product->is_in_stock() ) { |
||
483 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title() ) ); |
||
484 | return $error; |
||
485 | } |
||
486 | |||
487 | if ( ! $_product->managing_stock() ) { |
||
488 | continue; |
||
489 | } |
||
490 | |||
491 | $check_qty = $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $product_qty_in_cart[ $values['variation_id'] ] : $product_qty_in_cart[ $values['product_id'] ]; |
||
492 | |||
493 | /** |
||
494 | * Check stock based on all items in the cart. |
||
495 | */ |
||
496 | if ( ! $_product->has_enough_stock( $check_qty ) ) { |
||
497 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfill your order (%s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), $_product->get_stock_quantity() ) ); |
||
498 | return $error; |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Finally consider any held stock, from pending orders. |
||
503 | */ |
||
504 | if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $_product->backorders_allowed() ) { |
||
505 | $order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0; |
||
506 | $held_stock = $wpdb->get_var( |
||
507 | $wpdb->prepare( " |
||
508 | SELECT SUM( order_item_meta.meta_value ) AS held_qty |
||
509 | FROM {$wpdb->posts} AS posts |
||
510 | LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id |
||
511 | LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id |
||
512 | LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id |
||
513 | WHERE order_item_meta.meta_key = '_qty' |
||
514 | AND order_item_meta2.meta_key = %s AND order_item_meta2.meta_value = %d |
||
515 | AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' ) |
||
516 | AND posts.post_status = 'wc-pending' |
||
517 | AND posts.ID != %d;", |
||
518 | $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? '_variation_id' : '_product_id', |
||
519 | $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $values['variation_id'] : $values['product_id'], |
||
520 | $order_id |
||
521 | ) |
||
522 | ); |
||
523 | |||
524 | $not_enough_stock = false; |
||
525 | |||
526 | if ( $_product->is_type( 'variation' ) && 'parent' === $_product->managing_stock() && $_product->parent->get_stock_quantity() < ( $held_stock + $check_qty ) ) { |
||
527 | $not_enough_stock = true; |
||
528 | } elseif ( $_product->get_stock_quantity() < ( $held_stock + $check_qty ) ) { |
||
529 | $not_enough_stock = true; |
||
530 | } |
||
531 | if ( $not_enough_stock ) { |
||
532 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfill your order right now. Please try again in %d minutes or edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), get_option( 'woocommerce_hold_stock_minutes' ) ) ); |
||
533 | return $error; |
||
534 | } |
||
535 | } |
||
536 | } |
||
537 | |||
538 | return true; |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * Gets and formats a list of cart item data + variations for display on the frontend. |
||
543 | * |
||
544 | * @param array $cart_item |
||
545 | * @param bool $flat (default: false) |
||
546 | * @return string |
||
547 | */ |
||
548 | public function get_item_data( $cart_item, $flat = false ) { |
||
549 | $item_data = array(); |
||
550 | |||
551 | // Variation data |
||
552 | if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) { |
||
553 | |||
554 | foreach ( $cart_item['variation'] as $name => $value ) { |
||
555 | |||
556 | if ( '' === $value ) |
||
557 | continue; |
||
558 | |||
559 | $taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) ); |
||
560 | |||
561 | // If this is a term slug, get the term's nice name |
||
562 | if ( taxonomy_exists( $taxonomy ) ) { |
||
563 | $term = get_term_by( 'slug', $value, $taxonomy ); |
||
564 | if ( ! is_wp_error( $term ) && $term && $term->name ) { |
||
565 | $value = $term->name; |
||
566 | } |
||
567 | $label = wc_attribute_label( $taxonomy ); |
||
568 | |||
569 | // If this is a custom option slug, get the options name |
||
570 | } else { |
||
571 | $value = apply_filters( 'woocommerce_variation_option_name', $value ); |
||
572 | $product_attributes = $cart_item['data']->get_attributes(); |
||
573 | if ( isset( $product_attributes[ str_replace( 'attribute_', '', $name ) ] ) ) { |
||
574 | $label = wc_attribute_label( $product_attributes[ str_replace( 'attribute_', '', $name ) ]['name'] ); |
||
575 | } else { |
||
576 | $label = $name; |
||
577 | } |
||
578 | } |
||
579 | |||
580 | $item_data[] = array( |
||
581 | 'key' => $label, |
||
582 | 'value' => $value |
||
583 | ); |
||
584 | } |
||
585 | } |
||
586 | |||
587 | // Filter item data to allow 3rd parties to add more to the array |
||
588 | $item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item ); |
||
589 | |||
590 | // Format item data ready to display |
||
591 | foreach ( $item_data as $key => $data ) { |
||
592 | // Set hidden to true to not display meta on cart. |
||
593 | if ( ! empty( $data['hidden'] ) ) { |
||
594 | unset( $item_data[ $key ] ); |
||
595 | continue; |
||
596 | } |
||
597 | $item_data[ $key ]['key'] = ! empty( $data['key'] ) ? $data['key'] : $data['name']; |
||
598 | $item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value']; |
||
599 | } |
||
600 | |||
601 | // Output flat or in list format |
||
602 | if ( sizeof( $item_data ) > 0 ) { |
||
603 | ob_start(); |
||
604 | |||
605 | if ( $flat ) { |
||
606 | foreach ( $item_data as $data ) { |
||
607 | echo esc_html( $data['key'] ) . ': ' . wp_kses_post( $data['display'] ) . "\n"; |
||
608 | } |
||
609 | } else { |
||
610 | wc_get_template( 'cart/cart-item-data.php', array( 'item_data' => $item_data ) ); |
||
611 | } |
||
612 | |||
613 | return ob_get_clean(); |
||
614 | } |
||
615 | |||
616 | return ''; |
||
617 | } |
||
618 | |||
619 | /** |
||
620 | * Gets cross sells based on the items in the cart. |
||
621 | * |
||
622 | * @return array cross_sells (item ids) |
||
623 | */ |
||
624 | public function get_cross_sells() { |
||
625 | $cross_sells = array(); |
||
626 | $in_cart = array(); |
||
627 | if ( ! $this->is_empty() ) { |
||
628 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
629 | if ( $values['quantity'] > 0 ) { |
||
630 | $cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells ); |
||
631 | $in_cart[] = $values['product_id']; |
||
632 | } |
||
633 | } |
||
634 | } |
||
635 | $cross_sells = array_diff( $cross_sells, $in_cart ); |
||
636 | return $cross_sells; |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * Gets the url to the cart page. |
||
641 | * |
||
642 | * @deprecated 2.5.0 in favor to wc_get_cart_url() |
||
643 | * |
||
644 | * @return string url to page |
||
645 | */ |
||
646 | public function get_cart_url() { |
||
647 | return wc_get_cart_url(); |
||
648 | } |
||
649 | |||
650 | /** |
||
651 | * Gets the url to the checkout page. |
||
652 | * |
||
653 | * @deprecated 2.5.0 in favor to wc_get_checkout_url() |
||
654 | * |
||
655 | * @return string url to page |
||
656 | */ |
||
657 | public function get_checkout_url() { |
||
658 | return wc_get_checkout_url(); |
||
659 | } |
||
660 | |||
661 | /** |
||
662 | * Gets the url to remove an item from the cart. |
||
663 | * |
||
664 | * @param string $cart_item_key contains the id of the cart item |
||
665 | * @return string url to page |
||
666 | */ |
||
667 | public function get_remove_url( $cart_item_key ) { |
||
668 | $cart_page_url = wc_get_page_permalink( 'cart' ); |
||
669 | return apply_filters( 'woocommerce_get_remove_url', $cart_page_url ? wp_nonce_url( add_query_arg( 'remove_item', $cart_item_key, $cart_page_url ), 'woocommerce-cart' ) : '' ); |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * Gets the url to re-add an item into the cart. |
||
674 | * |
||
675 | * @param string $cart_item_key |
||
676 | * @return string url to page |
||
677 | */ |
||
678 | public function get_undo_url( $cart_item_key ) { |
||
679 | $cart_page_url = wc_get_page_permalink( 'cart' ); |
||
680 | |||
681 | $query_args = array( |
||
682 | 'undo_item' => $cart_item_key, |
||
683 | ); |
||
684 | |||
685 | return apply_filters( 'woocommerce_get_undo_url', $cart_page_url ? wp_nonce_url( add_query_arg( $query_args, $cart_page_url ), 'woocommerce-cart' ) : '', $cart_item_key ); |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Returns the contents of the cart in an array. |
||
690 | * |
||
691 | * @return array contents of the cart |
||
692 | */ |
||
693 | public function get_cart() { |
||
694 | if ( ! did_action( 'wp_loaded' ) ) { |
||
695 | _doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' ); |
||
696 | } |
||
697 | if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) { |
||
698 | $this->get_cart_from_session(); |
||
699 | } |
||
700 | return array_filter( (array) $this->cart_contents ); |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Returns the contents of the cart in an array without the 'data' element. |
||
705 | * |
||
706 | * @return array contents of the cart |
||
707 | */ |
||
708 | public function get_cart_for_session() { |
||
709 | $cart_session = array(); |
||
710 | |||
711 | if ( $this->get_cart() ) { |
||
712 | foreach ( $this->get_cart() as $key => $values ) { |
||
713 | $cart_session[ $key ] = $values; |
||
714 | unset( $cart_session[ $key ]['data'] ); // Unset product object |
||
715 | } |
||
716 | } |
||
717 | |||
718 | return $cart_session; |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * Returns a specific item in the cart. |
||
723 | * |
||
724 | * @param string $item_key Cart item key. |
||
725 | * @return array Item data |
||
726 | */ |
||
727 | public function get_cart_item( $item_key ) { |
||
728 | if ( isset( $this->cart_contents[ $item_key ] ) ) { |
||
729 | return $this->cart_contents[ $item_key ]; |
||
730 | } |
||
731 | |||
732 | return array(); |
||
733 | } |
||
734 | |||
735 | /** |
||
736 | * Returns the cart and shipping taxes, merged. |
||
737 | * |
||
738 | * @return array merged taxes |
||
739 | */ |
||
740 | public function get_taxes() { |
||
741 | $taxes = array(); |
||
742 | |||
743 | // Merge |
||
744 | foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) { |
||
745 | $taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
746 | } |
||
747 | |||
748 | return apply_filters( 'woocommerce_cart_get_taxes', $taxes, $this ); |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Get taxes, merged by code, formatted ready for output. |
||
753 | * |
||
754 | * @return array |
||
755 | */ |
||
756 | public function get_tax_totals() { |
||
757 | $taxes = $this->get_taxes(); |
||
758 | $tax_totals = array(); |
||
759 | |||
760 | foreach ( $taxes as $key => $tax ) { |
||
761 | $code = WC_Tax::get_rate_code( $key ); |
||
762 | |||
763 | if ( $code || $key === apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) ) { |
||
764 | View Code Duplication | if ( ! isset( $tax_totals[ $code ] ) ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
765 | $tax_totals[ $code ] = new stdClass(); |
||
766 | $tax_totals[ $code ]->amount = 0; |
||
767 | } |
||
768 | $tax_totals[ $code ]->tax_rate_id = $key; |
||
769 | $tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key ); |
||
770 | $tax_totals[ $code ]->label = WC_Tax::get_rate_label( $key ); |
||
771 | $tax_totals[ $code ]->amount += wc_round_tax_total( $tax ); |
||
772 | $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ) ); |
||
773 | } |
||
774 | } |
||
775 | |||
776 | View Code Duplication | if ( apply_filters( 'woocommerce_cart_hide_zero_taxes', true ) ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
777 | $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) ); |
||
778 | $tax_totals = array_intersect_key( $tax_totals, $amounts ); |
||
779 | } |
||
780 | |||
781 | return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this ); |
||
782 | } |
||
783 | |||
784 | /** |
||
785 | * Get all tax classes for items in the cart. |
||
786 | * @return array |
||
787 | */ |
||
788 | public function get_cart_item_tax_classes() { |
||
789 | $found_tax_classes = array(); |
||
790 | |||
791 | foreach ( WC()->cart->get_cart() as $item ) { |
||
792 | $found_tax_classes[] = $item['data']->get_tax_class(); |
||
793 | } |
||
794 | |||
795 | return array_unique( $found_tax_classes ); |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Determines the value that the customer spent and the subtotal |
||
800 | * displayed, used for things like coupon validation. |
||
801 | * |
||
802 | * Since the coupon lines are displayed based on the TAX DISPLAY value |
||
803 | * of cart, this is used to determine the spend. |
||
804 | * |
||
805 | * If cart totals are shown including tax, use the subtotal. |
||
806 | * If cart totals are shown excluding tax, use the subtotal ex tax |
||
807 | * (tax is shown after coupons). |
||
808 | * |
||
809 | * @since 2.6.0 |
||
810 | * @return string |
||
811 | */ |
||
812 | public function get_displayed_subtotal() { |
||
813 | if ( 'incl' === $this->tax_display_cart ) { |
||
814 | return wc_format_decimal( $this->subtotal ); |
||
815 | } elseif ( 'excl' === $this->tax_display_cart ) { |
||
816 | return wc_format_decimal( $this->subtotal_ex_tax ); |
||
817 | } |
||
818 | } |
||
819 | |||
820 | /*-----------------------------------------------------------------------------------*/ |
||
821 | /* Add to cart handling */ |
||
822 | /*-----------------------------------------------------------------------------------*/ |
||
823 | |||
824 | /** |
||
825 | * Check if product is in the cart and return cart item key. |
||
826 | * |
||
827 | * Cart item key will be unique based on the item and its properties, such as variations. |
||
828 | * |
||
829 | * @param mixed id of product to find in the cart |
||
830 | * @return string cart item key |
||
831 | */ |
||
832 | public function find_product_in_cart( $cart_id = false ) { |
||
833 | if ( $cart_id !== false ) { |
||
834 | if ( is_array( $this->cart_contents ) ) { |
||
835 | foreach ( $this->cart_contents as $cart_item_key => $cart_item ) { |
||
836 | if ( $cart_item_key == $cart_id ) { |
||
837 | return $cart_item_key; |
||
838 | } |
||
839 | } |
||
840 | } |
||
841 | } |
||
842 | return ''; |
||
843 | } |
||
844 | |||
845 | /** |
||
846 | * Generate a unique ID for the cart item being added. |
||
847 | * |
||
848 | * @param int $product_id - id of the product the key is being generated for |
||
849 | * @param int $variation_id of the product the key is being generated for |
||
850 | * @param array $variation data for the cart item |
||
851 | * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart |
||
852 | * @return string cart item key |
||
853 | */ |
||
854 | public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { |
||
855 | $id_parts = array( $product_id ); |
||
856 | |||
857 | if ( $variation_id && 0 != $variation_id ) { |
||
858 | $id_parts[] = $variation_id; |
||
859 | } |
||
860 | |||
861 | if ( is_array( $variation ) && ! empty( $variation ) ) { |
||
862 | $variation_key = ''; |
||
863 | foreach ( $variation as $key => $value ) { |
||
864 | $variation_key .= trim( $key ) . trim( $value ); |
||
865 | } |
||
866 | $id_parts[] = $variation_key; |
||
867 | } |
||
868 | |||
869 | if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) { |
||
870 | $cart_item_data_key = ''; |
||
871 | foreach ( $cart_item_data as $key => $value ) { |
||
872 | |||
873 | if ( is_array( $value ) ) { |
||
874 | $value = http_build_query( $value ); |
||
875 | } |
||
876 | $cart_item_data_key .= trim( $key ) . trim( $value ); |
||
877 | |||
878 | } |
||
879 | $id_parts[] = $cart_item_data_key; |
||
880 | } |
||
881 | |||
882 | return md5( implode( '_', $id_parts ) ); |
||
883 | } |
||
884 | |||
885 | /** |
||
886 | * Add a product to the cart. |
||
887 | * |
||
888 | * @param int $product_id contains the id of the product to add to the cart |
||
889 | * @param int $quantity contains the quantity of the item to add |
||
890 | * @param int $variation_id |
||
891 | * @param array $variation attribute values |
||
892 | * @param array $cart_item_data extra cart item data we want to pass into the item |
||
893 | * @return string|bool $cart_item_key |
||
894 | */ |
||
895 | public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { |
||
896 | // Wrap in try catch so plugins can throw an exception to prevent adding to cart |
||
897 | try { |
||
898 | $product_id = absint( $product_id ); |
||
899 | $variation_id = absint( $variation_id ); |
||
900 | |||
901 | // Ensure we don't add a variation to the cart directly by variation ID |
||
902 | if ( 'product_variation' == get_post_type( $product_id ) ) { |
||
903 | $variation_id = $product_id; |
||
904 | $product_id = wp_get_post_parent_id( $variation_id ); |
||
905 | } |
||
906 | |||
907 | // Get the product |
||
908 | $product_data = wc_get_product( $variation_id ? $variation_id : $product_id ); |
||
909 | |||
910 | // Sanity check |
||
911 | if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->post->post_status ) { |
||
912 | throw new Exception(); |
||
913 | } |
||
914 | |||
915 | // Load cart item data - may be added by other plugins |
||
916 | $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id ); |
||
917 | |||
918 | // Generate a ID based on product ID, variation ID, variation data, and other cart item data |
||
919 | $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data ); |
||
920 | |||
921 | // Find the cart item key in the existing cart |
||
922 | $cart_item_key = $this->find_product_in_cart( $cart_id ); |
||
0 ignored issues
–
show
$cart_id is of type string , but the function expects a boolean .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() |
|||
923 | |||
924 | // Force quantity to 1 if sold individually and check for existing item in cart |
||
925 | if ( $product_data->is_sold_individually() ) { |
||
926 | $quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data ); |
||
927 | $in_cart_quantity = $cart_item_key ? $this->cart_contents[ $cart_item_key ]['quantity'] : 0; |
||
928 | |||
929 | if ( $in_cart_quantity > 0 ) { |
||
930 | throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View Cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_title() ) ) ); |
||
931 | } |
||
932 | } |
||
933 | |||
934 | // Check product is_purchasable |
||
935 | if ( ! $product_data->is_purchasable() ) { |
||
936 | throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) ); |
||
937 | } |
||
938 | |||
939 | // Stock check - only check if we're managing stock and backorders are not allowed |
||
940 | if ( ! $product_data->is_in_stock() ) { |
||
941 | throw new Exception( sprintf( __( 'You cannot add "%s" to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_title() ) ); |
||
942 | } |
||
943 | |||
944 | if ( ! $product_data->has_enough_stock( $quantity ) ) { |
||
945 | throw new Exception( sprintf(__( 'You cannot add that amount of "%s" to the cart because there is not enough stock (%s remaining).', 'woocommerce' ), $product_data->get_title(), $product_data->get_stock_quantity() ) ); |
||
946 | } |
||
947 | |||
948 | // Stock check - this time accounting for whats already in-cart |
||
949 | if ( $managing_stock = $product_data->managing_stock() ) { |
||
950 | $products_qty_in_cart = $this->get_cart_item_quantities(); |
||
951 | |||
952 | if ( $product_data->is_type( 'variation' ) && true === $managing_stock ) { |
||
953 | $check_qty = isset( $products_qty_in_cart[ $variation_id ] ) ? $products_qty_in_cart[ $variation_id ] : 0; |
||
954 | } else { |
||
955 | $check_qty = isset( $products_qty_in_cart[ $product_id ] ) ? $products_qty_in_cart[ $product_id ] : 0; |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * Check stock based on all items in the cart. |
||
960 | */ |
||
961 | if ( ! $product_data->has_enough_stock( $check_qty + $quantity ) ) { |
||
962 | throw new Exception( sprintf( |
||
963 | '<a href="%s" class="button wc-forward">%s</a> %s', |
||
964 | wc_get_cart_url(), |
||
965 | __( 'View Cart', 'woocommerce' ), |
||
966 | sprintf( __( 'You cannot add that amount to the cart — we have %s in stock and you already have %s in your cart.', 'woocommerce' ), $product_data->get_stock_quantity(), $check_qty ) |
||
967 | ) ); |
||
968 | } |
||
969 | } |
||
970 | |||
971 | // If cart_item_key is set, the item is already in the cart |
||
972 | if ( $cart_item_key ) { |
||
973 | $new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity']; |
||
974 | $this->set_quantity( $cart_item_key, $new_quantity, false ); |
||
975 | } else { |
||
976 | $cart_item_key = $cart_id; |
||
977 | |||
978 | // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item |
||
979 | $this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array( |
||
980 | 'product_id' => $product_id, |
||
981 | 'variation_id' => $variation_id, |
||
982 | 'variation' => $variation, |
||
983 | 'quantity' => $quantity, |
||
984 | 'data' => $product_data |
||
985 | ) ), $cart_item_key ); |
||
986 | } |
||
987 | |||
988 | if ( did_action( 'wp' ) ) { |
||
989 | $this->set_cart_cookies( ! $this->is_empty() ); |
||
990 | } |
||
991 | |||
992 | do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ); |
||
993 | |||
994 | return $cart_item_key; |
||
995 | |||
996 | } catch ( Exception $e ) { |
||
997 | if ( $e->getMessage() ) { |
||
998 | wc_add_notice( $e->getMessage(), 'error' ); |
||
999 | } |
||
1000 | return false; |
||
1001 | } |
||
1002 | } |
||
1003 | |||
1004 | /** |
||
1005 | * Remove a cart item. |
||
1006 | * |
||
1007 | * @since 2.3.0 |
||
1008 | * @param string $cart_item_key |
||
1009 | * @return bool |
||
1010 | */ |
||
1011 | public function remove_cart_item( $cart_item_key ) { |
||
1012 | if ( isset( $this->cart_contents[ $cart_item_key ] ) ) { |
||
1013 | $this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ]; |
||
1014 | unset( $this->removed_cart_contents[ $cart_item_key ]['data'] ); |
||
1015 | |||
1016 | do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this ); |
||
1017 | |||
1018 | unset( $this->cart_contents[ $cart_item_key ] ); |
||
1019 | |||
1020 | do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this ); |
||
1021 | |||
1022 | $this->calculate_totals(); |
||
1023 | |||
1024 | return true; |
||
1025 | } |
||
1026 | |||
1027 | return false; |
||
1028 | } |
||
1029 | |||
1030 | /** |
||
1031 | * Restore a cart item. |
||
1032 | * |
||
1033 | * @param string $cart_item_key |
||
1034 | * @return bool |
||
1035 | */ |
||
1036 | public function restore_cart_item( $cart_item_key ) { |
||
1037 | if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) { |
||
1038 | $this->cart_contents[ $cart_item_key ] = $this->removed_cart_contents[ $cart_item_key ]; |
||
1039 | $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $this->cart_contents[ $cart_item_key ]['variation_id'] ? $this->cart_contents[ $cart_item_key ]['variation_id'] : $this->cart_contents[ $cart_item_key ]['product_id'] ); |
||
1040 | |||
1041 | do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this ); |
||
1042 | |||
1043 | unset( $this->removed_cart_contents[ $cart_item_key ] ); |
||
1044 | |||
1045 | do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this ); |
||
1046 | |||
1047 | $this->calculate_totals(); |
||
1048 | |||
1049 | return true; |
||
1050 | } |
||
1051 | |||
1052 | return false; |
||
1053 | } |
||
1054 | |||
1055 | /** |
||
1056 | * Set the quantity for an item in the cart. |
||
1057 | * |
||
1058 | * @param string $cart_item_key contains the id of the cart item |
||
1059 | * @param int $quantity contains the quantity of the item |
||
1060 | * @param bool $refresh_totals whether or not to calculate totals after setting the new qty |
||
1061 | * |
||
1062 | * @return bool |
||
1063 | */ |
||
1064 | public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) { |
||
1065 | if ( $quantity == 0 || $quantity < 0 ) { |
||
1066 | do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key ); |
||
1067 | unset( $this->cart_contents[ $cart_item_key ] ); |
||
1068 | } else { |
||
1069 | $old_quantity = $this->cart_contents[ $cart_item_key ]['quantity']; |
||
1070 | $this->cart_contents[ $cart_item_key ]['quantity'] = $quantity; |
||
1071 | do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity ); |
||
1072 | } |
||
1073 | |||
1074 | if ( $refresh_totals ) { |
||
1075 | $this->calculate_totals(); |
||
1076 | } |
||
1077 | |||
1078 | return true; |
||
1079 | } |
||
1080 | |||
1081 | /*-----------------------------------------------------------------------------------*/ |
||
1082 | /* Cart Calculation Functions */ |
||
1083 | /*-----------------------------------------------------------------------------------*/ |
||
1084 | |||
1085 | /** |
||
1086 | * Reset cart totals to the defaults. Useful before running calculations. |
||
1087 | * |
||
1088 | * @param bool $unset_session If true, the session data will be forced unset. |
||
1089 | * @access private |
||
1090 | */ |
||
1091 | private function reset( $unset_session = false ) { |
||
1092 | foreach ( $this->cart_session_data as $key => $default ) { |
||
1093 | $this->$key = $default; |
||
1094 | if ( $unset_session ) { |
||
1095 | unset( WC()->session->$key ); |
||
1096 | } |
||
1097 | } |
||
1098 | do_action( 'woocommerce_cart_reset', $this, $unset_session ); |
||
1099 | } |
||
1100 | |||
1101 | /** |
||
1102 | * Sort by subtotal. |
||
1103 | * @param array $a |
||
1104 | * @param array $b |
||
1105 | * @return int |
||
1106 | */ |
||
1107 | private function sort_by_subtotal( $a, $b ) { |
||
1108 | $first_item_subtotal = isset( $a['line_subtotal'] ) ? $a['line_subtotal'] : 0; |
||
1109 | $second_item_subtotal = isset( $b['line_subtotal'] ) ? $b['line_subtotal'] : 0; |
||
1110 | if ( $first_item_subtotal === $second_item_subtotal ) { |
||
1111 | return 0; |
||
1112 | } |
||
1113 | return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1; |
||
1114 | } |
||
1115 | |||
1116 | /** |
||
1117 | * Calculate totals for the items in the cart. |
||
1118 | */ |
||
1119 | public function calculate_totals() { |
||
1120 | $this->reset(); |
||
1121 | $this->coupons = $this->get_coupons(); |
||
1122 | |||
1123 | do_action( 'woocommerce_before_calculate_totals', $this ); |
||
1124 | |||
1125 | if ( $this->is_empty() ) { |
||
1126 | $this->set_session(); |
||
1127 | return; |
||
1128 | } |
||
1129 | |||
1130 | $tax_rates = array(); |
||
1131 | $shop_tax_rates = array(); |
||
1132 | $cart = $this->get_cart(); |
||
1133 | |||
1134 | /** |
||
1135 | * Calculate subtotals for items. This is done first so that discount logic can use the values. |
||
1136 | */ |
||
1137 | foreach ( $cart as $cart_item_key => $values ) { |
||
1138 | $_product = $values['data']; |
||
1139 | $line_price = $_product->get_price() * $values['quantity']; |
||
1140 | $line_subtotal = 0; |
||
1141 | $line_subtotal_tax = 0; |
||
1142 | |||
1143 | /** |
||
1144 | * No tax to calculate. |
||
1145 | */ |
||
1146 | if ( ! $_product->is_taxable() ) { |
||
1147 | |||
1148 | // Subtotal is the undiscounted price |
||
1149 | $this->subtotal += $line_price; |
||
1150 | $this->subtotal_ex_tax += $line_price; |
||
1151 | |||
1152 | /** |
||
1153 | * Prices include tax. |
||
1154 | * |
||
1155 | * To prevent rounding issues we need to work with the inclusive price where possible. |
||
1156 | * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would. |
||
1157 | * be 8.325 leading to totals being 1p off. |
||
1158 | * |
||
1159 | * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated. |
||
1160 | * afterwards. |
||
1161 | * |
||
1162 | * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that. |
||
1163 | */ |
||
1164 | } elseif ( $this->prices_include_tax ) { |
||
1165 | |||
1166 | // Get base tax rates |
||
1167 | if ( empty( $shop_tax_rates[ $_product->tax_class ] ) ) { |
||
1168 | $shop_tax_rates[ $_product->tax_class ] = WC_Tax::get_base_tax_rates( $_product->tax_class ); |
||
1169 | } |
||
1170 | |||
1171 | // Get item tax rates |
||
1172 | if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { |
||
1173 | $tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); |
||
1174 | } |
||
1175 | |||
1176 | $base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; |
||
1177 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1178 | |||
1179 | /** |
||
1180 | * ADJUST TAX - Calculations when base tax is not equal to the item tax. |
||
1181 | * |
||
1182 | * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. |
||
1183 | * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. |
||
1184 | * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. |
||
1185 | */ |
||
1186 | if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { |
||
1187 | |||
1188 | // Work out a new base price without the shop's base tax |
||
1189 | $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true ); |
||
1190 | |||
1191 | // Now we have a new item price (excluding TAX) |
||
1192 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1193 | |||
1194 | // Now add modified taxes |
||
1195 | $tax_result = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates ); |
||
1196 | $line_subtotal_tax = array_sum( $tax_result ); |
||
1197 | |||
1198 | /** |
||
1199 | * Regular tax calculation (customer inside base and the tax class is unmodified. |
||
1200 | */ |
||
1201 | } else { |
||
1202 | |||
1203 | // Calc tax normally |
||
1204 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true ); |
||
1205 | $line_subtotal_tax = array_sum( $taxes ); |
||
1206 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1207 | } |
||
1208 | |||
1209 | /** |
||
1210 | * Prices exclude tax. |
||
1211 | * |
||
1212 | * This calculation is simpler - work with the base, untaxed price. |
||
1213 | */ |
||
1214 | } else { |
||
1215 | |||
1216 | // Get item tax rates |
||
1217 | if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { |
||
1218 | $tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); |
||
1219 | } |
||
1220 | |||
1221 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1222 | |||
1223 | // Base tax for line before discount - we will store this in the order data |
||
1224 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); |
||
1225 | $line_subtotal_tax = array_sum( $taxes ); |
||
1226 | |||
1227 | $line_subtotal = $line_price; |
||
1228 | } |
||
1229 | |||
1230 | // Add to main subtotal |
||
1231 | $this->subtotal += $line_subtotal + $line_subtotal_tax; |
||
1232 | $this->subtotal_ex_tax += $line_subtotal; |
||
1233 | } |
||
1234 | |||
1235 | // Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart. |
||
1236 | uasort( $cart, array( $this, 'sort_by_subtotal' ) ); |
||
1237 | |||
1238 | /** |
||
1239 | * Calculate totals for items. |
||
1240 | */ |
||
1241 | foreach ( $cart as $cart_item_key => $values ) { |
||
1242 | |||
1243 | $_product = $values['data']; |
||
1244 | |||
1245 | // Prices |
||
1246 | $base_price = $_product->get_price(); |
||
1247 | $line_price = $_product->get_price() * $values['quantity']; |
||
1248 | |||
1249 | // Tax data |
||
1250 | $taxes = array(); |
||
1251 | $discounted_taxes = array(); |
||
1252 | |||
1253 | /** |
||
1254 | * No tax to calculate. |
||
1255 | */ |
||
1256 | if ( ! $_product->is_taxable() ) { |
||
1257 | |||
1258 | // Discounted Price (price with any pre-tax discounts applied) |
||
1259 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1260 | $line_subtotal_tax = 0; |
||
1261 | $line_subtotal = $line_price; |
||
1262 | $line_tax = 0; |
||
1263 | $line_total = round( $discounted_price * $values['quantity'], WC_ROUNDING_PRECISION ); |
||
1264 | |||
1265 | /** |
||
1266 | * Prices include tax. |
||
1267 | */ |
||
1268 | } elseif ( $this->prices_include_tax ) { |
||
1269 | |||
1270 | $base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; |
||
1271 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1272 | |||
1273 | /** |
||
1274 | * ADJUST TAX - Calculations when base tax is not equal to the item tax. |
||
1275 | * |
||
1276 | * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. |
||
1277 | * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. |
||
1278 | * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. |
||
1279 | */ |
||
1280 | if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { |
||
1281 | |||
1282 | // Work out a new base price without the shop's base tax |
||
1283 | $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true ); |
||
1284 | |||
1285 | // Now we have a new item price (excluding TAX) |
||
1286 | $line_subtotal = round( $line_price - array_sum( $taxes ), WC_ROUNDING_PRECISION ); |
||
1287 | $taxes = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates ); |
||
1288 | $line_subtotal_tax = array_sum( $taxes ); |
||
1289 | |||
1290 | // Adjusted price (this is the price including the new tax rate) |
||
1291 | $adjusted_price = ( $line_subtotal + $line_subtotal_tax ) / $values['quantity']; |
||
1292 | |||
1293 | // Apply discounts and get the discounted price FOR A SINGLE ITEM |
||
1294 | $discounted_price = $this->get_discounted_price( $values, $adjusted_price, true ); |
||
1295 | |||
1296 | // Convert back to line price and round nicely |
||
1297 | $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); |
||
1298 | |||
1299 | // Now use rounded line price to get taxes. |
||
1300 | $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); |
||
1301 | $line_tax = array_sum( $discounted_taxes ); |
||
1302 | $line_total = $discounted_line_price - $line_tax; |
||
1303 | |||
1304 | /** |
||
1305 | * Regular tax calculation (customer inside base and the tax class is unmodified. |
||
1306 | */ |
||
1307 | } else { |
||
1308 | |||
1309 | // Work out a new base price without the item tax |
||
1310 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true ); |
||
1311 | |||
1312 | // Now we have a new item price (excluding TAX) |
||
1313 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1314 | $line_subtotal_tax = array_sum( $taxes ); |
||
1315 | |||
1316 | // Calc prices and tax (discounted) |
||
1317 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1318 | |||
1319 | // Convert back to line price and round nicely |
||
1320 | $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); |
||
1321 | |||
1322 | // Now use rounded line price to get taxes. |
||
1323 | $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); |
||
1324 | $line_tax = array_sum( $discounted_taxes ); |
||
1325 | $line_total = $discounted_line_price - $line_tax; |
||
1326 | } |
||
1327 | |||
1328 | // Tax rows - merge the totals we just got |
||
1329 | View Code Duplication | foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
1330 | $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
1331 | } |
||
1332 | |||
1333 | /** |
||
1334 | * Prices exclude tax. |
||
1335 | */ |
||
1336 | } else { |
||
1337 | |||
1338 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1339 | |||
1340 | // Work out a new base price without the shop's base tax |
||
1341 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); |
||
1342 | |||
1343 | // Now we have the item price (excluding TAX) |
||
1344 | $line_subtotal = $line_price; |
||
1345 | $line_subtotal_tax = array_sum( $taxes ); |
||
1346 | |||
1347 | // Now calc product rates |
||
1348 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1349 | $discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates ); |
||
1350 | $discounted_tax_amount = array_sum( $discounted_taxes ); |
||
1351 | $line_tax = $discounted_tax_amount; |
||
1352 | $line_total = $discounted_price * $values['quantity']; |
||
1353 | |||
1354 | // Tax rows - merge the totals we just got |
||
1355 | View Code Duplication | foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
1356 | $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
1357 | } |
||
1358 | } |
||
1359 | |||
1360 | // Cart contents total is based on discounted prices and is used for the final total calculation |
||
1361 | $this->cart_contents_total += $line_total; |
||
1362 | |||
1363 | // Store costs + taxes for lines |
||
1364 | $this->cart_contents[ $cart_item_key ]['line_total'] = $line_total; |
||
1365 | $this->cart_contents[ $cart_item_key ]['line_tax'] = $line_tax; |
||
1366 | $this->cart_contents[ $cart_item_key ]['line_subtotal'] = $line_subtotal; |
||
1367 | $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax; |
||
1368 | |||
1369 | // Store rates ID and costs - Since 2.2 |
||
1370 | $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => $discounted_taxes, 'subtotal' => $taxes ); |
||
1371 | } |
||
1372 | |||
1373 | // Only calculate the grand total + shipping if on the cart/checkout |
||
1374 | if ( is_checkout() || is_cart() || defined('WOOCOMMERCE_CHECKOUT') || defined('WOOCOMMERCE_CART') ) { |
||
1375 | |||
1376 | // Calculate the Shipping |
||
1377 | $this->calculate_shipping(); |
||
1378 | |||
1379 | // Trigger the fees API where developers can add fees to the cart |
||
1380 | $this->calculate_fees(); |
||
1381 | |||
1382 | // Total up/round taxes and shipping taxes |
||
1383 | if ( $this->round_at_subtotal ) { |
||
1384 | $this->tax_total = WC_Tax::get_tax_total( $this->taxes ); |
||
0 ignored issues
–
show
It seems like
\WC_Tax::get_tax_total($this->taxes) can also be of type integer . However, the property $tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1385 | $this->shipping_tax_total = WC_Tax::get_tax_total( $this->shipping_taxes ); |
||
0 ignored issues
–
show
It seems like
\WC_Tax::get_tax_total($this->shipping_taxes) can also be of type integer . However, the property $shipping_tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1386 | $this->taxes = array_map( array( 'WC_Tax', 'round' ), $this->taxes ); |
||
1387 | $this->shipping_taxes = array_map( array( 'WC_Tax', 'round' ), $this->shipping_taxes ); |
||
1388 | } else { |
||
1389 | $this->tax_total = array_sum( $this->taxes ); |
||
0 ignored issues
–
show
It seems like
array_sum($this->taxes) can also be of type integer . However, the property $tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1390 | $this->shipping_tax_total = array_sum( $this->shipping_taxes ); |
||
0 ignored issues
–
show
It seems like
array_sum($this->shipping_taxes) can also be of type integer . However, the property $shipping_tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1391 | } |
||
1392 | |||
1393 | // VAT exemption done at this point - so all totals are correct before exemption |
||
1394 | if ( WC()->customer->is_vat_exempt() ) { |
||
1395 | $this->remove_taxes(); |
||
1396 | } |
||
1397 | |||
1398 | // Allow plugins to hook and alter totals before final total is calculated |
||
1399 | do_action( 'woocommerce_calculate_totals', $this ); |
||
1400 | |||
1401 | // Grand Total - Discounted product prices, discounted tax, shipping cost + tax |
||
1402 | $this->total = max( 0, apply_filters( 'woocommerce_calculated_total', round( $this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp ), $this ) ); |
||
1403 | |||
1404 | } else { |
||
1405 | |||
1406 | // Set tax total to sum of all tax rows |
||
1407 | $this->tax_total = WC_Tax::get_tax_total( $this->taxes ); |
||
1408 | |||
1409 | // VAT exemption done at this point - so all totals are correct before exemption |
||
1410 | if ( WC()->customer->is_vat_exempt() ) { |
||
1411 | $this->remove_taxes(); |
||
1412 | } |
||
1413 | } |
||
1414 | |||
1415 | do_action( 'woocommerce_after_calculate_totals', $this ); |
||
1416 | |||
1417 | $this->set_session(); |
||
1418 | } |
||
1419 | |||
1420 | /** |
||
1421 | * Remove taxes. |
||
1422 | */ |
||
1423 | public function remove_taxes() { |
||
1424 | $this->shipping_tax_total = $this->tax_total = 0; |
||
0 ignored issues
–
show
The property
$tax_total was declared of type double , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() The property
$shipping_tax_total was declared of type double , but $this->tax_total = 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
1425 | $this->subtotal = $this->subtotal_ex_tax; |
||
1426 | |||
1427 | foreach ( $this->cart_contents as $cart_item_key => $item ) { |
||
1428 | $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $this->cart_contents[ $cart_item_key ]['line_tax'] = 0; |
||
1429 | $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => array(), 'subtotal' => array() ); |
||
1430 | } |
||
1431 | |||
1432 | // If true, zero rate is applied so '0' tax is displayed on the frontend rather than nothing. |
||
1433 | if ( apply_filters( 'woocommerce_cart_remove_taxes_apply_zero_rate', true ) ) { |
||
1434 | $this->taxes = $this->shipping_taxes = array( apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) => 0 ); |
||
1435 | } else { |
||
1436 | $this->taxes = $this->shipping_taxes = array(); |
||
1437 | } |
||
1438 | } |
||
1439 | |||
1440 | /** |
||
1441 | * Looks at the totals to see if payment is actually required. |
||
1442 | * |
||
1443 | * @return bool |
||
1444 | */ |
||
1445 | public function needs_payment() { |
||
1446 | return apply_filters( 'woocommerce_cart_needs_payment', $this->total > 0, $this ); |
||
1447 | } |
||
1448 | |||
1449 | /*-----------------------------------------------------------------------------------*/ |
||
1450 | /* Shipping related functions */ |
||
1451 | /*-----------------------------------------------------------------------------------*/ |
||
1452 | |||
1453 | /** |
||
1454 | * Uses the shipping class to calculate shipping then gets the totals when its finished. |
||
1455 | */ |
||
1456 | public function calculate_shipping() { |
||
1457 | if ( $this->needs_shipping() && $this->show_shipping() ) { |
||
1458 | WC()->shipping->calculate_shipping( $this->get_shipping_packages() ); |
||
1459 | } else { |
||
1460 | WC()->shipping->reset_shipping(); |
||
1461 | } |
||
1462 | |||
1463 | // Get totals for the chosen shipping method |
||
1464 | $this->shipping_total = WC()->shipping->shipping_total; // Shipping Total |
||
1465 | $this->shipping_taxes = WC()->shipping->shipping_taxes; // Shipping Taxes |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * Get packages to calculate shipping for. |
||
1470 | * |
||
1471 | * This lets us calculate costs for carts that are shipped to multiple locations. |
||
1472 | * |
||
1473 | * Shipping methods are responsible for looping through these packages. |
||
1474 | * |
||
1475 | * By default we pass the cart itself as a package - plugins can change this. |
||
1476 | * through the filter and break it up. |
||
1477 | * |
||
1478 | * @since 1.5.4 |
||
1479 | * @return array of cart items |
||
1480 | */ |
||
1481 | public function get_shipping_packages() { |
||
1482 | // Packages array for storing 'carts' |
||
1483 | $packages = array(); |
||
1484 | |||
1485 | $packages[0]['contents'] = $this->get_cart(); // Items in the package |
||
1486 | $packages[0]['contents_cost'] = 0; // Cost of items in the package, set below |
||
1487 | $packages[0]['applied_coupons'] = $this->applied_coupons; |
||
1488 | $packages[0]['user']['ID'] = get_current_user_id(); |
||
1489 | $packages[0]['destination']['country'] = WC()->customer->get_shipping_country(); |
||
1490 | $packages[0]['destination']['state'] = WC()->customer->get_shipping_state(); |
||
1491 | $packages[0]['destination']['postcode'] = WC()->customer->get_shipping_postcode(); |
||
1492 | $packages[0]['destination']['city'] = WC()->customer->get_shipping_city(); |
||
1493 | $packages[0]['destination']['address'] = WC()->customer->get_shipping_address(); |
||
1494 | $packages[0]['destination']['address_2'] = WC()->customer->get_shipping_address_2(); |
||
1495 | |||
1496 | foreach ( $this->get_cart() as $item ) { |
||
1497 | if ( $item['data']->needs_shipping() ) { |
||
1498 | if ( isset( $item['line_total'] ) ) { |
||
1499 | $packages[0]['contents_cost'] += $item['line_total']; |
||
1500 | } |
||
1501 | } |
||
1502 | } |
||
1503 | |||
1504 | return apply_filters( 'woocommerce_cart_shipping_packages', $packages ); |
||
1505 | } |
||
1506 | |||
1507 | /** |
||
1508 | * Looks through the cart to see if shipping is actually required. |
||
1509 | * |
||
1510 | * @return bool whether or not the cart needs shipping |
||
1511 | */ |
||
1512 | public function needs_shipping() { |
||
1513 | // If shipping is disabled or not yet configured, we can skip this. |
||
1514 | if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) ) { |
||
1515 | return false; |
||
1516 | } |
||
1517 | |||
1518 | $needs_shipping = false; |
||
1519 | |||
1520 | if ( $this->cart_contents ) { |
||
0 ignored issues
–
show
The expression
$this->cart_contents of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using ![]() |
|||
1521 | foreach ( $this->cart_contents as $cart_item_key => $values ) { |
||
1522 | $_product = $values['data']; |
||
1523 | if ( $_product->needs_shipping() ) { |
||
1524 | $needs_shipping = true; |
||
1525 | } |
||
1526 | } |
||
1527 | } |
||
1528 | |||
1529 | return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping ); |
||
1530 | } |
||
1531 | |||
1532 | /** |
||
1533 | * Should the shipping address form be shown. |
||
1534 | * |
||
1535 | * @return bool |
||
1536 | */ |
||
1537 | public function needs_shipping_address() { |
||
1538 | |||
1539 | $needs_shipping_address = false; |
||
1540 | |||
1541 | if ( $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ) { |
||
1542 | $needs_shipping_address = true; |
||
1543 | } |
||
1544 | |||
1545 | return apply_filters( 'woocommerce_cart_needs_shipping_address', $needs_shipping_address ); |
||
1546 | } |
||
1547 | |||
1548 | /** |
||
1549 | * Sees if the customer has entered enough data to calc the shipping yet. |
||
1550 | * |
||
1551 | * @return bool |
||
1552 | */ |
||
1553 | public function show_shipping() { |
||
1554 | if ( ! wc_shipping_enabled() || ! is_array( $this->cart_contents ) ) |
||
1555 | return false; |
||
1556 | |||
1557 | if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) { |
||
1558 | if ( ! WC()->customer->has_calculated_shipping() ) { |
||
1559 | if ( ! WC()->customer->get_shipping_country() || ( ! WC()->customer->get_shipping_state() && ! WC()->customer->get_shipping_postcode() ) ) { |
||
1560 | return false; |
||
1561 | } |
||
1562 | } |
||
1563 | } |
||
1564 | |||
1565 | return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true ); |
||
1566 | } |
||
1567 | |||
1568 | /** |
||
1569 | * Sees if we need a shipping address. |
||
1570 | * |
||
1571 | * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only() |
||
1572 | * |
||
1573 | * @return bool |
||
1574 | */ |
||
1575 | public function ship_to_billing_address_only() { |
||
1576 | return wc_ship_to_billing_address_only(); |
||
1577 | } |
||
1578 | |||
1579 | /** |
||
1580 | * Gets the shipping total (after calculation). |
||
1581 | * |
||
1582 | * @return string price or string for the shipping total |
||
1583 | */ |
||
1584 | public function get_cart_shipping_total() { |
||
1585 | if ( isset( $this->shipping_total ) ) { |
||
1586 | if ( $this->shipping_total > 0 ) { |
||
1587 | |||
1588 | // Display varies depending on settings |
||
1589 | if ( $this->tax_display_cart == 'excl' ) { |
||
1590 | |||
1591 | $return = wc_price( $this->shipping_total ); |
||
1592 | |||
1593 | if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) { |
||
1594 | $return .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
1595 | } |
||
1596 | |||
1597 | return $return; |
||
1598 | |||
1599 | } else { |
||
1600 | |||
1601 | $return = wc_price( $this->shipping_total + $this->shipping_tax_total ); |
||
1602 | |||
1603 | if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) { |
||
1604 | $return .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
1605 | } |
||
1606 | |||
1607 | return $return; |
||
1608 | |||
1609 | } |
||
1610 | |||
1611 | } else { |
||
1612 | return __( 'Free!', 'woocommerce' ); |
||
1613 | } |
||
1614 | } |
||
1615 | |||
1616 | return ''; |
||
1617 | } |
||
1618 | |||
1619 | /*-----------------------------------------------------------------------------------*/ |
||
1620 | /* Coupons/Discount related functions */ |
||
1621 | /*-----------------------------------------------------------------------------------*/ |
||
1622 | |||
1623 | /** |
||
1624 | * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error. |
||
1625 | * |
||
1626 | * Checks two types of coupons: |
||
1627 | * 1. Where a list of customer emails are set (limits coupon usage to those defined). |
||
1628 | * 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email). |
||
1629 | * |
||
1630 | * @param array $posted |
||
1631 | */ |
||
1632 | public function check_customer_coupons( $posted ) { |
||
1633 | if ( ! empty( $this->applied_coupons ) ) { |
||
1634 | foreach ( $this->applied_coupons as $code ) { |
||
1635 | $coupon = new WC_Coupon( $code ); |
||
1636 | |||
1637 | if ( $coupon->is_valid() ) { |
||
1638 | |||
1639 | // Limit to defined email addresses |
||
1640 | if ( is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) { |
||
1641 | $check_emails = array(); |
||
1642 | $coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email ); |
||
1643 | |||
1644 | if ( is_user_logged_in() ) { |
||
1645 | $current_user = wp_get_current_user(); |
||
1646 | $check_emails[] = $current_user->user_email; |
||
1647 | } |
||
1648 | $check_emails[] = $posted['billing_email']; |
||
1649 | $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) ); |
||
1650 | |||
1651 | if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) { |
||
1652 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ); |
||
1653 | |||
1654 | // Remove the coupon |
||
1655 | $this->remove_coupon( $code ); |
||
1656 | |||
1657 | // Flag totals for refresh |
||
1658 | WC()->session->set( 'refresh_totals', true ); |
||
1659 | } |
||
1660 | } |
||
1661 | |||
1662 | // Usage limits per user - check against billing and user email and user ID |
||
1663 | if ( $coupon->usage_limit_per_user > 0 ) { |
||
1664 | $check_emails = array(); |
||
1665 | $used_by = $coupon->get_used_by(); |
||
1666 | |||
1667 | if ( is_user_logged_in() ) { |
||
1668 | $current_user = wp_get_current_user(); |
||
1669 | $check_emails[] = sanitize_email( $current_user->user_email ); |
||
1670 | $usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) ); |
||
1671 | } else { |
||
1672 | $check_emails[] = sanitize_email( $posted['billing_email'] ); |
||
1673 | $user = get_user_by( 'email', $posted['billing_email'] ); |
||
1674 | if ( $user ) { |
||
1675 | $usage_count = sizeof( array_keys( $used_by, $user->ID ) ); |
||
1676 | } else { |
||
1677 | $usage_count = 0; |
||
1678 | } |
||
1679 | } |
||
1680 | |||
1681 | foreach ( $check_emails as $check_email ) { |
||
1682 | $usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) ); |
||
1683 | } |
||
1684 | |||
1685 | View Code Duplication | if ( $usage_count >= $coupon->usage_limit_per_user ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
1686 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ); |
||
1687 | |||
1688 | // Remove the coupon |
||
1689 | $this->remove_coupon( $code ); |
||
1690 | |||
1691 | // Flag totals for refresh |
||
1692 | WC()->session->set( 'refresh_totals', true ); |
||
1693 | } |
||
1694 | } |
||
1695 | } |
||
1696 | } |
||
1697 | } |
||
1698 | } |
||
1699 | |||
1700 | /** |
||
1701 | * Returns whether or not a discount has been applied. |
||
1702 | * @param string $coupon_code |
||
1703 | * @return bool |
||
1704 | */ |
||
1705 | public function has_discount( $coupon_code = '' ) { |
||
1706 | return $coupon_code ? in_array( apply_filters( 'woocommerce_coupon_code', $coupon_code ), $this->applied_coupons ) : sizeof( $this->applied_coupons ) > 0; |
||
1707 | } |
||
1708 | |||
1709 | /** |
||
1710 | * Applies a coupon code passed to the method. |
||
1711 | * |
||
1712 | * @param string $coupon_code - The code to apply |
||
1713 | * @return bool True if the coupon is applied, false if it does not exist or cannot be applied |
||
1714 | */ |
||
1715 | public function add_discount( $coupon_code ) { |
||
1716 | // Coupons are globally disabled |
||
1717 | if ( ! wc_coupons_enabled() ) { |
||
1718 | return false; |
||
1719 | } |
||
1720 | |||
1721 | // Sanitize coupon code |
||
1722 | $coupon_code = apply_filters( 'woocommerce_coupon_code', $coupon_code ); |
||
1723 | |||
1724 | // Get the coupon |
||
1725 | $the_coupon = new WC_Coupon( $coupon_code ); |
||
1726 | |||
1727 | // Check it can be used with cart |
||
1728 | if ( ! $the_coupon->is_valid() ) { |
||
1729 | wc_add_notice( $the_coupon->get_error_message(), 'error' ); |
||
1730 | return false; |
||
1731 | } |
||
1732 | |||
1733 | // Check if applied |
||
1734 | if ( $this->has_discount( $coupon_code ) ) { |
||
1735 | $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED ); |
||
1736 | return false; |
||
1737 | } |
||
1738 | |||
1739 | // If its individual use then remove other coupons |
||
1740 | if ( $the_coupon->individual_use == 'yes' ) { |
||
1741 | $this->applied_coupons = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons ); |
||
1742 | } |
||
1743 | |||
1744 | if ( $this->applied_coupons ) { |
||
1745 | foreach ( $this->applied_coupons as $code ) { |
||
1746 | $coupon = new WC_Coupon( $code ); |
||
1747 | |||
1748 | if ( $coupon->individual_use == 'yes' && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) { |
||
1749 | |||
1750 | // Reject new coupon |
||
1751 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY ); |
||
1752 | |||
1753 | return false; |
||
1754 | } |
||
1755 | } |
||
1756 | } |
||
1757 | |||
1758 | $this->applied_coupons[] = $coupon_code; |
||
1759 | |||
1760 | // Choose free shipping |
||
1761 | if ( $the_coupon->enable_free_shipping() ) { |
||
1762 | $packages = WC()->shipping->get_packages(); |
||
1763 | $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); |
||
1764 | |||
1765 | foreach ( $packages as $i => $package ) { |
||
1766 | $chosen_shipping_methods[ $i ] = 'free_shipping'; |
||
1767 | } |
||
1768 | |||
1769 | WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); |
||
1770 | } |
||
1771 | |||
1772 | $the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS ); |
||
1773 | |||
1774 | do_action( 'woocommerce_applied_coupon', $coupon_code ); |
||
1775 | |||
1776 | return true; |
||
1777 | } |
||
1778 | |||
1779 | /** |
||
1780 | * Get array of applied coupon objects and codes. |
||
1781 | * @return array of applied coupons |
||
1782 | */ |
||
1783 | public function get_coupons( $deprecated = null ) { |
||
1784 | $coupons = array(); |
||
1785 | |||
1786 | if ( 'order' === $deprecated ) { |
||
1787 | return $coupons; |
||
1788 | } |
||
1789 | |||
1790 | foreach ( $this->get_applied_coupons() as $code ) { |
||
1791 | $coupon = new WC_Coupon( $code ); |
||
1792 | $coupons[ $code ] = $coupon; |
||
1793 | } |
||
1794 | |||
1795 | return $coupons; |
||
1796 | } |
||
1797 | |||
1798 | /** |
||
1799 | * Gets the array of applied coupon codes. |
||
1800 | * |
||
1801 | * @return array of applied coupons |
||
1802 | */ |
||
1803 | public function get_applied_coupons() { |
||
1804 | return $this->applied_coupons; |
||
1805 | } |
||
1806 | |||
1807 | /** |
||
1808 | * Get the discount amount for a used coupon. |
||
1809 | * @param string $code coupon code |
||
1810 | * @param bool $ex_tax inc or ex tax |
||
1811 | * @return float discount amount |
||
1812 | */ |
||
1813 | public function get_coupon_discount_amount( $code, $ex_tax = true ) { |
||
1814 | $discount_amount = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] : 0; |
||
1815 | |||
1816 | if ( ! $ex_tax ) { |
||
1817 | $discount_amount += $this->get_coupon_discount_tax_amount( $code ); |
||
1818 | } |
||
1819 | |||
1820 | return wc_cart_round_discount( $discount_amount, $this->dp ); |
||
1821 | } |
||
1822 | |||
1823 | /** |
||
1824 | * Get the discount tax amount for a used coupon (for tax inclusive prices). |
||
1825 | * @param string $code coupon code |
||
1826 | * @param bool inc or ex tax |
||
1827 | * @return float discount amount |
||
1828 | */ |
||
1829 | public function get_coupon_discount_tax_amount( $code ) { |
||
1830 | return wc_cart_round_discount( isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] : 0, $this->dp ); |
||
1831 | } |
||
1832 | |||
1833 | /** |
||
1834 | * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax. |
||
1835 | */ |
||
1836 | public function remove_coupons( $deprecated = null ) { |
||
0 ignored issues
–
show
|
|||
1837 | $this->applied_coupons = $this->coupon_discount_amounts = $this->coupon_discount_tax_amounts = $this->coupon_applied_count = array(); |
||
1838 | WC()->session->set( 'applied_coupons', array() ); |
||
1839 | WC()->session->set( 'coupon_discount_amounts', array() ); |
||
1840 | WC()->session->set( 'coupon_discount_tax_amounts', array() ); |
||
1841 | } |
||
1842 | |||
1843 | /** |
||
1844 | * Remove a single coupon by code. |
||
1845 | * @param string $coupon_code Code of the coupon to remove |
||
1846 | * @return bool |
||
1847 | */ |
||
1848 | public function remove_coupon( $coupon_code ) { |
||
1849 | // Coupons are globally disabled |
||
1850 | if ( ! wc_coupons_enabled() ) { |
||
1851 | return false; |
||
1852 | } |
||
1853 | |||
1854 | // Get the coupon |
||
1855 | $coupon_code = apply_filters( 'woocommerce_coupon_code', $coupon_code ); |
||
1856 | $position = array_search( $coupon_code, $this->applied_coupons ); |
||
1857 | |||
1858 | if ( $position !== false ) { |
||
1859 | unset( $this->applied_coupons[ $position ] ); |
||
1860 | } |
||
1861 | |||
1862 | WC()->session->set( 'applied_coupons', $this->applied_coupons ); |
||
1863 | |||
1864 | do_action( 'woocommerce_removed_coupon', $coupon_code ); |
||
1865 | |||
1866 | return true; |
||
1867 | } |
||
1868 | |||
1869 | /** |
||
1870 | * Function to apply discounts to a product and get the discounted price (before tax is applied). |
||
1871 | * |
||
1872 | * @param mixed $values |
||
1873 | * @param mixed $price |
||
1874 | * @param bool $add_totals (default: false) |
||
1875 | * @return float price |
||
1876 | */ |
||
1877 | public function get_discounted_price( $values, $price, $add_totals = false ) { |
||
1878 | if ( ! $price ) { |
||
1879 | return $price; |
||
1880 | } |
||
1881 | |||
1882 | $undiscounted_price = $price; |
||
1883 | |||
1884 | if ( ! empty( $this->coupons ) ) { |
||
1885 | $product = $values['data']; |
||
1886 | |||
1887 | foreach ( $this->coupons as $code => $coupon ) { |
||
1888 | if ( $coupon->is_valid() && ( $coupon->is_valid_for_product( $product, $values ) || $coupon->is_valid_for_cart() ) ) { |
||
1889 | $discount_amount = $coupon->get_discount_amount( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ? $price : $undiscounted_price, $values, true ); |
||
1890 | $discount_amount = min( $price, $discount_amount ); |
||
1891 | $price = max( $price - $discount_amount, 0 ); |
||
1892 | |||
1893 | // Store the totals for DISPLAY in the cart |
||
1894 | if ( $add_totals ) { |
||
1895 | $total_discount = $discount_amount * $values['quantity']; |
||
1896 | $total_discount_tax = 0; |
||
1897 | |||
1898 | if ( wc_tax_enabled() ) { |
||
1899 | $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); |
||
1900 | $taxes = WC_Tax::calc_tax( $discount_amount, $tax_rates, $this->prices_include_tax ); |
||
1901 | $total_discount_tax = WC_Tax::get_tax_total( $taxes ) * $values['quantity']; |
||
1902 | $total_discount = $this->prices_include_tax ? $total_discount - $total_discount_tax : $total_discount; |
||
1903 | $this->discount_cart_tax += $total_discount_tax; |
||
1904 | } |
||
1905 | |||
1906 | $this->discount_cart += $total_discount; |
||
1907 | $this->increase_coupon_discount_amount( $code, $total_discount, $total_discount_tax ); |
||
1908 | $this->increase_coupon_applied_count( $code, $values['quantity'] ); |
||
1909 | } |
||
1910 | } |
||
1911 | |||
1912 | // If the price is 0, we can stop going through coupons because there is nothing more to discount for this product. |
||
1913 | if ( 0 >= $price ) { |
||
1914 | break; |
||
1915 | } |
||
1916 | } |
||
1917 | } |
||
1918 | |||
1919 | return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this ); |
||
1920 | } |
||
1921 | |||
1922 | /** |
||
1923 | * Store how much discount each coupon grants. |
||
1924 | * |
||
1925 | * @access private |
||
1926 | * @param string $code |
||
1927 | * @param double $amount |
||
1928 | * @param double $tax |
||
1929 | */ |
||
1930 | private function increase_coupon_discount_amount( $code, $amount, $tax ) { |
||
1931 | $this->coupon_discount_amounts[ $code ] = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] + $amount : $amount; |
||
1932 | $this->coupon_discount_tax_amounts[ $code ] = isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] + $tax : $tax; |
||
1933 | } |
||
1934 | |||
1935 | /** |
||
1936 | * Store how many times each coupon is applied to cart/items. |
||
1937 | * |
||
1938 | * @access private |
||
1939 | * @param string $code |
||
1940 | * @param int $count |
||
1941 | */ |
||
1942 | private function increase_coupon_applied_count( $code, $count = 1 ) { |
||
1943 | if ( empty( $this->coupon_applied_count[ $code ] ) ) { |
||
1944 | $this->coupon_applied_count[ $code ] = 0; |
||
1945 | } |
||
1946 | $this->coupon_applied_count[ $code ] += $count; |
||
1947 | } |
||
1948 | |||
1949 | /*-----------------------------------------------------------------------------------*/ |
||
1950 | /* Fees API to add additional costs to orders */ |
||
1951 | /*-----------------------------------------------------------------------------------*/ |
||
1952 | |||
1953 | /** |
||
1954 | * Add additional fee to the cart. |
||
1955 | * |
||
1956 | * @param string $name Unique name for the fee. Multiple fees of the same name cannot be added. |
||
1957 | * @param float $amount Fee amount. |
||
1958 | * @param bool $taxable (default: false) Is the fee taxable? |
||
1959 | * @param string $tax_class (default: '') The tax class for the fee if taxable. A blank string is standard tax class. |
||
1960 | */ |
||
1961 | public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) { |
||
1962 | |||
1963 | $new_fee_id = sanitize_title( $name ); |
||
1964 | |||
1965 | // Only add each fee once |
||
1966 | foreach ( $this->fees as $fee ) { |
||
1967 | if ( $fee->id == $new_fee_id ) { |
||
1968 | return; |
||
1969 | } |
||
1970 | } |
||
1971 | |||
1972 | $new_fee = new stdClass(); |
||
1973 | $new_fee->id = $new_fee_id; |
||
1974 | $new_fee->name = esc_attr( $name ); |
||
1975 | $new_fee->amount = (float) esc_attr( $amount ); |
||
1976 | $new_fee->tax_class = $tax_class; |
||
1977 | $new_fee->taxable = $taxable ? true : false; |
||
1978 | $new_fee->tax = 0; |
||
1979 | $new_fee->tax_data = array(); |
||
1980 | $this->fees[] = $new_fee; |
||
1981 | } |
||
1982 | |||
1983 | /** |
||
1984 | * Get fees. |
||
1985 | * |
||
1986 | * @return array |
||
1987 | */ |
||
1988 | public function get_fees() { |
||
1989 | return array_filter( (array) $this->fees ); |
||
1990 | } |
||
1991 | |||
1992 | /** |
||
1993 | * Calculate fees. |
||
1994 | */ |
||
1995 | public function calculate_fees() { |
||
1996 | // Reset fees before calculation |
||
1997 | $this->fee_total = 0; |
||
0 ignored issues
–
show
The property
$fee_total was declared of type double , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
1998 | $this->fees = array(); |
||
1999 | |||
2000 | // Fire an action where developers can add their fees |
||
2001 | do_action( 'woocommerce_cart_calculate_fees', $this ); |
||
2002 | |||
2003 | // If fees were added, total them and calculate tax |
||
2004 | if ( ! empty( $this->fees ) ) { |
||
2005 | foreach ( $this->fees as $fee_key => $fee ) { |
||
2006 | $this->fee_total += $fee->amount; |
||
2007 | |||
2008 | if ( $fee->taxable ) { |
||
2009 | // Get tax rates |
||
2010 | $tax_rates = WC_Tax::get_rates( $fee->tax_class ); |
||
2011 | $fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false ); |
||
2012 | |||
2013 | if ( ! empty( $fee_taxes ) ) { |
||
2014 | // Set the tax total for this fee |
||
2015 | $this->fees[ $fee_key ]->tax = array_sum( $fee_taxes ); |
||
2016 | |||
2017 | // Set tax data - Since 2.2 |
||
2018 | $this->fees[ $fee_key ]->tax_data = $fee_taxes; |
||
2019 | |||
2020 | // Tax rows - merge the totals we just got |
||
2021 | View Code Duplication | foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
2022 | $this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
2023 | } |
||
2024 | } |
||
2025 | } |
||
2026 | } |
||
2027 | } |
||
2028 | } |
||
2029 | |||
2030 | /*-----------------------------------------------------------------------------------*/ |
||
2031 | /* Get Formatted Totals */ |
||
2032 | /*-----------------------------------------------------------------------------------*/ |
||
2033 | |||
2034 | /** |
||
2035 | * Gets the order total (after calculation). |
||
2036 | * |
||
2037 | * @return string formatted price |
||
2038 | */ |
||
2039 | public function get_total() { |
||
2040 | return apply_filters( 'woocommerce_cart_total', wc_price( $this->total ) ); |
||
2041 | } |
||
2042 | |||
2043 | /** |
||
2044 | * Gets the total excluding taxes. |
||
2045 | * |
||
2046 | * @return string formatted price |
||
2047 | */ |
||
2048 | public function get_total_ex_tax() { |
||
2049 | $total = $this->total - $this->tax_total - $this->shipping_tax_total; |
||
2050 | if ( $total < 0 ) { |
||
2051 | $total = 0; |
||
2052 | } |
||
2053 | return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( $total ) ); |
||
2054 | } |
||
2055 | |||
2056 | /** |
||
2057 | * Gets the cart contents total (after calculation). |
||
2058 | * |
||
2059 | * @return string formatted price |
||
2060 | */ |
||
2061 | public function get_cart_total() { |
||
2062 | if ( ! $this->prices_include_tax ) { |
||
2063 | $cart_contents_total = wc_price( $this->cart_contents_total ); |
||
2064 | } else { |
||
2065 | $cart_contents_total = wc_price( $this->cart_contents_total + $this->tax_total ); |
||
2066 | } |
||
2067 | |||
2068 | return apply_filters( 'woocommerce_cart_contents_total', $cart_contents_total ); |
||
2069 | } |
||
2070 | |||
2071 | /** |
||
2072 | * Gets the sub total (after calculation). |
||
2073 | * |
||
2074 | * @param bool $compound whether to include compound taxes |
||
2075 | * @return string formatted price |
||
2076 | */ |
||
2077 | public function get_cart_subtotal( $compound = false ) { |
||
2078 | |||
2079 | // If the cart has compound tax, we want to show the subtotal as |
||
2080 | // cart + shipping + non-compound taxes (after discount) |
||
2081 | if ( $compound ) { |
||
2082 | |||
2083 | $cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) ); |
||
2084 | |||
2085 | // Otherwise we show cart items totals only (before discount) |
||
2086 | } else { |
||
2087 | |||
2088 | // Display varies depending on settings |
||
2089 | if ( $this->tax_display_cart == 'excl' ) { |
||
2090 | |||
2091 | $cart_subtotal = wc_price( $this->subtotal_ex_tax ); |
||
2092 | |||
2093 | if ( $this->tax_total > 0 && $this->prices_include_tax ) { |
||
2094 | $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
2095 | } |
||
2096 | |||
2097 | } else { |
||
2098 | |||
2099 | $cart_subtotal = wc_price( $this->subtotal ); |
||
2100 | |||
2101 | if ( $this->tax_total > 0 && !$this->prices_include_tax ) { |
||
2102 | $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
2103 | } |
||
2104 | |||
2105 | } |
||
2106 | } |
||
2107 | |||
2108 | return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this ); |
||
2109 | } |
||
2110 | |||
2111 | /** |
||
2112 | * Get the product row price per item. |
||
2113 | * |
||
2114 | * @param WC_Product $_product |
||
2115 | * @return string formatted price |
||
2116 | */ |
||
2117 | public function get_product_price( $_product ) { |
||
2118 | if ( $this->tax_display_cart == 'excl' ) { |
||
2119 | $product_price = $_product->get_price_excluding_tax(); |
||
2120 | } else { |
||
2121 | $product_price = $_product->get_price_including_tax(); |
||
2122 | } |
||
2123 | |||
2124 | return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $_product ); |
||
2125 | } |
||
2126 | |||
2127 | /** |
||
2128 | * Get the product row subtotal. |
||
2129 | * |
||
2130 | * Gets the tax etc to avoid rounding issues. |
||
2131 | * |
||
2132 | * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate. |
||
2133 | * |
||
2134 | * @param WC_Product $_product |
||
2135 | * @param int $quantity |
||
2136 | * @return string formatted price |
||
2137 | */ |
||
2138 | public function get_product_subtotal( $_product, $quantity ) { |
||
2139 | |||
2140 | $price = $_product->get_price(); |
||
2141 | $taxable = $_product->is_taxable(); |
||
2142 | |||
2143 | // Taxable |
||
2144 | if ( $taxable ) { |
||
2145 | |||
2146 | if ( $this->tax_display_cart == 'excl' ) { |
||
2147 | |||
2148 | $row_price = $_product->get_price_excluding_tax( $quantity ); |
||
2149 | $product_subtotal = wc_price( $row_price ); |
||
2150 | |||
2151 | if ( $this->prices_include_tax && $this->tax_total > 0 ) { |
||
2152 | $product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
2153 | } |
||
2154 | |||
2155 | } else { |
||
2156 | |||
2157 | $row_price = $_product->get_price_including_tax( $quantity ); |
||
2158 | $product_subtotal = wc_price( $row_price ); |
||
2159 | |||
2160 | if ( ! $this->prices_include_tax && $this->tax_total > 0 ) { |
||
2161 | $product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
2162 | } |
||
2163 | |||
2164 | } |
||
2165 | |||
2166 | // Non-taxable |
||
2167 | } else { |
||
2168 | |||
2169 | $row_price = $price * $quantity; |
||
2170 | $product_subtotal = wc_price( $row_price ); |
||
2171 | |||
2172 | } |
||
2173 | |||
2174 | return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $_product, $quantity, $this ); |
||
2175 | } |
||
2176 | |||
2177 | /** |
||
2178 | * Gets the cart tax (after calculation). |
||
2179 | * |
||
2180 | * @return string formatted price |
||
2181 | */ |
||
2182 | public function get_cart_tax() { |
||
2183 | $cart_total_tax = wc_round_tax_total( $this->tax_total + $this->shipping_tax_total ); |
||
2184 | |||
2185 | return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' ); |
||
2186 | } |
||
2187 | |||
2188 | /** |
||
2189 | * Get a tax amount. |
||
2190 | * @param string $tax_rate_id |
||
2191 | * @return float amount |
||
2192 | */ |
||
2193 | public function get_tax_amount( $tax_rate_id ) { |
||
2194 | return isset( $this->taxes[ $tax_rate_id ] ) ? $this->taxes[ $tax_rate_id ] : 0; |
||
2195 | } |
||
2196 | |||
2197 | /** |
||
2198 | * Get a tax amount. |
||
2199 | * @param string $tax_rate_id |
||
2200 | * @return float amount |
||
2201 | */ |
||
2202 | public function get_shipping_tax_amount( $tax_rate_id ) { |
||
2203 | return isset( $this->shipping_taxes[ $tax_rate_id ] ) ? $this->shipping_taxes[ $tax_rate_id ] : 0; |
||
2204 | } |
||
2205 | |||
2206 | /** |
||
2207 | * Get tax row amounts with or without compound taxes includes. |
||
2208 | * |
||
2209 | * @param bool $compound True if getting compound taxes |
||
2210 | * @param bool $display True if getting total to display |
||
2211 | * @return float price |
||
2212 | */ |
||
2213 | public function get_taxes_total( $compound = true, $display = true ) { |
||
2214 | $total = 0; |
||
2215 | View Code Duplication | foreach ( $this->taxes as $key => $tax ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
2216 | if ( ! $compound && WC_Tax::is_compound( $key ) ) continue; |
||
2217 | $total += $tax; |
||
2218 | } |
||
2219 | View Code Duplication | foreach ( $this->shipping_taxes as $key => $tax ) { |
|
0 ignored issues
–
show
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. ![]() |
|||
2220 | if ( ! $compound && WC_Tax::is_compound( $key ) ) continue; |
||
2221 | $total += $tax; |
||
2222 | } |
||
2223 | if ( $display ) { |
||
2224 | $total = wc_round_tax_total( $total ); |
||
2225 | } |
||
2226 | return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this ); |
||
2227 | } |
||
2228 | |||
2229 | /** |
||
2230 | * Get the total of all cart discounts. |
||
2231 | * |
||
2232 | * @return float |
||
2233 | */ |
||
2234 | public function get_cart_discount_total() { |
||
2235 | return wc_cart_round_discount( $this->discount_cart, $this->dp ); |
||
2236 | } |
||
2237 | |||
2238 | /** |
||
2239 | * Get the total of all cart tax discounts (used for discounts on tax inclusive prices). |
||
2240 | * |
||
2241 | * @return float |
||
2242 | */ |
||
2243 | public function get_cart_discount_tax_total() { |
||
2244 | return wc_cart_round_discount( $this->discount_cart_tax, $this->dp ); |
||
2245 | } |
||
2246 | |||
2247 | /** |
||
2248 | * Gets the total discount amount - both kinds. |
||
2249 | * |
||
2250 | * @return mixed formatted price or false if there are none |
||
2251 | */ |
||
2252 | public function get_total_discount() { |
||
2253 | if ( $this->get_cart_discount_total() ) { |
||
2254 | $total_discount = wc_price( $this->get_cart_discount_total() ); |
||
2255 | } else { |
||
2256 | $total_discount = false; |
||
2257 | } |
||
2258 | return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this ); |
||
2259 | } |
||
2260 | |||
2261 | /** |
||
2262 | * Gets the total (product) discount amount - these are applied before tax. |
||
2263 | * |
||
2264 | * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required. |
||
2265 | * @return mixed formatted price or false if there are none |
||
2266 | */ |
||
2267 | public function get_discounts_before_tax() { |
||
2268 | _deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' ); |
||
2269 | if ( $this->get_cart_discount_total() ) { |
||
2270 | $discounts_before_tax = wc_price( $this->get_cart_discount_total() ); |
||
2271 | } else { |
||
2272 | $discounts_before_tax = false; |
||
2273 | } |
||
2274 | return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this ); |
||
2275 | } |
||
2276 | |||
2277 | /** |
||
2278 | * Get the total of all order discounts (after tax discounts). |
||
2279 | * |
||
2280 | * @deprecated Order discounts (after tax) removed in 2.3 |
||
2281 | * @return int |
||
2282 | */ |
||
2283 | public function get_order_discount_total() { |
||
2284 | _deprecated_function( 'get_order_discount_total', '2.3' ); |
||
2285 | return 0; |
||
2286 | } |
||
2287 | |||
2288 | /** |
||
2289 | * Function to apply cart discounts after tax. |
||
2290 | * @deprecated Coupons can not be applied after tax |
||
2291 | */ |
||
2292 | public function apply_cart_discounts_after_tax( $values, $price ) { |
||
0 ignored issues
–
show
|
|||
2293 | _deprecated_function( 'apply_cart_discounts_after_tax', '2.3' ); |
||
2294 | } |
||
2295 | |||
2296 | /** |
||
2297 | * Function to apply product discounts after tax. |
||
2298 | * @deprecated Coupons can not be applied after tax |
||
2299 | */ |
||
2300 | public function apply_product_discounts_after_tax( $values, $price ) { |
||
0 ignored issues
–
show
|
|||
2301 | _deprecated_function( 'apply_product_discounts_after_tax', '2.3' ); |
||
2302 | } |
||
2303 | |||
2304 | /** |
||
2305 | * Gets the order discount amount - these are applied after tax. |
||
2306 | * @deprecated Coupons can not be applied after tax |
||
2307 | */ |
||
2308 | public function get_discounts_after_tax() { |
||
2309 | _deprecated_function( 'get_discounts_after_tax', '2.3' ); |
||
2310 | } |
||
2311 | } |
||
2312 |
The break statement is not necessary if it is preceded for example by a return statement:
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.