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