PageRenderTime 57ms CodeModel.GetById 15ms 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

Large files files are truncated, but you can click here to view the full file

  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;

Large files files are truncated, but you can click here to view the full file