PageRenderTime 275ms CodeModel.GetById 39ms RepoModel.GetById 3ms app.codeStats 1ms

/wordpress/wp-content/plugins/woocommerce/classes/class-wc-cart.php

https://github.com/hurricane-voronin/eshop
PHP | 2148 lines | 1064 code | 490 blank | 594 comment | 290 complexity | 076a08d8e08258d7d05bcd5258926b9e MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, AGPL-1.0, LGPL-2.1

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 2.0.0
  10. * @package WooCommerce/Classes
  11. * @category Class
  12. * @author WooThemes
  13. */
  14. class WC_Cart {
  15. /** @var array Contains an array of cart items. */
  16. public $cart_contents;
  17. /** @var array Contains an array of coupon codes applied to the cart. */
  18. public $applied_coupons;
  19. /** @var array Contains an array of coupon code discounts after they have been applied. */
  20. public $coupon_discount_amounts;
  21. /** @var float The total cost of the cart items. */
  22. public $cart_contents_total;
  23. /** @var float The total weight of the cart items. */
  24. public $cart_contents_weight;
  25. /** @var float The total count of the cart items. */
  26. public $cart_contents_count;
  27. /** @var float The total tax for the cart items. */
  28. public $cart_contents_tax;
  29. /** @var float Cart grand total. */
  30. public $total;
  31. /** @var float Cart subtotal. */
  32. public $subtotal;
  33. /** @var float Cart subtotal without tax. */
  34. public $subtotal_ex_tax;
  35. /** @var float Total cart tax. */
  36. public $tax_total;
  37. /** @var array An array of taxes/tax rates for the cart. */
  38. public $taxes;
  39. /** @var array An array of taxes/tax rates for the shipping. */
  40. public $shipping_taxes;
  41. /** @var float Discounts before tax. */
  42. public $discount_cart;
  43. /** @var float Discounts after tax. */
  44. public $discount_total;
  45. /** @var float Total for additional fees. */
  46. public $fee_total;
  47. /** @var float Shipping cost. */
  48. public $shipping_total;
  49. /** @var float Shipping tax. */
  50. public $shipping_tax_total;
  51. /** @var float Shipping title/label. */
  52. public $shipping_label;
  53. /** @var WC_Tax */
  54. public $tax;
  55. /** @var array An array of fees. */
  56. public $fees;
  57. /**
  58. * Constructor for the cart class. Loads options and hooks in the init method.
  59. *
  60. * @access public
  61. * @return void
  62. */
  63. public function __construct() {
  64. $this->tax = new WC_Tax();
  65. $this->prices_include_tax = ( get_option( 'woocommerce_prices_include_tax' ) == 'yes' ) ? true : false;
  66. $this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' );
  67. $this->dp = (int) get_option( 'woocommerce_price_num_decimals' );
  68. $this->display_totals_ex_tax = $this->tax_display_cart == 'excl' ? true : false;
  69. $this->display_cart_ex_tax = $this->tax_display_cart == 'excl' ? true : false;
  70. add_action( 'init', array( $this, 'init' ), 5 ); // Get cart on init
  71. }
  72. /**
  73. * Loads the cart data from the PHP session during WordPress init and hooks in other methods.
  74. *
  75. * @access public
  76. * @return void
  77. */
  78. public function init() {
  79. $this->get_cart_from_session();
  80. add_action('woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
  81. add_action('woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
  82. add_action('woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 );
  83. }
  84. /*-----------------------------------------------------------------------------------*/
  85. /* Cart Session Handling */
  86. /*-----------------------------------------------------------------------------------*/
  87. /**
  88. * Get the cart data from the PHP session and store it in class variables.
  89. *
  90. * @access public
  91. * @return void
  92. */
  93. public function get_cart_from_session() {
  94. global $woocommerce;
  95. // Load the coupons
  96. $this->applied_coupons = ( empty( $woocommerce->session->coupon_codes ) ) ? array() : array_filter( (array) $woocommerce->session->coupon_codes );
  97. $this->coupon_discount_amounts = ( empty( $woocommerce->session->coupon_amounts ) ) ? array() : array_filter( (array) $woocommerce->session->coupon_amounts );
  98. // Load the cart
  99. if ( isset( $woocommerce->session->cart ) && is_array( $woocommerce->session->cart ) ) {
  100. $cart = $woocommerce->session->cart;
  101. foreach ( $cart as $key => $values ) {
  102. $_product = get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
  103. if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) {
  104. // Put session data into array. Run through filter so other plugins can load their own session data
  105. $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', array(
  106. 'product_id' => $values['product_id'],
  107. 'variation_id' => $values['variation_id'],
  108. 'variation' => $values['variation'],
  109. 'quantity' => $values['quantity'],
  110. 'data' => $_product
  111. ), $values, $key );
  112. }
  113. }
  114. do_action( 'woocommerce_cart_loaded_from_session', $this );
  115. if ( ! is_array( $this->cart_contents ) )
  116. $this->cart_contents = array();
  117. } else {
  118. $this->cart_contents = array();
  119. }
  120. // Cookie
  121. if ( sizeof( $this->cart_contents ) > 0 )
  122. $woocommerce->cart_has_contents_cookie( true );
  123. else
  124. $woocommerce->cart_has_contents_cookie( false );
  125. // Load totals
  126. $this->cart_contents_total = isset( $woocommerce->session->cart_contents_total ) ? $woocommerce->session->cart_contents_total : 0;
  127. $this->cart_contents_weight = isset( $woocommerce->session->cart_contents_weight ) ? $woocommerce->session->cart_contents_weight : 0;
  128. $this->cart_contents_count = isset( $woocommerce->session->cart_contents_count ) ? $woocommerce->session->cart_contents_count : 0;
  129. $this->cart_contents_tax = isset( $woocommerce->session->cart_contents_tax ) ? $woocommerce->session->cart_contents_tax : 0;
  130. $this->total = isset( $woocommerce->session->total ) ? $woocommerce->session->total : 0;
  131. $this->subtotal = isset( $woocommerce->session->subtotal ) ? $woocommerce->session->subtotal : 0;
  132. $this->subtotal_ex_tax = isset( $woocommerce->session->subtotal_ex_tax ) ? $woocommerce->session->subtotal_ex_tax : 0;
  133. $this->tax_total = isset( $woocommerce->session->tax_total ) ? $woocommerce->session->tax_total : 0;
  134. $this->taxes = isset( $woocommerce->session->taxes ) ? $woocommerce->session->taxes : array();
  135. $this->shipping_taxes = isset( $woocommerce->session->shipping_taxes ) ? $woocommerce->session->shipping_taxes : array();
  136. $this->discount_cart = isset( $woocommerce->session->discount_cart ) ? $woocommerce->session->discount_cart : 0;
  137. $this->discount_total = isset( $woocommerce->session->discount_total ) ? $woocommerce->session->discount_total : 0;
  138. $this->shipping_total = isset( $woocommerce->session->shipping_total ) ? $woocommerce->session->shipping_total : 0;
  139. $this->shipping_tax_total = isset( $woocommerce->session->shipping_tax_total ) ? $woocommerce->session->shipping_tax_total : 0;
  140. $this->shipping_label = isset( $woocommerce->session->shipping_label ) ? $woocommerce->session->shipping_label : '';
  141. // Queue re-calc if subtotal is not set
  142. if ( ! $this->subtotal && sizeof( $this->cart_contents ) > 0 )
  143. $this->calculate_totals();
  144. }
  145. /**
  146. * Sets the php session data for the cart and coupons.
  147. *
  148. * @access public
  149. * @return void
  150. */
  151. public function set_session() {
  152. global $woocommerce;
  153. // Set cart and coupon session data
  154. $cart_session = array();
  155. if ( $this->cart_contents ) {
  156. foreach ( $this->cart_contents as $key => $values ) {
  157. $cart_session[ $key ] = $values;
  158. // Unset product object
  159. unset( $cart_session[ $key ]['data'] );
  160. }
  161. }
  162. $woocommerce->session->cart = $cart_session;
  163. $woocommerce->session->coupon_codes = $this->applied_coupons;
  164. $woocommerce->session->coupon_amounts = $this->coupon_discount_amounts;
  165. // Store totals to avoid re-calc on page load
  166. $woocommerce->session->cart_contents_total = $this->cart_contents_total;
  167. $woocommerce->session->cart_contents_weight = $this->cart_contents_weight;
  168. $woocommerce->session->cart_contents_count = $this->cart_contents_count;
  169. $woocommerce->session->cart_contents_tax = $this->cart_contents_tax;
  170. $woocommerce->session->total = $this->total;
  171. $woocommerce->session->subtotal = $this->subtotal;
  172. $woocommerce->session->subtotal_ex_tax = $this->subtotal_ex_tax;
  173. $woocommerce->session->tax_total = $this->tax_total;
  174. $woocommerce->session->shipping_taxes = $this->shipping_taxes;
  175. $woocommerce->session->taxes = $this->taxes;
  176. $woocommerce->session->discount_cart = $this->discount_cart;
  177. $woocommerce->session->discount_total = $this->discount_total;
  178. $woocommerce->session->shipping_total = $this->shipping_total;
  179. $woocommerce->session->shipping_tax_total = $this->shipping_tax_total;
  180. $woocommerce->session->shipping_label = $this->shipping_label;
  181. if ( get_current_user_id() )
  182. $this->persistent_cart_update();
  183. do_action( 'woocommerce_cart_updated' );
  184. }
  185. /**
  186. * Empties the cart and optionally the persistent cart too.
  187. *
  188. * @access public
  189. * @param bool $clear_persistent_cart (default: true)
  190. * @return void
  191. */
  192. public function empty_cart( $clear_persistent_cart = true ) {
  193. global $woocommerce;
  194. $this->cart_contents = array();
  195. $this->reset();
  196. unset( $woocommerce->session->order_awaiting_payment, $woocommerce->session->coupon_codes, $woocommerce->session->coupon_amounts, $woocommerce->session->cart );
  197. if ( $clear_persistent_cart && get_current_user_id() )
  198. $this->persistent_cart_destroy();
  199. do_action( 'woocommerce_cart_emptied' );
  200. }
  201. /*-----------------------------------------------------------------------------------*/
  202. /* Persistent cart handling */
  203. /*-----------------------------------------------------------------------------------*/
  204. /**
  205. * Save the persistent cart when the cart is updated.
  206. *
  207. * @access public
  208. * @return void
  209. */
  210. public function persistent_cart_update() {
  211. global $woocommerce;
  212. update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', array(
  213. 'cart' => $woocommerce->session->cart,
  214. ) );
  215. }
  216. /**
  217. * Delete the persistent cart permanently.
  218. *
  219. * @access public
  220. * @return void
  221. */
  222. public function persistent_cart_destroy() {
  223. delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart' );
  224. }
  225. /*-----------------------------------------------------------------------------------*/
  226. /* Cart Data Functions */
  227. /*-----------------------------------------------------------------------------------*/
  228. /**
  229. * Coupons enabled function. Filterable.
  230. *
  231. * @access public
  232. * @return void
  233. */
  234. public function coupons_enabled() {
  235. $coupons_enabled = get_option( 'woocommerce_enable_coupons' ) == 'no' ? false : true;
  236. return apply_filters( 'woocommerce_coupons_enabled', $coupons_enabled );
  237. }
  238. /**
  239. * Get number of items in the cart.
  240. *
  241. * @access public
  242. * @return int
  243. */
  244. public function get_cart_contents_count() {
  245. return apply_filters( 'woocommerce_cart_contents_count', $this->cart_contents_count );
  246. }
  247. /**
  248. * Check all cart items for errors.
  249. *
  250. * @access public
  251. * @return void
  252. */
  253. public function check_cart_items() {
  254. global $woocommerce;
  255. // Check item stock
  256. $result = $this->check_cart_item_stock();
  257. if (is_wp_error($result))
  258. $woocommerce->add_error( $result->get_error_message() );
  259. }
  260. /**
  261. * Check cart coupons for errors.
  262. *
  263. * @access public
  264. * @return void
  265. */
  266. public function check_cart_coupons() {
  267. global $woocommerce;
  268. if ( ! empty( $this->applied_coupons ) ) {
  269. foreach ( $this->applied_coupons as $key => $code ) {
  270. $coupon = new WC_Coupon( $code );
  271. if ( is_wp_error( $coupon->is_valid() ) ) {
  272. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
  273. // Remove the coupon
  274. unset( $this->applied_coupons[ $key ] );
  275. $woocommerce->session->coupon_codes = $this->applied_coupons;
  276. $woocommerce->session->refresh_totals = true;
  277. }
  278. }
  279. }
  280. }
  281. /**
  282. * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
  283. *
  284. * @access public
  285. * @return array
  286. */
  287. public function get_cart_item_quantities() {
  288. $quantities = array();
  289. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  290. if ( $values['data']->managing_stock() ) {
  291. if ( $values['variation_id'] > 0 ) {
  292. if ( $values['data']->variation_has_stock ) {
  293. // Variation has stock levels defined so its handled individually
  294. $quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity'];
  295. } else {
  296. // Variation has no stock levels defined so use parents
  297. $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
  298. }
  299. } else {
  300. $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
  301. }
  302. }
  303. }
  304. return $quantities;
  305. }
  306. /**
  307. * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
  308. *
  309. * @access public
  310. * @param array $posted
  311. */
  312. public function check_customer_coupons( $posted ) {
  313. global $woocommerce;
  314. if ( ! empty( $this->applied_coupons ) ) {
  315. foreach ( $this->applied_coupons as $key => $code ) {
  316. $coupon = new WC_Coupon( $code );
  317. if ( ! is_wp_error( $coupon->is_valid() ) && is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
  318. $coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email );
  319. if ( is_user_logged_in() ) {
  320. $current_user = wp_get_current_user();
  321. $check_emails[] = $current_user->user_email;
  322. }
  323. $check_emails[] = $posted['billing_email'];
  324. $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
  325. if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) {
  326. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
  327. // Remove the coupon
  328. unset( $this->applied_coupons[ $key ] );
  329. $woocommerce->session->coupon_codes = $this->applied_coupons;
  330. $woocommerce->session->refresh_totals = true;
  331. }
  332. }
  333. }
  334. }
  335. }
  336. /**
  337. * Looks through the cart to check each item is in stock. If not, add an error.
  338. *
  339. * @access public
  340. * @return bool
  341. */
  342. public function check_cart_item_stock() {
  343. global $woocommerce, $wpdb;
  344. $error = new WP_Error();
  345. $product_qty_in_cart = $this->get_cart_item_quantities();
  346. // First stock check loop
  347. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  348. $_product = $values['data'];
  349. /**
  350. * Check stock based on inventory
  351. */
  352. if ( $_product->managing_stock() ) {
  353. /**
  354. * Check the stock for this item individually
  355. */
  356. if ( ! $_product->is_in_stock() || ! $_product->has_enough_stock( $values['quantity'] ) ) {
  357. $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 ) );
  358. return $error;
  359. }
  360. // For later on...
  361. $key = '_product_id';
  362. $value = $values['product_id'];
  363. $in_cart = $values['quantity'];
  364. /**
  365. * Next check entire cart quantities
  366. */
  367. if ( $values['variation_id'] && $_product->variation_has_stock && isset( $product_qty_in_cart[ $values['variation_id'] ] ) ) {
  368. $key = '_variation_id';
  369. $value = $values['variation_id'];
  370. $in_cart = $product_qty_in_cart[ $values['variation_id'] ];
  371. if ( ! $_product->has_enough_stock( $product_qty_in_cart[ $values['variation_id'] ] ) ) {
  372. $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfil your order (%s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), $_product->stock ) );
  373. return $error;
  374. }
  375. } elseif ( isset( $product_qty_in_cart[ $values['product_id'] ] ) ) {
  376. $in_cart = $product_qty_in_cart[ $values['product_id'] ];
  377. if ( ! $_product->has_enough_stock( $product_qty_in_cart[ $values['product_id'] ] ) ) {
  378. $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfil your order (%s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), $_product->stock ) );
  379. return $error;
  380. }
  381. }
  382. /**
  383. * Finally consider any held stock, from pending orders
  384. */
  385. if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $_product->backorders_allowed() ) {
  386. $order_id = isset( $woocommerce->session->order_awaiting_payment ) ? absint( $woocommerce->session->order_awaiting_payment ) : 0;
  387. $held_stock = $wpdb->get_var( $wpdb->prepare( "
  388. SELECT SUM( order_item_meta.meta_value ) AS held_qty
  389. FROM {$wpdb->posts} AS posts
  390. LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
  391. LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
  392. LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
  393. LEFT JOIN {$wpdb->term_relationships} AS rel ON posts.ID=rel.object_ID
  394. LEFT JOIN {$wpdb->term_taxonomy} AS tax USING( term_taxonomy_id )
  395. LEFT JOIN {$wpdb->terms} AS term USING( term_id )
  396. WHERE order_item_meta.meta_key = '_qty'
  397. AND order_item_meta2.meta_key = %s AND order_item_meta2.meta_value = %d
  398. AND posts.post_type = 'shop_order'
  399. AND posts.post_status = 'publish'
  400. AND tax.taxonomy = 'shop_order_status'
  401. AND term.slug IN ('pending')
  402. AND posts.ID != %d
  403. ", $key, $value, $order_id ) );
  404. if ( $_product->stock < ( $held_stock + $in_cart ) ) {
  405. $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfil your order right now. Please try again in %d minutes or edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), get_option( 'woocommerce_hold_stock_minutes' ) ) );
  406. return $error;
  407. }
  408. }
  409. /**
  410. * Check stock based on stock-status
  411. */
  412. } else {
  413. if ( ! $_product->is_in_stock() ) {
  414. $error->add( 'out-of-stock', sprintf(__( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title() ) );
  415. return $error;
  416. }
  417. }
  418. }
  419. return true;
  420. }
  421. /**
  422. * Gets and formats a list of cart item data + variations for display on the frontend.
  423. *
  424. * @access public
  425. * @param array $cart_item
  426. * @param bool $flat (default: false)
  427. * @return string
  428. */
  429. public function get_item_data( $cart_item, $flat = false ) {
  430. global $woocommerce;
  431. $return = '';
  432. $has_data = false;
  433. if ( ! $flat ) $return .= '<dl class="variation">';
  434. // Variation data
  435. if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) {
  436. $variation_list = array();
  437. foreach ( $cart_item['variation'] as $name => $value ) {
  438. if ( ! $value ) continue;
  439. // If this is a term slug, get the term's nice name
  440. if ( taxonomy_exists( esc_attr( str_replace( 'attribute_', '', $name ) ) ) ) {
  441. $term = get_term_by( 'slug', $value, esc_attr( str_replace( 'attribute_', '', $name ) ) );
  442. if ( ! is_wp_error( $term ) && $term->name )
  443. $value = $term->name;
  444. // If this is a custom option slug, get the options name
  445. } else {
  446. $value = apply_filters( 'woocommerce_variation_option_name', $value );
  447. }
  448. if ( $flat )
  449. $variation_list[] = $woocommerce->attribute_label( str_replace( 'attribute_', '', $name ) ) . ': ' . $value;
  450. else
  451. $variation_list[] = '<dt>' . $woocommerce->attribute_label( str_replace( 'attribute_', '', $name ) ) . ':</dt><dd>' . $value . '</dd>';
  452. }
  453. if ($flat)
  454. $return .= implode( ", \n", $variation_list );
  455. else
  456. $return .= implode( '', $variation_list );
  457. $has_data = true;
  458. }
  459. // Other data - returned as array with name/value values
  460. $other_data = apply_filters( 'woocommerce_get_item_data', array(), $cart_item );
  461. if ( $other_data && is_array( $other_data ) && sizeof( $other_data ) > 0 ) {
  462. $data_list = array();
  463. foreach ($other_data as $data ) {
  464. // Set hidden to true to not display meta on cart.
  465. if ( empty( $data['hidden'] ) ) {
  466. $display_value = !empty($data['display']) ? $data['display'] : $data['value'];
  467. if ($flat)
  468. $data_list[] = $data['name'].': '.$display_value;
  469. else
  470. $data_list[] = '<dt>'.$data['name'].':</dt><dd>'.$display_value.'</dd>';
  471. }
  472. }
  473. if ($flat)
  474. $return .= implode(', ', $data_list);
  475. else
  476. $return .= implode('', $data_list);
  477. $has_data = true;
  478. }
  479. if ( ! $flat )
  480. $return .= '</dl>';
  481. if ( $has_data )
  482. return $return;
  483. }
  484. /**
  485. * Gets cross sells based on the items in the cart.
  486. *
  487. * @return array cross_sells (item ids)
  488. */
  489. public function get_cross_sells() {
  490. $cross_sells = array();
  491. $in_cart = array();
  492. if ( sizeof( $this->cart_contents) > 0 ) {
  493. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  494. if ( $values['quantity'] > 0 ) {
  495. $cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells );
  496. $in_cart[] = $values['product_id'];
  497. }
  498. }
  499. }
  500. $cross_sells = array_diff( $cross_sells, $in_cart );
  501. return $cross_sells;
  502. }
  503. /**
  504. * Gets the url to the cart page.
  505. *
  506. * @return string url to page
  507. */
  508. public function get_cart_url() {
  509. $cart_page_id = woocommerce_get_page_id('cart');
  510. if ( $cart_page_id ) return apply_filters( 'woocommerce_get_cart_url', get_permalink( $cart_page_id ) );
  511. }
  512. /**
  513. * Gets the url to the checkout page.
  514. *
  515. * @return string url to page
  516. */
  517. public function get_checkout_url() {
  518. $checkout_page_id = woocommerce_get_page_id('checkout');
  519. if ( $checkout_page_id ) {
  520. if ( is_ssl() )
  521. return str_replace( 'http:', 'https:', get_permalink($checkout_page_id) );
  522. else
  523. return apply_filters( 'woocommerce_get_checkout_url', get_permalink($checkout_page_id) );
  524. }
  525. }
  526. /**
  527. * Gets the url to remove an item from the cart.
  528. *
  529. * @return string url to page
  530. */
  531. public function get_remove_url( $cart_item_key ) {
  532. global $woocommerce;
  533. $cart_page_id = woocommerce_get_page_id('cart');
  534. if ($cart_page_id)
  535. return apply_filters( 'woocommerce_get_remove_url', $woocommerce->nonce_url( 'cart', add_query_arg( 'remove_item', $cart_item_key, get_permalink($cart_page_id) ) ) );
  536. }
  537. /**
  538. * Returns the contents of the cart in an array.
  539. *
  540. * @return array contents of the cart
  541. */
  542. public function get_cart() {
  543. return array_filter( (array) $this->cart_contents );
  544. }
  545. /**
  546. * Returns the cart and shipping taxes, merged.
  547. *
  548. * @return array merged taxes
  549. */
  550. public function get_taxes() {
  551. $merged_taxes = array();
  552. // Merge
  553. foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) {
  554. $merged_taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
  555. }
  556. return $merged_taxes;
  557. }
  558. /**
  559. * Returns the cart and shipping taxes, merged & formatted.
  560. *
  561. * @return array merged taxes
  562. */
  563. public function get_formatted_taxes() {
  564. $taxes = $this->get_taxes();
  565. foreach ( $taxes as $key => $tax )
  566. if ( is_numeric( $tax ) )
  567. $taxes[ $key ] = woocommerce_price( $tax );
  568. return apply_filters( 'woocommerce_cart_formatted_taxes', $taxes, $this );
  569. }
  570. /*-----------------------------------------------------------------------------------*/
  571. /* Add to cart handling */
  572. /*-----------------------------------------------------------------------------------*/
  573. /**
  574. * Check if product is in the cart and return cart item key.
  575. *
  576. * Cart item key will be unique based on the item and its properties, such as variations.
  577. *
  578. * @param mixed id of product to find in the cart
  579. * @return string cart item key
  580. */
  581. public function find_product_in_cart( $cart_id = false ) {
  582. if ( $cart_id !== false )
  583. foreach ( $this->cart_contents as $cart_item_key => $cart_item )
  584. if ( $cart_item_key == $cart_id )
  585. return $cart_item_key;
  586. }
  587. /**
  588. * Generate a unique ID for the cart item being added.
  589. *
  590. * @param int $product_id - id of the product the key is being generated for
  591. * @param int $variation_id of the product the key is being generated for
  592. * @param array $variation data for the cart item
  593. * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart
  594. * @return string cart item key
  595. */
  596. public function generate_cart_id( $product_id, $variation_id = '', $variation = '', $cart_item_data = array() ) {
  597. $id_parts = array( $product_id );
  598. if ( $variation_id ) $id_parts[] = $variation_id;
  599. if ( is_array( $variation ) ) {
  600. $variation_key = '';
  601. foreach ( $variation as $key => $value ) {
  602. $variation_key .= trim( $key ) . trim( $value );
  603. }
  604. $id_parts[] = $variation_key;
  605. }
  606. if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
  607. $cart_item_data_key = '';
  608. foreach ( $cart_item_data as $key => $value ) {
  609. if ( is_array( $value ) ) $value = http_build_query( $value );
  610. $cart_item_data_key .= trim($key) . trim($value);
  611. }
  612. $id_parts[] = $cart_item_data_key;
  613. }
  614. return md5( implode( '_', $id_parts ) );
  615. }
  616. /**
  617. * Add a product to the cart.
  618. *
  619. * @param string $product_id contains the id of the product to add to the cart
  620. * @param string $quantity contains the quantity of the item to add
  621. * @param int $variation_id
  622. * @param array $variation attribute values
  623. * @param array $cart_item_data extra cart item data we want to pass into the item
  624. * @return bool
  625. */
  626. public function add_to_cart( $product_id, $quantity = 1, $variation_id = '', $variation = '', $cart_item_data = array() ) {
  627. global $woocommerce;
  628. if ( $quantity <= 0 ) return false;
  629. // Load cart item data - may be added by other plugins
  630. $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id );
  631. // Generate a ID based on product ID, variation ID, variation data, and other cart item data
  632. $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
  633. // See if this product and its options is already in the cart
  634. $cart_item_key = $this->find_product_in_cart( $cart_id );
  635. $product_data = get_product( $variation_id ? $variation_id : $product_id );
  636. if ( ! $product_data )
  637. return false;
  638. // Force quantity to 1 if sold individually
  639. if ( $product_data->is_sold_individually() )
  640. $quantity = 1;
  641. // Check product is_purchasable
  642. if ( ! $product_data->is_purchasable() ) {
  643. $woocommerce->add_error( sprintf( __( 'Sorry, &quot;%s&quot; cannot be purchased.', 'woocommerce' ), $product_data->get_title() ) );
  644. return false;
  645. }
  646. // Stock check - only check if we're managing stock and backorders are not allowed
  647. if ( ! $product_data->is_in_stock() ) {
  648. $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() ) );
  649. return false;
  650. } elseif ( ! $product_data->has_enough_stock( $quantity ) ) {
  651. $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() ));
  652. return false;
  653. }
  654. // Downloadable/virtual qty check
  655. if ( $product_data->is_sold_individually() ) {
  656. $in_cart_quantity = $cart_item_key ? $this->cart_contents[$cart_item_key]['quantity'] : 0;
  657. // If its greater than 0, its already in the cart
  658. if ( $in_cart_quantity > 0 ) {
  659. $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' ) ) );
  660. return false;
  661. }
  662. }
  663. // Stock check - this time accounting for whats already in-cart
  664. $product_qty_in_cart = $this->get_cart_item_quantities();
  665. if ( $product_data->managing_stock() ) {
  666. // Variations
  667. if ( $variation_id && $product_data->variation_has_stock ) {
  668. if ( isset( $product_qty_in_cart[ $variation_id ] ) && ! $product_data->has_enough_stock( $product_qty_in_cart[ $variation_id ] + $quantity ) ) {
  669. $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 ] ));
  670. return false;
  671. }
  672. // Products
  673. } else {
  674. if ( isset( $product_qty_in_cart[ $product_id ] ) && ! $product_data->has_enough_stock( $product_qty_in_cart[ $product_id ] + $quantity ) ) {
  675. $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 ] ));
  676. return false;
  677. }
  678. }
  679. }
  680. // If cart_item_key is set, the item is already in the cart
  681. if ( $cart_item_key ) {
  682. $new_quantity = $quantity + $this->cart_contents[$cart_item_key]['quantity'];
  683. $this->set_quantity( $cart_item_key, $new_quantity );
  684. } else {
  685. $cart_item_key = $cart_id;
  686. // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item
  687. $this->cart_contents[$cart_item_key] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array(
  688. 'product_id' => $product_id,
  689. 'variation_id' => $variation_id,
  690. 'variation' => $variation,
  691. 'quantity' => $quantity,
  692. 'data' => $product_data
  693. ) ), $cart_item_key );
  694. }
  695. do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
  696. $woocommerce->cart_has_contents_cookie( true );
  697. $this->calculate_totals();
  698. return true;
  699. }
  700. /**
  701. * Set the quantity for an item in the cart.
  702. *
  703. * @param string cart_item_key contains the id of the cart item
  704. * @param string quantity contains the quantity of the item
  705. */
  706. public function set_quantity( $cart_item_key, $quantity = 1 ) {
  707. if ( $quantity == 0 || $quantity < 0 ) {
  708. do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
  709. unset( $this->cart_contents[$cart_item_key] );
  710. } else {
  711. $this->cart_contents[$cart_item_key]['quantity'] = $quantity;
  712. do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity );
  713. }
  714. $this->calculate_totals();
  715. }
  716. /*-----------------------------------------------------------------------------------*/
  717. /* Cart Calculation Functions */
  718. /*-----------------------------------------------------------------------------------*/
  719. /**
  720. * Reset cart totals and clear sessions.
  721. *
  722. * @access private
  723. * @return void
  724. */
  725. private function reset() {
  726. global $woocommerce;
  727. $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;
  728. $this->shipping_taxes = $this->taxes = $this->coupon_discount_amounts = array();
  729. 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 );
  730. }
  731. /**
  732. * Function to apply discounts to a product and get the discounted price (before tax is applied).
  733. *
  734. * @access public
  735. * @param mixed $values
  736. * @param mixed $price
  737. * @param bool $add_totals (default: false)
  738. * @return float price
  739. */
  740. public function get_discounted_price( $values, $price, $add_totals = false ) {
  741. if ( ! $price ) return $price;
  742. if ( ! empty( $this->applied_coupons ) ) {
  743. foreach ( $this->applied_coupons as $code ) {
  744. $coupon = new WC_Coupon( $code );
  745. if ( $coupon->apply_before_tax() && $coupon->is_valid() ) {
  746. switch ( $coupon->type ) {
  747. case "fixed_product" :
  748. case "percent_product" :
  749. $this_item_is_discounted = false;
  750. $product_cats = wp_get_post_terms( $values['product_id'], 'product_cat', array("fields" => "ids") );
  751. $product_ids_on_sale = woocommerce_get_product_ids_on_sale();
  752. // Specific products get the discount
  753. if ( sizeof( $coupon->product_ids ) > 0 ) {
  754. 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 ) )
  755. $this_item_is_discounted = true;
  756. // Category discounts
  757. } elseif ( sizeof($coupon->product_categories ) > 0 ) {
  758. if ( sizeof( array_intersect( $product_cats, $coupon->product_categories ) ) > 0 )
  759. $this_item_is_discounted = true;
  760. } else {
  761. // No product ids - all items discounted
  762. $this_item_is_discounted = true;
  763. }
  764. // Specific product ID's excluded from the discount
  765. if ( sizeof( $coupon->exclude_product_ids ) > 0 )
  766. 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 ) )
  767. $this_item_is_discounted = false;
  768. // Specific categories excluded from the discount
  769. if ( sizeof( $coupon->exclude_product_categories ) > 0 )
  770. if ( sizeof( array_intersect( $product_cats, $coupon->exclude_product_categories ) ) > 0 )
  771. $this_item_is_discounted = false;
  772. // Sale Items excluded from discount
  773. if ( $coupon->exclude_sale_items == 'yes' )
  774. if ( in_array( $values['product_id'], $product_ids_on_sale, true ) || in_array( $values['variation_id'], $product_ids_on_sale, true ) || in_array( $values['data']->get_parent(), $product_ids_on_sale, true ) )
  775. $this_item_is_discounted = false;
  776. // Apply filter
  777. $this_item_is_discounted = apply_filters( 'woocommerce_item_is_discounted', $this_item_is_discounted, $values, $before_tax = true, $coupon );
  778. // Apply the discount
  779. if ( $this_item_is_discounted ) {
  780. if ( $coupon->type=='fixed_product' ) {
  781. if ( $price < $coupon->amount ) {
  782. $discount_amount = $price;
  783. } else {
  784. $discount_amount = $coupon->amount;
  785. }
  786. $price = $price - $coupon->amount;
  787. if ( $price < 0 ) $price = 0;
  788. if ( $add_totals ) {
  789. $this->discount_cart = $this->discount_cart + ( $discount_amount * $values['quantity'] );
  790. $this->increase_coupon_discount_amount( $code, $discount_amount * $values['quantity'] );
  791. }
  792. } elseif ( $coupon->type == 'percent_product' ) {
  793. $percent_discount = ( $values['data']->get_price() / 100 ) * $coupon->amount;
  794. if ( $add_totals ) {
  795. $this->discount_cart = $this->discount_cart + ( $percent_discount * $values['quantity'] );
  796. $this->increase_coupon_discount_amount( $code, $percent_discount * $values['quantity'] );
  797. }
  798. $price = $price - $percent_discount;
  799. }
  800. }
  801. break;
  802. case "fixed_cart" :
  803. /**
  804. * This is the most complex discount - we need to divide the discount between rows based on their price in
  805. * proportion to the subtotal. This is so rows with different tax rates get a fair discount, and so rows
  806. * with no price (free) don't get discount too.
  807. */
  808. // Get item discount by dividing item cost by subtotal to get a %
  809. if ( $this->subtotal_ex_tax )
  810. $discount_percent = ( $values['data']->get_price_excluding_tax() * $values['quantity'] ) / $this->subtotal_ex_tax;
  811. else
  812. $discount_percent = 0;
  813. // Use pence to help prevent rounding errors
  814. $coupon_amount_pence = $coupon->amount * 100;
  815. // Work out the discount for the row
  816. $item_discount = $coupon_amount_pence * $discount_percent;
  817. // Work out discount per item
  818. $item_discount = $item_discount / $values['quantity'];
  819. // Pence
  820. $price = $price * 100;
  821. // Check if discount is more than price
  822. if ( $price < $item_discount )
  823. $discount_amount = $price;
  824. else
  825. $discount_amount = $item_discount;
  826. // Take discount off of price (in pence)
  827. $price = $price - $discount_amount;
  828. // Back to pounds
  829. $price = $price / 100;
  830. // Cannot be below 0
  831. if ( $price < 0 )
  832. $price = 0;
  833. // Add coupon to discount total (once, since this is a fixed cart discount and we don't want rounding issues)
  834. if ( $add_totals ) {
  835. $this->discount_cart = $this->discount_cart + ( ( $discount_amount * $values['quantity'] ) / 100 );
  836. $this->increase_coupon_discount_amount( $code, ( $discount_amount * $values['quantity'] ) / 100 );
  837. }
  838. break;
  839. case "percent" :
  840. $percent_discount = round( ( $values['data']->get_price() / 100 ) * $coupon->amount, $this->dp );
  841. if ( $add_totals ) {
  842. $this->discount_cart = $this->discount_cart + ( $percent_discount * $values['quantity'] );
  843. $this->increase_coupon_discount_amount( $code, $percent_discount * $values['quantity'] );
  844. }
  845. $price = $price - $percent_discount;
  846. break;
  847. }
  848. }
  849. }
  850. }
  851. return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this );
  852. }
  853. /**
  854. * Function to apply product discounts after tax.
  855. *
  856. * @access public
  857. * @param mixed $values
  858. * @param mixed $price
  859. */
  860. public function apply_product_discounts_after_tax( $values, $price ) {
  861. if ( ! empty( $this->applied_coupons) ) {
  862. foreach ( $this->applied_coupons as $code ) {
  863. $coupon = new WC_Coupon( $code );
  864. do_action( 'woocommerce_product_discount_after_tax_' . $coupon->type, $coupon, $values, $price );
  865. if ( ! $coupon->is_valid() ) continue;
  866. if ( $coupon->type != 'fixed_product' && $coupon->type != 'percent_product' ) continue;
  867. if ( ! $coupon->apply_before_tax() ) {
  868. $product_cats = wp_get_post_terms( $values['product_id'], 'product_cat', array("fields" => "ids") );
  869. $product_ids_on_sale = woocommerce_get_product_ids_on_sale();
  870. $this_item_is_discounted = false;
  871. // Specific products get the discount
  872. if ( sizeof( $coupon->product_ids ) > 0 ) {
  873. 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))
  874. $this_item_is_discounted = true;
  875. // Category discounts
  876. } elseif ( sizeof( $coupon->product_categories ) > 0 ) {
  877. if ( sizeof( array_intersect( $product_cats, $coupon->product_categories ) ) > 0 )
  878. $this_item_is_discounted = true;
  879. } else {
  880. // No product ids - all items discounted
  881. $this_item_is_discounted = true;
  882. }
  883. // Specific product ID's excluded from the discount
  884. if ( sizeof( $coupon->exclude_product_ids ) > 0 )
  885. 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 ) )
  886. $this_item_is_discounted = false;
  887. // Specific categories excluded from the discount
  888. if ( sizeof( $coupon->exclude_product_categories ) > 0 )
  889. if ( sizeof( array_intersect( $product_cats, $coupon->exclude_product_categories ) ) > 0 )
  890. $this_item_is_discounted = false;
  891. // Sale Items excluded from discount
  892. if ( $coupon->exclude_sale_items == 'yes' )
  893. if ( in_array( $values['product_id'], $product_ids_on_sale, true ) || in_array( $values['variation_id'], $product_ids_on_sale, true ) || in_array( $values['data']->get_parent(), $product_ids_on_sale, true ) )
  894. $this_item_is_discounted = false;
  895. // Apply filter
  896. $this_item_is_discounted = apply_filters( 'woocommerce_item_is_discounted', $this_item_is_discounted, $values, $before_tax = false, $coupon );
  897. // Apply the discount
  898. if ( $this_item_is_discounted ) {
  899. if ( $coupon->type == 'fixed_product' ) {
  900. if ( $price < $coupon->amount )
  901. $discount_amount = $price;
  902. else
  903. $discount_amount = $coupon->amount;
  904. $this->discount_total = $this->discount_total + ( $discount_amount * $values['quantity'] );
  905. $this->increase_coupon_discount_amount( $code, $discount_amount * $values['quantity'] );
  906. } elseif ( $coupon->type == 'percent_product' ) {
  907. $this->discount_total = $this->discount_total + round( ( $price / 100 ) * $coupon->amount, $this->dp );
  908. $this->increase_coupon_discount_amount( $code, round( ( $price / 100 ) * $coupon->amount, $this->dp ) );
  909. }
  910. }
  911. }
  912. }
  913. }
  914. }
  915. /**
  916. * Function to apply cart discounts after tax.
  917. *
  918. * @access public
  919. */
  920. public function apply_cart_discounts_after_tax() {
  921. $pre_discount_total = number_format( $this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp, '.', '' );
  922. if ( $this->applied_coupons ) {
  923. foreach ( $this->applied_coupons as $code ) {
  924. $coupon = new WC_Coupon( $code );
  925. do_action( 'woocommerce_cart_discount_after_tax_' . $coupon->type, $coupon );
  926. if ( ! $coupon->apply_before_tax() && $coupon->is_valid() ) {
  927. switch ( $coupon->type ) {
  928. case "fixed_cart" :
  929. if ( $coupon->amount > $pre_discount_total )
  930. $coupon->amount = $pre_discount_total;
  931. $pre_discount_total = $pre_discount_total - $coupon->amount;
  932. $this->discount_total = $this->discount_total + $coupon->amount;
  933. $this->increase_coupon_discount_amount( $code, $coupon->amount );
  934. break;
  935. case "percent" :
  936. $percent_discount = round( ( round( $this->cart_contents_total + $this->tax_total, $this->dp ) / 100 ) * $coupon->amount, $this->dp );
  937. if ( $coupon->amount > $percent_discount )
  938. $coupon->amount = $percent_discount;
  939. $pre_discount_total = $pre_discount_total - $percent_discount;
  940. $this->discount_total = $this->discount_total + $percent_discount;
  941. $this->increase_coupon_discount_amount( $code, $percent_discount );
  942. break;
  943. }
  944. }
  945. }
  946. }
  947. }
  948. /**
  949. * Store how much discount each coupon grants.
  950. *
  951. * @access private
  952. * @param mixed $code
  953. * @param mixed $amount
  954. * @return void
  955. */
  956. private function increase_coupon_discount_amount( $code, $amount ) {
  957. if ( empty( $this->coupon_discount_amounts[ $code ] ) )
  958. $this->coupon_discount_amounts[ $code ] = 0;
  959. $this->coupon_discount_amounts[ $code ] += $amount;
  960. }
  961. /**
  962. * Calculate totals for the items in the cart.
  963. *
  964. * @access public
  965. */
  966. public function calculate_totals() {
  967. global $woocommerce;
  968. $this->reset();
  969. do_action( 'woocommerce_before_calculate_totals', $this );
  970. // Get count of all items + weights + subtotal (we may need this for discounts)
  971. if ( sizeof( $this->cart_contents ) > 0 ) {
  972. foreach ( $this->cart_contents as $cart_item_key => $values ) {
  973. $_product = $values['data'];
  974. $this->cart_contents_weight = $this->cart_contents_weight + ( $_product->get_weight() * $values['quantity'] );
  975. $this->cart_contents_count = $this->cart_contents_count + $values['quantity'];
  976. // Base Price (inclusive of tax for now)
  977. $row_base_price = $_product->get_price() * $values['quantity'];
  978. $base_tax_rates = $this->tax->get_shop_base_rate( $_product->tax_class );
  979. $tax_amount = 0;
  980. if ( $this->prices_include_tax ) {
  981. if ( $_product->is_taxable() ) {
  982. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  983. // ADJUST BASE if tax rate is different (different region or modified tax class)
  984. if ( $tax_rates !== $base_tax_rates ) {
  985. $base_taxes = $this->tax->calc_tax( $row_base_price, $base_tax_rates, true, true );
  986. $modded_taxes = $this->tax->calc_tax( $row_base_price - array_sum( $base_taxes ), $tax_rates, false );
  987. $row_base_price = ( $row_base_price - array_sum( $base_taxes ) ) + array_sum( $modded_taxes );
  988. }
  989. $taxes = $this->tax->calc_tax( $row_base_price, $tax_rates, true );
  990. $tax_amount = get_option('woocommerce_tax_round_at_subtotal') == 'no' ? $this->tax->get_tax_total( $taxes ) : array_sum( $taxes );
  991. }
  992. // Sub total is based on base prices (without discounts)
  993. $this->subtotal = $this->subtotal + $row_base_price;
  994. $this->subtotal_ex_tax = $this->subtotal_ex_tax + ( $row_base_price - $tax_amount);
  995. } else {
  996. if ( $_product->is_taxable() ) {
  997. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  998. $taxes = $this->tax->calc_tax( $row_base_price, $tax_rates, false );
  999. $tax_amount = get_option('woocommerce_tax_round_at_subtotal') == 'no' ? $this->tax->get_tax_total( $taxes ) : array_sum( $taxes );
  1000. }
  1001. // Sub total is based on base prices (without discounts)
  1002. $this->subtotal = $this->subtotal + $row_base_price + $tax_amount;
  1003. $this->subtotal_ex_tax = $this->subtotal_ex_tax + $row_base_price;
  1004. }
  1005. }
  1006. }
  1007. // Now calc the main totals, including discounts
  1008. if ( $this->prices_include_tax ) {
  1009. /**
  1010. * Calculate totals for items
  1011. */
  1012. if ( sizeof($this->cart_contents) > 0 ) {
  1013. foreach ($this->cart_contents as $cart_item_key => $values ) {
  1014. /**
  1015. * Prices include tax
  1016. *
  1017. * To prevent rounding issues we need to work with the inclusive price where possible
  1018. * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would
  1019. * be 8.325 leading to totals being 1p off
  1020. *
  1021. * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated
  1022. * afterwards.
  1023. *
  1024. * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that
  1025. *
  1026. * Used this excellent article for reference:
  1027. * http://developer.practicalecommerce.com/articles/1473-Coding-for-Tax-Calculations-Everything-You-Never-Wanted-to-Know-Part-2
  1028. */
  1029. $_product = $values['data'];
  1030. // Base Price (inclusive of tax for now)
  1031. $base_price = $_product->get_price();
  1032. // Base Price Adjustment
  1033. if ( $_product->is_taxable() ) {
  1034. // Get rates
  1035. $tax_rates = $this->tax->get_rates( $_product->get_tax_class() );
  1036. /**
  1037. * ADJUST TAX - Calculations when customer is OUTSIDE the shop base country/state and prices INCLUDE tax
  1038. * OR
  1039. * ADJUST TAX - Calculations when a tax class is modified
  1040. */
  1041. if ( ( $woocommerce->customer->is_customer_outside_base() && ( defined('WOOCOMMERCE_CHECKOUT') || $woocommerce->customer->has_calculated_shipping() ) ) || ( $_product->get_tax_class() !== $_product->tax_class ) ) {
  1042. // Get tax rate for the store base, ensuring we use the unmodified tax_class for the product
  1043. $base_tax_rates = $this->tax->get_shop_base_rate( $_product->tax_class );
  1044. // Work out new price based on region
  1045. $row_base_price = $base_price * $values['quantity'];

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