woothemes /
woocommerce
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; |
||
| 126 | case 'round_at_subtotal' : |
||
| 127 | return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ); |
||
| 128 | break; |
||
| 129 | case 'tax_display_cart' : |
||
| 130 | return get_option( 'woocommerce_tax_display_cart' ); |
||
| 131 | break; |
||
| 132 | case 'dp' : |
||
| 133 | return wc_get_price_decimals(); |
||
| 134 | break; |
||
| 135 | case 'display_totals_ex_tax' : |
||
| 136 | case 'display_cart_ex_tax' : |
||
| 137 | return $this->tax_display_cart === 'excl'; |
||
| 138 | break; |
||
| 139 | case 'cart_contents_weight' : |
||
| 140 | return $this->get_cart_contents_weight(); |
||
| 141 | break; |
||
| 142 | case 'cart_contents_count' : |
||
| 143 | return $this->get_cart_contents_count(); |
||
| 144 | break; |
||
| 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() ) { |
|
| 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 ] ) ) { |
|
| 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 ) ) { |
|
| 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
|
|||
| 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 ) { |
|
| 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 ) { |
|
| 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;
}
Loading history...
|
|||
| 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;
}
Loading history...
|
|||
| 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;
}
Loading history...
|
|||
| 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;
}
Loading history...
|
|||
| 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;
Loading history...
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;
Loading history...
|
|||
| 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 ) { |
||
| 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 ) { |
|
| 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 ) { |
||
| 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;
Loading history...
|
|||
| 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 ) { |
|
| 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 ) { |
|
| 2216 | if ( ! $compound && WC_Tax::is_compound( $key ) ) continue; |
||
| 2217 | $total += $tax; |
||
| 2218 | } |
||
| 2219 | View Code Duplication | foreach ( $this->shipping_taxes as $key => $tax ) { |
|
| 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 ) { |
||
| 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 ) { |
||
| 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 |
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: