PageRenderTime 46ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/class-wc-cart.php

https://github.com/CammoKing/woocommerce
PHP | 2008 lines | 988 code | 449 blank | 571 comment | 269 complexity | 472e36020039f86050dc12655b3639a8 MD5 | raw file
Possible License(s): GPL-3.0
  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 1.6.4
  10. * @package WooCommerce/Classes
  11. * @author WooThemes
  12. */
  13. class WC_Cart {
  14. /** @var array Contains an array of cart items. */
  15. var $cart_contents;
  16. /** @var array Contains an array of coupons applied to the cart. */
  17. var $applied_coupons;
  18. /** @var float The total cost of the cart items. */
  19. var $cart_contents_total;
  20. /** @var float The total weight of the cart items. */
  21. var $cart_contents_weight;
  22. /** @var float The total count of the cart items. */
  23. var $cart_contents_count;
  24. /** @var float The total tax for the cart items. */
  25. var $cart_contents_tax;
  26. /** @var float Cart grand total. */
  27. var $total;
  28. /** @var float Cart subtotal. */
  29. var $subtotal;
  30. /** @var float Cart subtotal without tax. */
  31. var $subtotal_ex_tax;
  32. /** @var float Total cart tax. */
  33. var $tax_total;
  34. /** @var array An array of taxes/tax rates for the cart. */
  35. var $taxes;
  36. /** @var array An array of taxes/tax rates for the shipping. */
  37. var $shipping_taxes;
  38. /** @var float Discounts before tax. */
  39. var $discount_cart;
  40. /** @var float Discounts after tax. */
  41. var $discount_total;
  42. /** @var float Total for additonal fees. */
  43. var $fee_total;
  44. /** @var float Shipping cost. */
  45. var $shipping_total;
  46. /** @var float Shipping tax. */
  47. var $shipping_tax_total;
  48. /** @var float Shipping title/label. */
  49. var $shipping_label;
  50. /** @var WC_Tax */
  51. var $tax;
  52. /** @var array An array of fees. */
  53. var $fees;
  54. /**
  55. * Constructor for the cart class. Loads options and hooks in the init method.
  56. *
  57. * @access public
  58. * @return void
  59. */
  60. function __construct() {
  61. $this->tax = new WC_Tax();
  62. $this->prices_include_tax = ( get_option('woocommerce_prices_include_tax') == 'yes' ) ? true : false;
  63. $this->display_totals_ex_tax = ( get_option('woocommerce_display_totals_excluding_tax') == 'yes' ) ? true : false;
  64. $this->display_cart_ex_tax = ( get_option('woocommerce_display_cart_prices_excluding_tax') == 'yes' ) ? true : false;
  65. $this->dp = ( '' != get_option( 'woocommerce_price_num_decimals' ) ) ? get_option( 'woocommerce_price_num_decimals' ) : 0;
  66. add_action( 'init', array( &$this, 'init' ), 5 ); // Get cart on init
  67. }
  68. /**
  69. * Loads the cart data from the PHP session during WordPress init and hooks in other methods.
  70. *
  71. * @access public
  72. * @return void
  73. */
  74. function init() {
  75. $this->get_cart_from_session();
  76. add_action('woocommerce_check_cart_items', array( &$this, 'check_cart_items' ), 1 );
  77. add_action('woocommerce_check_cart_items', array( &$this, 'check_cart_coupons' ), 1 );
  78. add_action('woocommerce_after_checkout_validation', array( &$this, 'check_customer_coupons' ), 1 );
  79. }
  80. /*-----------------------------------------------------------------------------------*/
  81. /* Cart Session Handling */
  82. /*-----------------------------------------------------------------------------------*/
  83. /**
  84. * Get the cart data from the PHP session and store it in class variables.
  85. *
  86. * @access public
  87. * @return void
  88. */
  89. function get_cart_from_session() {
  90. global $woocommerce;
  91. // Load the coupons
  92. if ( get_option( 'woocommerce_enable_coupons' ) == 'yes' )
  93. $this->applied_coupons = ( empty( $woocommerce->session->coupons ) ) ? array() : array_unique( array_filter( (array) $woocommerce->session->coupons ) );
  94. // Load the cart
  95. if ( isset( $woocommerce->session->cart ) && is_array( $woocommerce->session->cart ) ) {
  96. $cart = $woocommerce->session->cart;
  97. foreach ( $cart as $key => $values ) {
  98. $_product = get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
  99. if ( $_product->exists() && $values['quantity'] > 0 ) {
  100. // Put session data into array. Run through filter so other plugins can load their own session data
  101. $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', array(
  102. 'product_id' => $values['product_id'],
  103. 'variation_id' => $values['variation_id'],
  104. 'variation' => $values['variation'],
  105. 'quantity' => $values['quantity'],
  106. 'data' => $_product
  107. ), $values, $key );
  108. }
  109. }
  110. if ( ! is_array( $this->cart_contents ) )
  111. $this->cart_contents = array();
  112. } else {
  113. $this->cart_contents = array();
  114. }
  115. // Cookie
  116. if ( sizeof( $this->cart_contents ) > 0 )
  117. $woocommerce->cart_has_contents_cookie( true );
  118. else
  119. $woocommerce->cart_has_contents_cookie( false );
  120. // Load totals
  121. $this->cart_contents_total = isset( $woocommerce->session->cart_contents_total ) ? $woocommerce->session->cart_contents_total : 0;
  122. $this->cart_contents_weight = isset( $woocommerce->session->cart_contents_weight ) ? $woocommerce->session->cart_contents_weight : 0;
  123. $this->cart_contents_count = isset( $woocommerce->session->cart_contents_count ) ? $woocommerce->session->cart_contents_count : 0;
  124. $this->cart_contents_tax = isset( $woocommerce->session->cart_contents_tax ) ? $woocommerce->session->cart_contents_tax : 0;
  125. $this->total = isset( $woocommerce->session->total ) ? $woocommerce->session->total : 0;
  126. $this->subtotal = isset( $woocommerce->session->subtotal ) ? $woocommerce->session->subtotal : 0;
  127. $this->subtotal_ex_tax = isset( $woocommerce->session->subtotal_ex_tax ) ? $woocommerce->session->subtotal_ex_tax : 0;
  128. $this->tax_total = isset( $woocommerce->session->tax_total ) ? $woocommerce->session->tax_total : 0;
  129. $this->taxes = isset( $woocommerce->session->taxes ) ? $woocommerce->session->taxes : array();
  130. $this->shipping_taxes = isset( $woocommerce->session->shipping_taxes ) ? $woocommerce->session->shipping_taxes : array();
  131. $this->discount_cart = isset( $woocommerce->session->discount_cart ) ? $woocommerce->session->discount_cart : 0;
  132. $this->discount_total = isset( $woocommerce->session->discount_total ) ? $woocommerce->session->discount_total : 0;
  133. $this->shipping_total = isset( $woocommerce->session->shipping_total ) ? $woocommerce->session->shipping_total : 0;
  134. $this->shipping_tax_total = isset( $woocommerce->session->shipping_tax_total ) ? $woocommerce->session->shipping_tax_total : 0;
  135. $this->shipping_label = isset( $woocommerce->session->shipping_label ) ? $woocommerce->session->shipping_label : '';
  136. // Queue re-calc if subtotal is not set
  137. if ( ! $this->subtotal && sizeof( $this->cart_contents ) > 0 )
  138. $this->set_session();
  139. }
  140. /**
  141. * Sets the php session data for the cart and coupons and re-calculates totals.
  142. *
  143. * @access public
  144. * @return void
  145. */
  146. function set_session() {
  147. global $woocommerce;
  148. // Re-calc totals
  149. $this->calculate_totals();
  150. // Set cart and coupon session data
  151. $cart_session = array();
  152. if ( $this->cart_contents ) {
  153. foreach ( $this->cart_contents as $key => $values ) {
  154. $cart_session[ $key ] = $values;
  155. // Unset product object
  156. unset( $cart_session[ $key ]['data'] );
  157. }
  158. }
  159. $woocommerce->session->cart = $cart_session;
  160. $woocommerce->session->coupons = $this->applied_coupons;
  161. // Store totals to avoid re-calc on page load
  162. $woocommerce->session->cart_contents_total = $this->cart_contents_total;
  163. $woocommerce->session->cart_contents_weight = $this->cart_contents_weight;
  164. $woocommerce->session->cart_contents_count = $this->cart_contents_count;
  165. $woocommerce->session->cart_contents_tax = $this->cart_contents_tax;
  166. $woocommerce->session->total = $this->total;
  167. $woocommerce->session->subtotal = $this->subtotal;
  168. $woocommerce->session->subtotal_ex_tax = $this->subtotal_ex_tax;
  169. $woocommerce->session->tax_total = $this->tax_total;
  170. $woocommerce->session->shipping_taxes = $this->shipping_taxes;
  171. $woocommerce->session->taxes = $this->taxes;
  172. $woocommerce->session->discount_cart = $this->discount_cart;
  173. $woocommerce->session->discount_total = $this->discount_total;
  174. $woocommerce->session->shipping_total = $this->shipping_total;
  175. $woocommerce->session->shipping_tax_total = $this->shipping_tax_total;
  176. $woocommerce->session->shipping_label = $this->shipping_label;
  177. if ( get_current_user_id() )
  178. $this->persistent_cart_update();
  179. do_action( 'woocommerce_cart_updated' );
  180. }
  181. /**
  182. * Empties the cart and optionally the persistent cart too.
  183. *
  184. * @access public
  185. * @param bool $clear_persistent_cart (default: true)
  186. * @return void
  187. */
  188. function empty_cart( $clear_persistent_cart = true ) {
  189. global $woocommerce;
  190. $this->cart_contents = array();
  191. $this->reset();
  192. unset( $woocommerce->session->order_awaiting_payment, $woocommerce->session->coupons, $woocommerce->session->cart );
  193. if ( $clear_persistent_cart && get_current_user_id() )
  194. $this->persistent_cart_destroy();
  195. do_action( 'woocommerce_cart_emptied' );
  196. }
  197. /*-----------------------------------------------------------------------------------*/
  198. /* Persistent cart handling */
  199. /*-----------------------------------------------------------------------------------*/
  200. /**
  201. * Save the persistent cart when the cart is updated.
  202. *
  203. * @access public
  204. * @return void
  205. */
  206. function persistent_cart_update() {
  207. global $woocommerce;
  208. update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', array(
  209. 'cart' => $woocommerce->session->cart,
  210. ) );
  211. }
  212. /**
  213. * Delete the persistent cart permanently.
  214. *
  215. * @access public
  216. * @return void
  217. */
  218. function persistent_cart_destroy() {
  219. delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart' );
  220. }
  221. /*-----------------------------------------------------------------------------------*/
  222. /* Cart Data Functions */
  223. /*-----------------------------------------------------------------------------------*/
  224. /**
  225. * Get number of items in the cart.
  226. *
  227. * @access public
  228. * @return int
  229. */
  230. function get_cart_contents_count() {
  231. return apply_filters( 'woocommerce_cart_contents_count', $this->cart_contents_count );
  232. }
  233. /**
  234. * Check all cart items for errors.
  235. *
  236. * @access public
  237. * @return void
  238. */
  239. function check_cart_items() {
  240. global $woocommerce;
  241. // Check item stock
  242. $result = $this->check_cart_item_stock();
  243. if (is_wp_error($result))
  244. $woocommerce->add_error( $result->get_error_message() );
  245. }
  246. /**
  247. * Check cart coupons for errors.
  248. *
  249. * @access public
  250. * @return void
  251. */
  252. function check_cart_coupons() {
  253. global $woocommerce;
  254. if ( ! empty( $this->applied_coupons ) ) {
  255. foreach ( $this->applied_coupons as $key => $code ) {
  256. $coupon = new WC_Coupon( $code );
  257. if ( is_wp_error( $coupon->is_valid() ) ) {
  258. $woocommerce->add_error( sprintf( __( 'Sorry, it seems the coupon "%s" is invalid - it has now been removed from your order.', 'woocommerce' ), $code ) );
  259. // Remove the coupon
  260. unset( $this->applied_coupons[ $key ] );
  261. $woocommerce->session->coupons = $this->applied_coupons;
  262. $woocommerce->session->refresh_totals = true;
  263. }
  264. }
  265. }
  266. }
  267. /**
  268. * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
  269. *
  270. * @access public
  271. * @return array
  272. */
  273. function get_cart_item_quantities() {
  274. $quantities = array();
  275. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  276. if ( $values['data']->managing_stock() ) {
  277. if ( $values['variation_id'] > 0 ) {
  278. if ( $values['data']->variation_has_stock ) {
  279. // Variation has stock levels defined so its handled individually
  280. $quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity'];
  281. } else {
  282. // Variation has no stock levels defined so use parents
  283. $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
  284. }
  285. } else {
  286. $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
  287. }
  288. }
  289. }
  290. return $quantities;
  291. }
  292. /**
  293. * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
  294. *
  295. * @access public
  296. * @param array $posted
  297. */
  298. function check_customer_coupons( $posted ) {
  299. global $woocommerce;
  300. if ( ! empty( $this->applied_coupons ) ) {
  301. foreach ( $this->applied_coupons as $key => $code ) {
  302. $coupon = new WC_Coupon( $code );
  303. if ( is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
  304. $coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email );
  305. if ( is_user_logged_in() ) {
  306. $current_user = wp_get_current_user();
  307. $check_emails[] = $current_user->user_email;
  308. }
  309. $check_emails[] = $posted['billing_email'];
  310. $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
  311. if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) {
  312. $woocommerce->add_error( sprintf( __( 'Sorry, it seems the coupon "%s" is not yours - it has now been removed from your order.', 'woocommerce' ), $code ) );
  313. // Remove the coupon
  314. unset( $this->applied_coupons[ $key ] );
  315. $woocommerce->session->coupons = $this->applied_coupons;
  316. $woocommerce->session->refresh_totals = true;
  317. }
  318. }
  319. }
  320. }
  321. }
  322. /**
  323. * Looks through the cart to check each item is in stock. If not, add an error.
  324. *
  325. * @access public
  326. * @return bool
  327. */
  328. function check_cart_item_stock() {
  329. $error = new WP_Error();
  330. $product_qty_in_cart = $this->get_cart_item_quantities();
  331. // First stock check loop
  332. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  333. $_product = $values['data'];
  334. /**
  335. * Check stock based on inventory
  336. */
  337. if ( $_product->managing_stock() ) {
  338. /**
  339. * Check the stock for this item individually
  340. */
  341. if ( ! $_product->is_in_stock() || ! $_product->has_enough_stock( $values['quantity'] ) ) {
  342. $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 ) );
  343. return $error;
  344. }
  345. /**
  346. * Next check entire cart quantities
  347. */
  348. if ( $values['variation_id'] && $_product->variation_has_stock && isset( $product_qty_in_cart[$values['variation_id']] ) ) {
  349. if ( ! $_product->has_enough_stock( $product_qty_in_cart[$values['variation_id']] ) ) {
  350. $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 ) );
  351. return $error;
  352. }
  353. } elseif ( isset( $product_qty_in_cart[$values['product_id']] ) ) {
  354. if ( ! $_product->has_enough_stock( $product_qty_in_cart[$values['product_id']] ) ) {
  355. $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 ) );
  356. return $error;
  357. }
  358. }
  359. /**
  360. * Check stock based on stock-status
  361. */
  362. } else {
  363. if ( ! $_product->is_in_stock() ) {
  364. $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfill your order. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title() ) );
  365. return $error;
  366. }
  367. }
  368. }
  369. return true;
  370. }
  371. /**
  372. * Gets and formats a list of cart item data + variations for display on the frontend.
  373. *
  374. * @access public
  375. * @param array $cart_item
  376. * @param bool $flat (default: false)
  377. * @return string
  378. */
  379. function get_item_data( $cart_item, $flat = false ) {
  380. global $woocommerce;
  381. $return = '';
  382. $has_data = false;
  383. if ( ! $flat ) $return .= '<dl class="variation">';
  384. // Variation data
  385. if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) {
  386. $variation_list = array();
  387. foreach ( $cart_item['variation'] as $name => $value ) {
  388. if ( ! $value ) continue;
  389. // If this is a term slug, get the term's nice name
  390. if ( taxonomy_exists( esc_attr( str_replace( 'attribute_', '', $name ) ) ) ) {
  391. $term = get_term_by( 'slug', $value, esc_attr( str_replace( 'attribute_', '', $name ) ) );
  392. if ( ! is_wp_error( $term ) && $term->name )
  393. $value = $term->name;
  394. } else {
  395. $value = ucfirst( $value );
  396. }
  397. if ( $flat )
  398. $variation_list[] = $woocommerce->attribute_label( str_replace( 'attribute_', '', $name ) ) . ': ' . $value;
  399. else
  400. $variation_list[] = '<dt>' . $woocommerce->attribute_label( str_replace( 'attribute_', '', $name ) ) . ':</dt><dd>' . $value . '</dd>';
  401. }
  402. if ($flat)
  403. $return .= implode( ", \n", $variation_list );
  404. else
  405. $return .= implode( '', $variation_list );
  406. $has_data = true;
  407. }
  408. // Other data - returned as array with name/value values
  409. $other_data = apply_filters( 'woocommerce_get_item_data', array(), $cart_item );
  410. if ( $other_data && is_array( $other_data ) && sizeof( $other_data ) > 0 ) {
  411. $data_list = array();
  412. foreach ($other_data as $data ) {
  413. // Set hidden to true to not display meta on cart.
  414. if ( empty( $data['hidden'] ) ) {
  415. $display_value = !empty($data['display']) ? $data['display'] : $data['value'];
  416. if ($flat)
  417. $data_list[] = $data['name'].': '.$display_value;
  418. else
  419. $data_list[] = '<dt>'.$data['name'].':</dt><dd>'.$display_value.'</dd>';
  420. }
  421. }
  422. if ($flat)
  423. $return .= implode(', ', $data_list);
  424. else
  425. $return .= implode('', $data_list);
  426. $has_data = true;
  427. }
  428. if ( ! $flat )
  429. $return .= '</dl>';
  430. if ( $has_data )
  431. return $return;
  432. }
  433. /**
  434. * Gets cross sells based on the items in the cart.
  435. *
  436. * @return array cross_sells (item ids)
  437. */
  438. function get_cross_sells() {
  439. $cross_sells = array();
  440. $in_cart = array();
  441. if ( sizeof( $this->cart_contents) > 0 ) {
  442. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  443. if ( $values['quantity'] > 0 ) {
  444. $cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells );
  445. $in_cart[] = $values['product_id'];
  446. }
  447. }
  448. }
  449. $cross_sells = array_diff( $cross_sells, $in_cart );
  450. return $cross_sells;
  451. }
  452. /**
  453. * Gets the url to the cart page.
  454. *
  455. * @return string url to page
  456. */
  457. function get_cart_url() {
  458. $cart_page_id = woocommerce_get_page_id('cart');
  459. if ( $cart_page_id ) return apply_filters( 'woocommerce_get_cart_url', get_permalink( $cart_page_id ) );
  460. }
  461. /**
  462. * Gets the url to the checkout page.
  463. *
  464. * @return string url to page
  465. */
  466. function get_checkout_url() {
  467. $checkout_page_id = woocommerce_get_page_id('checkout');
  468. if ( $checkout_page_id ) {
  469. if ( is_ssl() )
  470. return str_replace( 'http:', 'https:', get_permalink($checkout_page_id) );
  471. else
  472. return apply_filters( 'woocommerce_get_checkout_url', get_permalink($checkout_page_id) );
  473. }
  474. }
  475. /**
  476. * Gets the url to remove an item from the cart.
  477. *
  478. * @return string url to page
  479. */
  480. function get_remove_url( $cart_item_key ) {
  481. global $woocommerce;
  482. $cart_page_id = woocommerce_get_page_id('cart');
  483. if ($cart_page_id)
  484. return apply_filters( 'woocommerce_get_remove_url', $woocommerce->nonce_url( 'cart', add_query_arg( 'remove_item', $cart_item_key, get_permalink($cart_page_id) ) ) );
  485. }
  486. /**
  487. * Returns the contents of the cart in an array.
  488. *
  489. * @return array contents of the cart
  490. */
  491. function get_cart() {
  492. return array_filter( (array) $this->cart_contents );
  493. }
  494. /**
  495. * Returns the cart and shipping taxes, merged.
  496. *
  497. * @return array merged taxes
  498. */
  499. function get_taxes() {
  500. $merged_taxes = array();
  501. // Merge
  502. foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) {
  503. $merged_taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
  504. }
  505. return $merged_taxes;
  506. }
  507. /**
  508. * Returns the cart and shipping taxes, merged & formatted.
  509. *
  510. * @return array merged taxes
  511. */
  512. function get_formatted_taxes() {
  513. $taxes = $this->get_taxes();
  514. foreach ( $taxes as $key => $tax )
  515. if ( $tax > 0 )
  516. $taxes[ $key ] = woocommerce_price( $tax );
  517. return apply_filters( 'woocommerce_cart_formatted_taxes', $taxes, $this );
  518. }
  519. /*-----------------------------------------------------------------------------------*/
  520. /* Add to cart handling */
  521. /*-----------------------------------------------------------------------------------*/
  522. /**
  523. * Check if product is in the cart and return cart item key.
  524. *
  525. * Cart item key will be unique based on the item and its properties, such as variations.
  526. *
  527. * @param mixed id of product to find in the cart
  528. * @return string cart item key
  529. */
  530. function find_product_in_cart( $cart_id = false ) {
  531. if ( $cart_id !== false )
  532. foreach ( $this->cart_contents as $cart_item_key => $cart_item )
  533. if ( $cart_item_key == $cart_id )
  534. return $cart_item_key;
  535. }
  536. /**
  537. * Generate a unique ID for the cart item being added.
  538. *
  539. * @param int $product_id - id of the product the key is being generated for
  540. * @param int $variation_id of the product the key is being generated for
  541. * @param array $variation data for the cart item
  542. * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart
  543. * @return string cart item key
  544. */
  545. function generate_cart_id( $product_id, $variation_id = '', $variation = '', $cart_item_data = array() ) {
  546. $id_parts = array( $product_id );
  547. if ( $variation_id ) $id_parts[] = $variation_id;
  548. if ( is_array( $variation ) ) {
  549. $variation_key = '';
  550. foreach ( $variation as $key => $value ) {
  551. $variation_key .= trim( $key ) . trim( $value );
  552. }
  553. $id_parts[] = $variation_key;
  554. }
  555. if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
  556. $cart_item_data_key = '';
  557. foreach ( $cart_item_data as $key => $value ) {
  558. if ( is_array( $value ) ) $value = http_build_query( $value );
  559. $cart_item_data_key .= trim($key) . trim($value);
  560. }
  561. $id_parts[] = $cart_item_data_key;
  562. }
  563. return md5( implode( '_', $id_parts ) );
  564. }
  565. /**
  566. * Add a product to the cart.
  567. *
  568. * @param string $product_id contains the id of the product to add to the cart
  569. * @param string $quantity contains the quantity of the item to add
  570. * @param int $variation_id
  571. * @param array $variation attribute values
  572. * @param array $cart_item_data extra cart item data we want to pass into the item
  573. * @return bool
  574. */
  575. function add_to_cart( $product_id, $quantity = 1, $variation_id = '', $variation = '', $cart_item_data = array() ) {
  576. global $woocommerce;
  577. if ( $quantity <= 0 ) return false;
  578. // Load cart item data - may be added by other plugins
  579. $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id );
  580. // Generate a ID based on product ID, variation ID, variation data, and other cart item data
  581. $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
  582. // See if this product and its options is already in the cart
  583. $cart_item_key = $this->find_product_in_cart( $cart_id );
  584. $product_data = get_product( $variation_id ? $variation_id : $product_id );
  585. // Force quantity to 1 if sold individually
  586. if ( $product_data->is_sold_individually() )
  587. $quantity = 1;
  588. // Check product is_purchasable
  589. if ( ! $product_data->is_purchasable() ) {
  590. $woocommerce->add_error( sprintf( __( 'Sorry, &quot;%s&quot; cannot be purchased.', 'woocommerce' ), $product_data->get_title() ) );
  591. return false;
  592. }
  593. // Stock check - only check if we're managing stock and backorders are not allowed
  594. if ( ! $product_data->is_in_stock() ) {
  595. $woocommerce->add_error( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_title() ) );
  596. return false;
  597. } elseif ( ! $product_data->has_enough_stock( $quantity ) ) {
  598. $woocommerce->add_error( 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() ));
  599. return false;
  600. }
  601. // Downloadable/virtual qty check
  602. if ( $product_data->is_sold_individually() ) {
  603. $in_cart_quantity = $cart_item_key ? $this->cart_contents[$cart_item_key]['quantity'] : 0;
  604. // If its greater than 0, its already in the cart
  605. if ( $in_cart_quantity > 0 ) {
  606. $woocommerce->add_error( sprintf('<a href="%s" class="button">%s</a> %s', get_permalink(woocommerce_get_page_id('cart')), __( 'View Cart &rarr;', 'woocommerce' ), __( 'You already have this item in your cart.', 'woocommerce' ) ) );
  607. return false;
  608. }
  609. }
  610. // Stock check - this time accounting for whats already in-cart
  611. $product_qty_in_cart = $this->get_cart_item_quantities();
  612. if ( $product_data->managing_stock() ) {
  613. // Variations
  614. if ( $variation_id && $product_data->variation_has_stock ) {
  615. if ( isset( $product_qty_in_cart[ $variation_id ] ) && ! $product_data->has_enough_stock( $product_qty_in_cart[ $variation_id ] + $quantity ) ) {
  616. $woocommerce->add_error( sprintf(__( '<a href="%s" class="button">%s</a> You cannot add that amount to the cart &mdash; we have %s in stock and you already have %s in your cart.', 'woocommerce' ), get_permalink(woocommerce_get_page_id('cart')), __( 'View Cart &rarr;', 'woocommerce' ), $product_data->get_stock_quantity(), $product_qty_in_cart[ $variation_id ] ));
  617. return false;
  618. }
  619. // Products
  620. } else {
  621. if ( isset( $product_qty_in_cart[ $product_id ] ) && ! $product_data->has_enough_stock( $product_qty_in_cart[ $product_id ] + $quantity ) ) {
  622. $woocommerce->add_error( sprintf(__( '<a href="%s" class="button">%s</a> You cannot add that amount to the cart &mdash; we have %s in stock and you already have %s in your cart.', 'woocommerce' ), get_permalink(woocommerce_get_page_id('cart')), __( 'View Cart &rarr;', 'woocommerce' ), $product_data->get_stock_quantity(), $product_qty_in_cart[ $product_id ] ));
  623. return false;
  624. }
  625. }
  626. }
  627. // If cart_item_key is set, the item is already in the cart
  628. if ( $cart_item_key ) {
  629. $new_quantity = $quantity + $this->cart_contents[$cart_item_key]['quantity'];
  630. $this->set_quantity( $cart_item_key, $new_quantity );
  631. } else {
  632. $cart_item_key = $cart_id;
  633. // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item
  634. $this->cart_contents[$cart_item_key] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array(
  635. 'product_id' => $product_id,
  636. 'variation_id' => $variation_id,
  637. 'variation' => $variation,
  638. 'quantity' => $quantity,
  639. 'data' => $product_data
  640. ) ), $cart_item_key );
  641. }
  642. do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
  643. $woocommerce->cart_has_contents_cookie( true );
  644. $this->set_session();
  645. return true;
  646. }
  647. /**
  648. * Set the quantity for an item in the cart.
  649. *
  650. * @param string cart_item_key contains the id of the cart item
  651. * @param string quantity contains the quantity of the item
  652. */
  653. function set_quantity( $cart_item_key, $quantity = 1 ) {
  654. if ( $quantity == 0 || $quantity < 0 ) {
  655. do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
  656. unset( $this->cart_contents[$cart_item_key] );
  657. } else {
  658. $this->cart_contents[$cart_item_key]['quantity'] = $quantity;
  659. do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity );
  660. }
  661. $this->set_session();
  662. }
  663. /*-----------------------------------------------------------------------------------*/
  664. /* Cart Calculation Functions */
  665. /*-----------------------------------------------------------------------------------*/
  666. /**
  667. * Reset cart totals and clear sessions.
  668. *
  669. * @access private
  670. * @return void
  671. */
  672. private function reset() {
  673. global $woocommerce;
  674. $this->total = $this->cart_contents_total = $this->cart_contents_weight = $this->cart_contents_count = $this->cart_contents_tax = $this->tax_total = $this->shipping_tax_total = $this->subtotal = $this->subtotal_ex_tax = $this->discount_total = $this->discount_cart = $this->shipping_total = $this->fee_total = 0;
  675. $this->shipping_taxes = $this->taxes = array();
  676. unset( $woocommerce->session->cart_contents_total, $woocommerce->session->cart_contents_weight, $woocommerce->session->cart_contents_count, $woocommerce->session->cart_contents_tax, $woocommerce->session->total, $woocommerce->session->subtotal, $woocommerce->session->subtotal_ex_tax, $woocommerce->session->tax_total, $woocommerce->session->taxes, $woocommerce->session->shipping_taxes, $woocommerce->session->discount_cart, $woocommerce->session->discount_total, $woocommerce->session->shipping_total, $woocommerce->session->shipping_tax_total, $woocommerce->session->shipping_label );
  677. }
  678. /**
  679. * Function to apply discounts to a product and get the discounted price (before tax is applied).
  680. *
  681. * @access public
  682. * @param mixed $values
  683. * @param mixed $price
  684. * @param bool $add_totals (default: false)
  685. * @return float price
  686. */
  687. function get_discounted_price( $values, $price, $add_totals = false ) {
  688. if ( ! $price ) return $price;
  689. if ( ! empty( $this->applied_coupons ) ) {
  690. foreach ( $this->applied_coupons as $code ) {
  691. $coupon = new WC_Coupon( $code );
  692. if ( $coupon->apply_before_tax() && $coupon->is_valid() ) {
  693. switch ( $coupon->type ) {
  694. case "fixed_product" :
  695. case "percent_product" :
  696. $this_item_is_discounted = false;
  697. $product_cats = wp_get_post_terms( $values['product_id'], 'product_cat', array("fields" => "ids") );
  698. // Specific products get the discount
  699. if ( sizeof( $coupon->product_ids ) > 0 ) {
  700. if ( in_array( $values['product_id'], $coupon->product_ids ) || in_array( $values['variation_id'], $coupon->product_ids ) || in_array( $values['data']->get_parent(), $coupon->product_ids ) )
  701. $this_item_is_discounted = true;
  702. // Category discounts
  703. } elseif ( sizeof($coupon->product_categories ) > 0 ) {
  704. if ( sizeof( array_intersect( $product_cats, $coupon->product_categories ) ) > 0 )
  705. $this_item_is_discounted = true;
  706. } else {
  707. // No product ids - all items discounted
  708. $this_item_is_discounted = true;
  709. }
  710. // Specific product ID's excluded from the discount
  711. if ( sizeof( $coupon->exclude_product_ids ) > 0 )
  712. if ( in_array( $values['product_id'], $coupon->exclude_product_ids ) || in_array( $values['variation_id'], $coupon->exclude_product_ids ) || in_array( $values['data']->get_parent(), $coupon->exclude_product_ids ) )
  713. $this_item_is_discounted = false;
  714. // Specific categories excluded from the discount
  715. if ( sizeof( $coupon->exclude_product_categories ) > 0 )
  716. if ( sizeof( array_intersect( $product_cats, $coupon->exclude_product_categories ) ) > 0 )
  717. $this_item_is_discounted = false;
  718. // Apply filter
  719. $this_item_is_discounted = apply_filters( 'woocommerce_item_is_discounted', $this_item_is_discounted, $values, $before_tax = true );
  720. // Apply the discount
  721. if ( $this_item_is_discounted ) {
  722. if ( $coupon->type=='fixed_product' ) {
  723. if ( $price < $coupon->amount ) {
  724. $discount_amount = $price;
  725. } else {
  726. $discount_amount = $coupon->amount;
  727. }
  728. $price = $price - $coupon->amount;
  729. if ( $price < 0 ) $price = 0;
  730. if ( $add_totals ) {
  731. $this->discount_cart = $this->discount_cart + ( $discount_amount * $values['quantity'] );
  732. }
  733. } elseif ( $coupon->type == 'percent_product' ) {
  734. $percent_discount = ( $values['data']->get_price_excluding_tax() / 100 ) * $coupon->amount;
  735. if ( $add_totals ) $this->discount_cart = $this->discount_cart + ( $percent_discount * $values['quantity'] );
  736. $price = $price - $percent_discount;
  737. }
  738. }
  739. break;
  740. case "fixed_cart" :
  741. /**
  742. * This is the most complex discount - we need to divide the discount between rows based on their price in
  743. * proportion to the subtotal. This is so rows with different tax rates get a fair discount, and so rows
  744. * with no price (free) don't get discount too.
  745. */
  746. // Get item discount by dividing item cost by subtotal to get a %
  747. if ( $this->subtotal_ex_tax )
  748. $discount_percent = ( $values['data']->get_price_excluding_tax()*$values['quantity'] ) / $this->subtotal_ex_tax;
  749. else
  750. $discount_percent = 0;
  751. // Use pence to help prevent rounding errors
  752. $coupon_amount_pence = $coupon->amount * 100;
  753. // Work out the discount for the row
  754. $item_discount = $coupon_amount_pence * $discount_percent;
  755. // Work out discount per item
  756. $item_discount = $item_discount / $values['quantity'];
  757. // Pence
  758. $price = ( $price * 100 );
  759. // Check if discount is more than price
  760. if ( $price < $item_discount )
  761. $discount_amount = $price;
  762. else
  763. $discount_amount = $item_discount;
  764. // Take discount off of price (in pence)
  765. $price = $price - $discount_amount;
  766. // Back to pounds
  767. $price = $price / 100;
  768. // Cannot be below 0
  769. if ( $price < 0 ) $price = 0;
  770. // Add coupon to discount total (once, since this is a fixed cart discount and we don't want rounding issues)
  771. if ( $add_totals ) $this->discount_cart = $this->discount_cart + ( ( $discount_amount * $values['quantity'] ) / 100 );
  772. break;
  773. case "percent" :
  774. $percent_discount = round( ( $values['data']->get_price() / 100 ) * $coupon->amount, $this->dp );
  775. if ( $add_totals )
  776. $this->discount_cart = $this->discount_cart + ( $percent_discount * $values['quantity'] );
  777. $price = $price - $percent_discount;
  778. break;
  779. }
  780. }
  781. }
  782. }
  783. return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this );
  784. }
  785. /**
  786. * Function to apply product discounts after tax.
  787. *
  788. * @access public
  789. * @param mixed $values
  790. * @param mixed $price
  791. */
  792. function apply_product_discounts_after_tax( $values, $price ) {
  793. if ( ! empty( $this->applied_coupons) ) {
  794. foreach ( $this->applied_coupons as $code ) {
  795. $coupon = new WC_Coupon( $code );
  796. do_action( 'woocommerce_product_discount_after_tax_' . $coupon->type, $coupon, $values, $price );
  797. if ( $coupon->type != 'fixed_product' && $coupon->type != 'percent_product' ) continue;
  798. if ( !$coupon->apply_before_tax() && $coupon->is_valid() ) {
  799. $product_cats = wp_get_post_terms( $values['product_id'], 'product_cat', array("fields" => "ids") );
  800. $this_item_is_discounted = false;
  801. // Specific products get the discount
  802. if ( sizeof( $coupon->product_ids ) > 0 ) {
  803. if (in_array($values['product_id'], $coupon->product_ids) || in_array($values['variation_id'], $coupon->product_ids) || in_array($values['data']->get_parent(), $coupon->product_ids))
  804. $this_item_is_discounted = true;
  805. // Category discounts
  806. } elseif ( sizeof( $coupon->product_categories ) > 0 ) {
  807. if ( sizeof( array_intersect( $product_cats, $coupon->product_categories ) ) > 0 )
  808. $this_item_is_discounted = true;
  809. } else {
  810. // No product ids - all items discounted
  811. $this_item_is_discounted = true;
  812. }
  813. // Specific product ID's excluded from the discount
  814. if ( sizeof( $coupon->exclude_product_ids ) > 0 )
  815. if ( in_array( $values['product_id'], $coupon->exclude_product_ids ) || in_array( $values['variation_id'], $coupon->exclude_product_ids ) || in_array( $values['data']->get_parent(), $coupon->exclude_product_ids ) )
  816. $this_item_is_discounted = false;
  817. // Specific categories excluded from the discount
  818. if ( sizeof( $coupon->exclude_product_categories ) > 0 )
  819. if ( sizeof( array_intersect( $product_cats, $coupon->exclude_product_categories ) ) > 0 )
  820. $this_item_is_discounted = false;
  821. // Apply filter
  822. $this_item_is_discounted = apply_filters( 'woocommerce_item_is_discounted', $this_item_is_discounted, $values, $before_tax = false );
  823. // Apply the discount
  824. if ( $this_item_is_discounted ) {
  825. if ( $coupon->type == 'fixed_product' ) {
  826. if ( $price < $coupon->amount )
  827. $discount_amount = $price;
  828. else
  829. $discount_amount = $coupon->amount;
  830. $this->discount_total = $this->discount_total + ( $discount_amount * $values['quantity'] );
  831. } elseif ( $coupon->type == 'percent_product' ) {
  832. $this->discount_total = $this->discount_total + round( ( $price / 100 ) * $coupon->amount, $this->dp );
  833. }
  834. }
  835. }
  836. }
  837. }
  838. }
  839. /**
  840. * Function to apply cart discounts after tax.
  841. *
  842. * @access public
  843. */
  844. function apply_cart_discounts_after_tax() {
  845. if ( $this->applied_coupons ) {
  846. foreach ( $this->applied_coupons as $code ) {
  847. $coupon = new WC_Coupon( $code );
  848. do_action( 'woocommerce_cart_discount_after_tax_' . $coupon->type, $coupon );
  849. if ( !$coupon->apply_before_tax() && $coupon->is_valid() ) {
  850. switch ( $coupon->type ) {
  851. case "fixed_cart" :
  852. $this->discount_total = $this->discount_total + $coupon->amount;
  853. break;
  854. case "percent" :
  855. $percent_discount = ( round( $this->cart_contents_total + $this->tax_total, $this->dp ) / 100 ) * $coupon->amount;
  856. $this->discount_total = $this->discount_total + round( $percent_discount, $this->dp );
  857. break;
  858. }
  859. }
  860. }
  861. }
  862. }
  863. /**
  864. * Calculate totals for the items in the cart.
  865. *
  866. * @access public
  867. */
  868. function calculate_totals() {
  869. global $woocommerce;
  870. $this->reset();
  871. do_action( 'woocommerce_before_calculate_totals', $this );
  872. // Get count of all items + weights + subtotal (we may need this for discounts)
  873. if ( sizeof( $this->cart_contents ) > 0 ) {
  874. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  875. $_product = $values['data'];
  876. $this->cart_contents_weight = $this->cart_contents_weight + ( $_product->get_weight() * $values['quantity'] );
  877. $this->cart_contents_count = $this->cart_contents_count + $values['quantity'];
  878. // Base Price (inclusive of tax for now)
  879. $row_base_price = $_product->get_price() * $values['quantity'];
  880. $base_tax_rates = $this->tax->get_shop_base_rate( $_product->tax_class );
  881. $tax_amount = 0;
  882. if ( $this->prices_include_tax ) {
  883. if ( $_product->is_taxable() ) {
  884. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  885. // ADJUST BASE if tax rate is different (different region or modified tax class)
  886. if ( $tax_rates !== $base_tax_rates ) {
  887. $base_taxes = $this->tax->calc_tax( $row_base_price, $base_tax_rates, true, true );
  888. $modded_taxes = $this->tax->calc_tax( $row_base_price - array_sum( $base_taxes ), $tax_rates, false );
  889. $row_base_price = ( $row_base_price - array_sum( $base_taxes ) ) + array_sum( $modded_taxes );
  890. }
  891. $taxes = $this->tax->calc_tax( $row_base_price, $tax_rates, true );
  892. $tax_amount = $this->tax->get_tax_total( $taxes );
  893. }
  894. // Sub total is based on base prices (without discounts)
  895. $this->subtotal = $this->subtotal + $row_base_price;
  896. $this->subtotal_ex_tax = $this->subtotal_ex_tax + ( $row_base_price - $tax_amount);
  897. } else {
  898. if ( $_product->is_taxable() ) {
  899. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  900. $taxes = $this->tax->calc_tax( $row_base_price, $tax_rates, false );
  901. $tax_amount = $this->tax->get_tax_total( $taxes );
  902. }
  903. // Sub total is based on base prices (without discounts)
  904. $this->subtotal = $this->subtotal + $row_base_price + $tax_amount;
  905. $this->subtotal_ex_tax = $this->subtotal_ex_tax + $row_base_price;
  906. }
  907. }
  908. }
  909. // Now calc the main totals, including discounts
  910. if ( $this->prices_include_tax ) {
  911. /**
  912. * Calculate totals for items
  913. */
  914. if ( sizeof($this->cart_contents) > 0 ) {
  915. foreach ($this->cart_contents as $cart_item_key => $values ) {
  916. /**
  917. * Prices include tax
  918. *
  919. * To prevent rounding issues we need to work with the inclusive price where possible
  920. * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would
  921. * be 8.325 leading to totals being 1p off
  922. *
  923. * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated
  924. * afterwards.
  925. *
  926. * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that
  927. *
  928. * Used this excellent article for reference:
  929. * http://developer.practicalecommerce.com/articles/1473-Coding-for-Tax-Calculations-Everything-You-Never-Wanted-to-Know-Part-2
  930. */
  931. $_product = $values['data'];
  932. // Base Price (inlusive of tax for now)
  933. $base_price = $_product->get_price();
  934. // Base Price Adjustment
  935. if ( $_product->is_taxable() ) {
  936. // Get rates
  937. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  938. /**
  939. * ADJUST TAX - Calculations when customer is OUTSIDE the shop base country/state and prices INCLUDE tax
  940. * OR
  941. * ADJUST TAX - Calculations when a tax class is modified
  942. */
  943. if ( ( $woocommerce->customer->is_customer_outside_base() && ( defined('WOOCOMMERCE_CHECKOUT') || $woocommerce->customer->has_calculated_shipping() ) ) || ( $_product->get_tax_class() !== $_product->tax_class ) ) {
  944. // Get tax rate for the store base, ensuring we use the unmodified tax_class for the product
  945. $base_tax_rates = $this->tax->get_shop_base_rate( $_product->tax_class );
  946. // Work out new price based on region
  947. $row_base_price = $base_price * $values['quantity'];
  948. $base_taxes = $this->tax->calc_tax( $row_base_price, $base_tax_rates, true, true ); // Unrounded
  949. $taxes = $this->tax->calc_tax( $row_base_price - array_sum($base_taxes), $tax_rates, false );
  950. // Tax amount
  951. $tax_amount = array_sum( $taxes );
  952. // Line subtotal + tax
  953. $line_subtotal_tax = get_option('woocommerce_tax_round_at_subtotal') == 'no' ? round( $tax_amount, $this->dp ) : $tax_amount;
  954. $line_subtotal = $row_base_price - $this->tax->get_tax_total( $base_taxes );
  955. // Adjusted price
  956. $adjusted_price = ( $row_base_price - array_sum( $base_taxes ) + array_sum( $taxes ) ) / $values['quantity'];
  957. // Apply discounts
  958. $discounted_price = $this->get_discounted_price( $values, $adjusted_price, true );
  959. $discounted_taxes = $this->tax->calc_tax( $discounted_price * $values['quantity'], $tax_rates, true );
  960. $discounted_tax_amount = array_sum( $discounted_taxes ); // Sum taxes
  961. /**
  962. * Regular tax calculation (customer inside base and the tax class is unmodified
  963. */
  964. } else {
  965. // Base tax for line before discount - we will store this in the order data
  966. $tax_amount = array_sum( $this->tax->calc_tax( $base_price * $values['quantity'], $tax_rates, true ) );
  967. // Line subtotal + tax
  968. $line_subtotal_tax = get_option('woocommerce_tax_round_at_subtotal') == 'no' ? round( $tax_amount, $this->dp ) : $tax_amount;
  969. $line_subtotal = ( $base_price * $values['quantity'] ) - round( $line_subtotal_tax, $this->dp );
  970. // Calc prices and tax (discounted)
  971. $discounted_price = $this->get_discounted_price( $values, $base_price, true );
  972. $discounted_taxes = $this->tax->calc_tax( $discounted_price * $values['quantity'], $tax_rates, true );
  973. $discounted_tax_amount = array_sum( $discounted_taxes ); // Sum taxes
  974. }
  975. // Tax rows - merge the totals we just got
  976. foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
  977. $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
  978. }
  979. } else {
  980. // Discounted Price (price with any pre-tax discounts applied)
  981. $discounted_price = $this->get_discounted_price( $values, $base_price, true );
  982. $discounted_tax_amount = 0;
  983. $tax_amount = 0;
  984. $line_subtotal_tax = 0;
  985. $line_subtotal = $base_price * $values['quantity'];
  986. }
  987. // Line prices
  988. $line_tax = get_option('woocommerce_tax_round_at_subtotal') == 'no' ? round( $discounted_tax_amount, $this->dp ) : $discounted_tax_amount;
  989. $line_total = round( ( $discounted_price * $values['quantity'] ) - round( $line_tax, $this->dp ), $this->dp );
  990. // Add any product discounts (after tax)
  991. $this->apply_product_discounts_after_tax( $values, $line_total + $discounted_tax_amount );
  992. // Cart contents total is based on discounted prices and is used for the final total calculation
  993. $this->cart_contents_total = $this->cart_contents_total + $line_total;
  994. // Store costs + taxes for lines
  995. $this->cart_contents[ $cart_item_key ]['line_total'] = $line_total;
  996. $this->cart_contents[ $cart_item_key ]['line_tax'] = $line_tax;
  997. $this->cart_contents[ $cart_item_key ]['line_subtotal'] = $line_subtotal;
  998. $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax;
  999. }
  1000. }
  1001. } else {
  1002. if ( sizeof( $this->cart_contents ) > 0 ) {
  1003. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  1004. /**
  1005. * Prices exclude tax
  1006. *
  1007. * This calculation is simpler - work with the base, untaxed price.
  1008. */
  1009. $_product = $values['data'];
  1010. // Base Price (i.e. no tax, regardless of region)
  1011. $base_price = $_product->get_price();
  1012. // Discounted Price (base price with any pre-tax discounts applied
  1013. $discounted_price = $this->get_discounted_price( $values, $base_price, true );
  1014. // Tax Amount (For the line, based on discounted, ex.tax price)
  1015. if ( $_product->is_taxable() ) {
  1016. // Get tax rates
  1017. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  1018. // Base tax for line before discount - we will store this in the order data
  1019. $tax_amount = array_sum( $this->tax->calc_tax( $base_price * $values['quantity'], $tax_rates, false ) );
  1020. // Now calc product rates
  1021. $discounted_taxes = $this->tax->calc_tax( $discounted_price * $values['quantity'], $tax_rates, false );
  1022. $discounted_tax_amount = array_sum( $discounted_taxes );
  1023. // Tax rows - merge the totals we just got
  1024. foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
  1025. $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
  1026. }
  1027. } else {
  1028. $discounted_tax_amount = 0;
  1029. $tax_amount = 0;
  1030. }
  1031. // Line prices
  1032. $line_subtotal_tax = $tax_amount;
  1033. $line_tax = $discounted_tax_amount;
  1034. $line_subtotal = $base_price * $values['quantity'];
  1035. $line_total = $discounted_price * $values['quantity'];
  1036. // Add any product discounts (after tax)
  1037. $this->apply_product_discounts_after_tax( $values, $line_total + $line_tax );
  1038. // Cart contents total is based on discounted prices and is used for the final total calculation
  1039. $this->cart_contents_total = $this->cart_contents_total + $line_total;
  1040. // Store costs + taxes for lines
  1041. $this->cart_contents[ $cart_item_key ]['line_total'] = $line_total;
  1042. $this->cart_contents[ $cart_item_key ]['line_tax'] = $line_tax;
  1043. $this->cart_contents[ $cart_item_key ]['line_subtotal'] = $line_subtotal;
  1044. $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax;
  1045. }
  1046. }
  1047. }
  1048. // Add fees
  1049. foreach ( $this->get_fees() as $fee ) {
  1050. $this->fee_total += $fee->amount;
  1051. if ( $fee->taxable ) {
  1052. // Get tax rates
  1053. $tax_rates = $this->tax->get_rates();
  1054. $fee_taxes = $this->tax->calc_tax( $fee->amount, $tax_rates, false );
  1055. // Store
  1056. $fee->tax = array_sum( $fee_taxes );
  1057. // Tax rows - merge the totals we just got
  1058. foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) {
  1059. $this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
  1060. }
  1061. }
  1062. }
  1063. // Set tax total to sum of all tax rows
  1064. $this->tax_total = $this->tax->get_tax_total( $this->taxes );
  1065. // VAT exemption done at this point - so all totals are correct before exemption
  1066. if ( $woocommerce->customer->is_vat_exempt() ) {
  1067. $this->shipping_tax_total = $this->tax_total = 0;
  1068. $this->taxes = $this->shipping_taxes = array();
  1069. foreach ( $this->cart_contents as $cart_item_key => $item )
  1070. $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $this->cart_contents[ $cart_item_key ]['line_tax'] = 0;
  1071. }
  1072. // Cart Discounts (after tax)
  1073. $this->apply_cart_discounts_after_tax();
  1074. // Only go beyond this point if on the cart/checkout
  1075. if ( ! is_checkout() && ! is_cart() && ! defined('WOOCOMMERCE_CHECKOUT') && ! defined('WOOCOMMERCE_CART') ) return;
  1076. // Cart Shipping
  1077. $this->calculate_shipping();
  1078. // VAT exemption for shipping
  1079. if ( $woocommerce->customer->is_vat_exempt() ) {
  1080. $this->shipping_tax_total = 0;
  1081. $this->shipping_taxes = array();
  1082. }
  1083. // Round cart/shipping tax rows
  1084. $this->taxes = array_map( array( &$this->tax, 'round' ), $this->taxes );
  1085. $this->shipping_taxes = array_map( array( &$this->tax, 'round' ), $this->shipping_taxes );
  1086. // Allow plugins to hook and alter totals before final total is calculated
  1087. do_action( 'woocommerce_calculate_totals', $this );
  1088. /**
  1089. * Grand Total
  1090. *
  1091. * Based on discounted product prices, discounted tax, shipping cost + tax, and any discounts to be added after tax (e.g. store credit)
  1092. */
  1093. $this->total = apply_filters( 'woocommerce_calculated_total', number_format( $this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total - $this->discount_total + $this->fee_total, $this->dp, '.', '' ), $this );
  1094. if ( $this->total < 0 )
  1095. $this->total = 0;
  1096. }
  1097. /**
  1098. * looks at the totals to see if payment is actually required.
  1099. *
  1100. * @return bool
  1101. */
  1102. function needs_payment() {
  1103. $needs_payment = ( $this->total > 0 ) ? true : false;
  1104. return apply_filters( 'woocommerce_cart_needs_payment', $needs_payment, $this );
  1105. }
  1106. /*-----------------------------------------------------------------------------------*/
  1107. /* Shipping related functions */
  1108. /*-----------------------------------------------------------------------------------*/
  1109. /**
  1110. * Uses the shipping class to calculate shipping then gets the totals when its finished.
  1111. *
  1112. * @access public
  1113. * @return void
  1114. */
  1115. function calculate_shipping() {
  1116. global $woocommerce;
  1117. if ( $this->needs_shipping() && $this->show_shipping() ) {
  1118. $woocommerce->shipping->calculate_shipping( $this->get_shipping_packages() );
  1119. } else {
  1120. $woocommerce->shipping->reset_shipping();
  1121. }
  1122. // Get totals for the chosen shipping method
  1123. $this->shipping_total = $woocommerce->shipping->shipping_total; // Shipping Total
  1124. $this->shipping_label = $woocommerce->shipping->shipping_label; // Shipping Label
  1125. $this->shipping_taxes = $woocommerce->shipping->shipping_taxes; // Shipping Taxes
  1126. $this->shipping_tax_total = $this->tax->get_tax_total( $this->shipping_taxes ); // Shipping tax amount
  1127. }
  1128. /**
  1129. * Get packages to calculate shipping for.
  1130. *
  1131. * This lets us calculate costs for carts that are shipped to multiple locations.
  1132. *
  1133. * Shipping methods are responsble for looping through these packages.
  1134. *
  1135. * By default we pass the cart itself as a package - plugins can change this
  1136. * through the filter and break it up.
  1137. *
  1138. * @since 1.5.4
  1139. * @access public
  1140. * @return array of cart items
  1141. */
  1142. function get_shipping_packages() {
  1143. global $woocommerce;
  1144. // Packages array for storing 'carts'
  1145. $packages = array();
  1146. $packages[0]['contents'] = $this->get_cart(); // Items in the package
  1147. $packages[0]['contents_cost'] = 0; // Cost of items in the package, set below
  1148. $packages[0]['applied_coupons'] = $this->applied_coupons; // Applied coupons - some, like free shipping, affect costs
  1149. $packages[0]['destination']['country'] = $woocommerce->customer->get_shipping_country();
  1150. $packages[0]['destination']['state'] = $woocommerce->customer->get_shipping_state();
  1151. $packages[0]['destination']['postcode'] = $woocommerce->customer->get_shipping_postcode();
  1152. $packages[0]['destination']['city'] = $woocommerce->customer->get_shipping_city();
  1153. foreach ( $this->get_cart() as $item )
  1154. if ( $item['data']->needs_shipping() )
  1155. $packages[0]['contents_cost'] += $item['line_total'];
  1156. return apply_filters( 'woocommerce_cart_shipping_packages', $packages );
  1157. }
  1158. /**
  1159. * Looks through the cart to see if shipping is actually required.
  1160. *
  1161. * @return bool whether or not the cart needs shipping
  1162. */
  1163. function needs_shipping() {
  1164. if ( get_option('woocommerce_calc_shipping')=='no' ) return false;
  1165. if ( ! is_array( $this->cart_contents ) ) return false;
  1166. $needs_shipping = false;
  1167. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  1168. $_product = $values['data'];
  1169. if ( $_product->needs_shipping() ) {
  1170. $needs_shipping = true;
  1171. }
  1172. }
  1173. return apply_filters( 'woocomerce_cart_needs_shipping', $needs_shipping );
  1174. }
  1175. /**
  1176. * Sees if the customer has entered enough data to calc the shipping yet.
  1177. *
  1178. * @return bool
  1179. */
  1180. function show_shipping() {
  1181. global $woocommerce;
  1182. if ( get_option('woocommerce_calc_shipping')=='no' ) return false;
  1183. if ( ! is_array( $this->cart_contents ) ) return false;
  1184. if ( get_option( 'woocommerce_shipping_cost_requires_address' ) == 'yes' ) {
  1185. if ( ! $woocommerce->customer->has_calculated_shipping() ) {
  1186. if ( ! $woocommerce->customer->get_shipping_country() || ( ! $woocommerce->customer->get_shipping_state() && ! $woocommerce->customer->get_shipping_postcode() ) ) return false;
  1187. }
  1188. }
  1189. $show_shipping = true;
  1190. return apply_filters( 'woocomerce_cart_ready_to_calc_shipping', $show_shipping );
  1191. }
  1192. /**
  1193. * Sees if we need a shipping address.
  1194. *
  1195. * @return bool
  1196. */
  1197. function ship_to_billing_address_only() {
  1198. if ( get_option('woocommerce_ship_to_billing_address_only') == 'yes' ) return true; else return false;
  1199. }
  1200. /**
  1201. * Gets the shipping total (after calculation).
  1202. *
  1203. * @return mixed price or string for the shipping total
  1204. */
  1205. function get_cart_shipping_total() {
  1206. global $woocommerce;
  1207. if ( isset( $this->shipping_label ) ) {
  1208. if ( $this->shipping_total > 0 ) {
  1209. // Display ex tax if the option is set, or prices exclude tax
  1210. if ( $this->display_totals_ex_tax || !$this->prices_include_tax ) {
  1211. $return = woocommerce_price( $this->shipping_total );
  1212. if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) {
  1213. $return .= ' <small>' . $woocommerce->countries->ex_tax_or_vat() . '</small>';
  1214. }
  1215. return $return;
  1216. } else {
  1217. $return = woocommerce_price( $this->shipping_total + $this->shipping_tax_total );
  1218. if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) {
  1219. $return .= ' <small>' . $woocommerce->countries->inc_tax_or_vat() . '</small>';
  1220. }
  1221. return $return;
  1222. }
  1223. } else {
  1224. return __( 'Free!', 'woocommerce' );
  1225. }
  1226. }
  1227. }
  1228. /**
  1229. * Gets title of the chosen shipping method.
  1230. *
  1231. * @return string shipping method title
  1232. */
  1233. function get_cart_shipping_title() {
  1234. if ( isset( $this->shipping_label ) ) {
  1235. return __( 'via', 'woocommerce' ) . ' ' . $this->shipping_label;
  1236. }
  1237. return false;
  1238. }
  1239. /*-----------------------------------------------------------------------------------*/
  1240. /* Coupons/Discount related functions */
  1241. /*-----------------------------------------------------------------------------------*/
  1242. /**
  1243. * Returns whether or not a discount has been applied.
  1244. *
  1245. * @return bool
  1246. */
  1247. function has_discount( $code ) {
  1248. if ( in_array( $code, $this->applied_coupons ) ) return true;
  1249. return false;
  1250. }
  1251. /**
  1252. * Applies a coupon code passed to the method.
  1253. *
  1254. * @param string $coupon_code - The code to apply
  1255. * @return bool True if the coupon is applied, false if it does not exist or cannot be applied
  1256. */
  1257. function add_discount( $coupon_code ) {
  1258. global $woocommerce;
  1259. // Coupons are globally disabled
  1260. if ( get_option('woocommerce_enable_coupons') == 'no' ) return false;
  1261. $the_coupon = new WC_Coupon( $coupon_code );
  1262. if ( $the_coupon->id ) {
  1263. // Check it can be used with cart
  1264. $return = $the_coupon->is_valid();
  1265. if ( ! $return || is_wp_error( $return ) ) {
  1266. $woocommerce->add_error( is_wp_error( $return ) ? $return->get_error_message() : __( 'Invalid coupon.', 'woocommerce' ) );
  1267. return false;
  1268. }
  1269. // Check if applied
  1270. if ( $woocommerce->cart->has_discount( $coupon_code ) ) {
  1271. $woocommerce->add_error( __( 'Discount code already applied!', 'woocommerce' ) );
  1272. return false;
  1273. }
  1274. // If its individual use then remove other coupons
  1275. if ( $the_coupon->individual_use == 'yes' ) {
  1276. $this->applied_coupons = array();
  1277. }
  1278. foreach ( $this->applied_coupons as $code ) {
  1279. $coupon = new WC_Coupon($code);
  1280. if ( $coupon->individual_use == 'yes' ) {
  1281. $this->applied_coupons = array();
  1282. }
  1283. }
  1284. $this->applied_coupons[] = $coupon_code;
  1285. // Choose free shipping
  1286. if ( $the_coupon->enable_free_shipping() ) {
  1287. $woocommerce->session->chosen_shipping_method = 'free_shipping';
  1288. }
  1289. $this->set_session();
  1290. $woocommerce->add_message( __( 'Discount code applied successfully.', 'woocommerce' ) );
  1291. do_action( 'woocommerce_applied_coupon', $coupon_code );
  1292. return true;
  1293. } else {
  1294. $woocommerce->add_error( __( 'Coupon does not exist!', 'woocommerce' ) );
  1295. return false;
  1296. }
  1297. return false;
  1298. }
  1299. /**
  1300. * Gets the array of applied coupon codes.
  1301. *
  1302. * @return array of applied coupons
  1303. */
  1304. function get_applied_coupons() {
  1305. return (array) $this->applied_coupons;
  1306. }
  1307. /**
  1308. * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
  1309. *
  1310. * @params int type - 0 for all, 1 for before tax, 2 for after tax
  1311. */
  1312. function remove_coupons( $type = 0 ) {
  1313. global $woocommerce;
  1314. if ( 1 == $type ) {
  1315. if ( $this->applied_coupons ) {
  1316. foreach ( $this->applied_coupons as $index => $code ) {
  1317. $coupon = new WC_Coupon( $code );
  1318. if ( $coupon->apply_before_tax() ) unset( $this->applied_coupons[ $index ] );
  1319. }
  1320. }
  1321. $woocommerce->session->coupons = $this->applied_coupons;
  1322. } elseif ( $type == 2 ) {
  1323. if ( $this->applied_coupons ) {
  1324. foreach ( $this->applied_coupons as $index => $code ) {
  1325. $coupon = new WC_Coupon( $code );
  1326. if ( ! $coupon->apply_before_tax() ) unset( $this->applied_coupons[ $index ] );
  1327. }
  1328. }
  1329. $woocommerce->session->coupons = $this->applied_coupons;
  1330. } else {
  1331. unset( $woocommerce->session->coupons );
  1332. $this->applied_coupons = array();
  1333. }
  1334. }
  1335. /*-----------------------------------------------------------------------------------*/
  1336. /* Fees API to add additonal costs to orders */
  1337. /*-----------------------------------------------------------------------------------*/
  1338. /**
  1339. * add_fee function.
  1340. *
  1341. * @access public
  1342. * @param mixed $name
  1343. * @param mixed $amount
  1344. * @param bool $taxable (default: false)
  1345. * @param string $tax_class (default: '')
  1346. * @return void
  1347. */
  1348. function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
  1349. if ( empty( $this->fees ) )
  1350. $this->fees = array();
  1351. $new_fee = new stdClass();
  1352. $new_fee->id = sanitize_title( $name );
  1353. $new_fee->name = esc_attr( $name );
  1354. $new_fee->amount = (float) esc_attr( $amount );
  1355. $new_fee->tax_class = $tax_class;
  1356. $new_fee->taxable = $taxable ? true : false;
  1357. $new_fee->tax = 0;
  1358. $this->fees[] = $new_fee;
  1359. }
  1360. /**
  1361. * get_fees function.
  1362. *
  1363. * @access public
  1364. * @return void
  1365. */
  1366. function get_fees() {
  1367. return (array) $this->fees;
  1368. }
  1369. /*-----------------------------------------------------------------------------------*/
  1370. /* Get Formatted Totals */
  1371. /*-----------------------------------------------------------------------------------*/
  1372. /**
  1373. * Get the total of all order discounts (after tax discounts).
  1374. *
  1375. * @return float
  1376. */
  1377. function get_order_discount_total() {
  1378. return $this->discount_total;
  1379. }
  1380. /**
  1381. * Get the total of all cart discounts (before tax discounts).
  1382. *
  1383. * @return float
  1384. */
  1385. function get_cart_discount_total() {
  1386. return $this->discount_cart;
  1387. }
  1388. /**
  1389. * Gets the order total (after calculation).
  1390. *
  1391. * @return string formatted price
  1392. */
  1393. function get_total() {
  1394. return apply_filters( 'woocommerce_cart_total', woocommerce_price( $this->total ) );
  1395. }
  1396. /**
  1397. * Gets the total excluding taxes.
  1398. *
  1399. * @return string formatted price
  1400. */
  1401. function get_total_ex_tax() {
  1402. $total = $this->total - $this->tax_total - $this->shipping_tax_total;
  1403. if ( $total < 0 ) $total = 0;
  1404. return apply_filters( 'woocommerce_cart_total_ex_tax', woocommerce_price( $total ) );
  1405. }
  1406. /**
  1407. * Gets the cart contents total (after calculation).
  1408. *
  1409. * @return string formatted price
  1410. */
  1411. function get_cart_total() {
  1412. if ( ! $this->prices_include_tax ) {
  1413. $cart_contents_total = woocommerce_price( $this->cart_contents_total );
  1414. } else {
  1415. $cart_contents_total = woocommerce_price( $this->cart_contents_total + $this->tax_total );
  1416. }
  1417. return apply_filters( 'woocommerce_cart_contents_total', $cart_contents_total );
  1418. }
  1419. /**
  1420. * Gets the sub total (after calculation).
  1421. *
  1422. * @params bool whether to include compound taxes
  1423. * @return string formatted price
  1424. */
  1425. function get_cart_subtotal( $compound = false ) {
  1426. global $woocommerce;
  1427. // If the cart has compound tax, we want to show the subtotal as
  1428. // cart + shipping + non-compound taxes (after discount)
  1429. if ( $compound ) {
  1430. $cart_subtotal = woocommerce_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false ) );
  1431. // Otherwise we show cart items totals only (before discount)
  1432. } else {
  1433. // Display ex tax if the option is set, or prices exclude tax
  1434. if ( $this->display_totals_ex_tax || ! $this->prices_include_tax || $woocommerce->customer->is_vat_exempt() ) {
  1435. $cart_subtotal = woocommerce_price( $this->subtotal_ex_tax );
  1436. if ( $this->tax_total > 0 && $this->prices_include_tax ) {
  1437. $cart_subtotal .= ' <small>' . $woocommerce->countries->ex_tax_or_vat() . '</small>';
  1438. }
  1439. } else {
  1440. $cart_subtotal = woocommerce_price( $this->subtotal );
  1441. if ( $this->tax_total > 0 && !$this->prices_include_tax ) {
  1442. $cart_subtotal .= ' <small>' . $woocommerce->countries->inc_tax_or_vat() . '</small>';
  1443. }
  1444. }
  1445. }
  1446. return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this );
  1447. }
  1448. /**
  1449. * Get the product row subtotal.
  1450. *
  1451. * Gets the tax etc to avoid rounding issues.
  1452. *
  1453. * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate
  1454. *
  1455. * @params object product
  1456. * @params int quantity
  1457. * @return string formatted price
  1458. */
  1459. function get_product_subtotal( $_product, $quantity ) {
  1460. global $woocommerce;
  1461. $price = $_product->get_price();
  1462. $taxable = $_product->is_taxable();
  1463. $base_tax_rates = $this->tax->get_shop_base_rate( $_product->tax_class );
  1464. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() ); // This will get the base rate unless we're on the checkout page
  1465. // Taxable
  1466. if ( $taxable ) {
  1467. if ( ( $this->display_cart_ex_tax || $woocommerce->customer->is_vat_exempt() ) && $this->prices_include_tax ) {
  1468. $base_taxes = $this->tax->calc_tax( $price * $quantity, $base_tax_rates, true );
  1469. $base_tax_amount = array_sum( $base_taxes );
  1470. $row_price = ( $price * $quantity ) - $base_tax_amount;
  1471. $product_subtotal = woocommerce_price( $row_price );
  1472. $product_subtotal .= ' <small class="tax_label">' . $woocommerce->countries->ex_tax_or_vat() . '</small>';
  1473. } elseif ( ! $this->display_cart_ex_tax && $tax_rates !== $base_tax_rates && $this->prices_include_tax ) {
  1474. $base_taxes = $this->tax->calc_tax( $price * $quantity, $base_tax_rates, true, true );
  1475. $modded_taxes = $this->tax->calc_tax( ( $price * $quantity ) - array_sum( $base_taxes ), $tax_rates, false );
  1476. $row_price = (( $price * $quantity ) - array_sum( $base_taxes )) + array_sum( $modded_taxes );
  1477. $product_subtotal = woocommerce_price( $row_price );
  1478. if ( ! $this->prices_include_tax ) {
  1479. $product_subtotal .= ' <small class="tax_label">' . $woocommerce->countries->inc_tax_or_vat() . '</small>';
  1480. }
  1481. } else {
  1482. $row_price = $price * $quantity;
  1483. $product_subtotal = woocommerce_price( $row_price );
  1484. }
  1485. // Non taxable
  1486. } else {
  1487. $row_price = $price * $quantity;
  1488. $product_subtotal = woocommerce_price( $row_price );
  1489. }
  1490. return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $_product, $quantity, $this );
  1491. }
  1492. /**
  1493. * Gets the cart tax (after calculation).
  1494. *
  1495. * @return string formatted price
  1496. */
  1497. function get_cart_tax() {
  1498. $return = false;
  1499. $cart_total_tax = $this->tax_total + $this->shipping_tax_total;
  1500. if ( $cart_total_tax > 0 ) $return = woocommerce_price( $cart_total_tax );
  1501. return apply_filters( 'woocommerce_get_cart_tax', $return );
  1502. }
  1503. /**
  1504. * Get tax row amounts with or without compound taxes includes.
  1505. *
  1506. * @return float price
  1507. */
  1508. function get_taxes_total( $compound = true ) {
  1509. $total = 0;
  1510. foreach ( $this->taxes as $key => $tax ) {
  1511. if ( ! $compound && $this->tax->is_compound( $key ) ) continue;
  1512. $total += $tax;
  1513. }
  1514. foreach ( $this->shipping_taxes as $key => $tax ) {
  1515. if ( ! $compound && $this->tax->is_compound( $key ) ) continue;
  1516. $total += $tax;
  1517. }
  1518. return $total;
  1519. }
  1520. /**
  1521. * Gets the total (product) discount amount - these are applied before tax.
  1522. *
  1523. * @return mixed formatted price or false if there are none
  1524. */
  1525. function get_discounts_before_tax() {
  1526. if ( $this->discount_cart ) {
  1527. $discounts_before_tax = woocommerce_price( $this->discount_cart );
  1528. } else {
  1529. $discounts_before_tax = false;
  1530. }
  1531. return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this );
  1532. }
  1533. /**
  1534. * Gets the order discount amount - these are applied after tax.
  1535. *
  1536. * @return mixed formatted price or false if there are none
  1537. */
  1538. function get_discounts_after_tax() {
  1539. if ( $this->discount_total ) {
  1540. $discounts_after_tax = woocommerce_price( $this->discount_total );
  1541. } else {
  1542. $discounts_after_tax = false;
  1543. }
  1544. return apply_filters( 'woocommerce_cart_discounts_after_tax', $discounts_after_tax, $this );
  1545. }
  1546. /**
  1547. * Gets the total discount amount - both kinds.
  1548. *
  1549. * @return mixed formatted price or false if there are none
  1550. */
  1551. function get_total_discount() {
  1552. if ( $this->discount_total || $this->discount_cart ) {
  1553. $total_discount = woocommerce_price( $this->discount_total + $this->discount_cart );
  1554. } else {
  1555. $total_discount = false;
  1556. }
  1557. return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this );
  1558. }
  1559. }