class-wc-cart.php 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969
  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. * @package WooCommerce/Classes
  9. * @version 2.1.0
  10. */
  11. defined( 'ABSPATH' ) || exit;
  12. require_once WC_ABSPATH . 'includes/legacy/class-wc-legacy-cart.php';
  13. require_once WC_ABSPATH . 'includes/class-wc-cart-fees.php';
  14. require_once WC_ABSPATH . 'includes/class-wc-cart-session.php';
  15. /**
  16. * WC_Cart class.
  17. */
  18. class WC_Cart extends WC_Legacy_Cart {
  19. /**
  20. * Contains an array of cart items.
  21. *
  22. * @var array
  23. */
  24. public $cart_contents = array();
  25. /**
  26. * Contains an array of removed cart items so we can restore them if needed.
  27. *
  28. * @var array
  29. */
  30. public $removed_cart_contents = array();
  31. /**
  32. * Contains an array of coupon codes applied to the cart.
  33. *
  34. * @var array
  35. */
  36. public $applied_coupons = array();
  37. /**
  38. * Are prices in the cart displayed inc or excl tax?
  39. *
  40. * @var string
  41. */
  42. public $tax_display_cart = 'incl';
  43. /**
  44. * This stores the chosen shipping methods for the cart item packages.
  45. *
  46. * @var array
  47. */
  48. protected $shipping_methods;
  49. /**
  50. * Total defaults used to reset.
  51. *
  52. * @var array
  53. */
  54. protected $default_totals = array(
  55. 'subtotal' => 0,
  56. 'subtotal_tax' => 0,
  57. 'shipping_total' => 0,
  58. 'shipping_tax' => 0,
  59. 'shipping_taxes' => array(),
  60. 'discount_total' => 0,
  61. 'discount_tax' => 0,
  62. 'cart_contents_total' => 0,
  63. 'cart_contents_tax' => 0,
  64. 'cart_contents_taxes' => array(),
  65. 'fee_total' => 0,
  66. 'fee_tax' => 0,
  67. 'fee_taxes' => array(),
  68. 'total' => 0,
  69. 'total_tax' => 0,
  70. );
  71. /**
  72. * Store calculated totals.
  73. *
  74. * @var array
  75. */
  76. protected $totals = array();
  77. /**
  78. * Reference to the cart session handling class.
  79. *
  80. * @var WC_Cart_Session
  81. */
  82. protected $session;
  83. /**
  84. * Reference to the cart fees API class.
  85. *
  86. * @var WC_Cart_Fees
  87. */
  88. protected $fees_api;
  89. /**
  90. * Constructor for the cart class. Loads options and hooks in the init method.
  91. */
  92. public function __construct() {
  93. $this->session = new WC_Cart_Session( $this );
  94. $this->fees_api = new WC_Cart_Fees( $this );
  95. $this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' );
  96. // Register hooks for the objects.
  97. $this->session->init();
  98. add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
  99. add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
  100. add_action( 'woocommerce_cart_item_removed', array( $this, 'calculate_totals' ), 20, 0 );
  101. add_action( 'woocommerce_cart_item_restored', array( $this, 'calculate_totals' ), 20, 0 );
  102. add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
  103. add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
  104. add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 );
  105. }
  106. /**
  107. * When cloning, ensure object properties are handled.
  108. *
  109. * These properties store a reference to the cart, so we use new instead of clone.
  110. */
  111. public function __clone() {
  112. $this->session = clone $this->session;
  113. $this->fees_api = clone $this->fees_api;
  114. }
  115. /*
  116. |--------------------------------------------------------------------------
  117. | Getters.
  118. |--------------------------------------------------------------------------
  119. |
  120. | Methods to retrieve class properties and avoid direct access.
  121. */
  122. /**
  123. * Gets cart contents.
  124. *
  125. * @since 3.2.0
  126. * @return array of applied coupons
  127. */
  128. public function get_cart_contents() {
  129. return (array) $this->cart_contents;
  130. }
  131. /**
  132. * Return items removed from the cart.
  133. *
  134. * @since 3.2.0
  135. * @return array
  136. */
  137. public function get_removed_cart_contents() {
  138. return (array) $this->removed_cart_contents;
  139. }
  140. /**
  141. * Gets the array of applied coupon codes.
  142. *
  143. * @return array of applied coupons
  144. */
  145. public function get_applied_coupons() {
  146. return (array) $this->applied_coupons;
  147. }
  148. /**
  149. * Return all calculated coupon totals.
  150. *
  151. * @since 3.2.0
  152. * @return array
  153. */
  154. public function get_coupon_discount_totals() {
  155. return (array) $this->coupon_discount_totals;
  156. }
  157. /**
  158. * Return all calculated coupon tax totals.
  159. *
  160. * @since 3.2.0
  161. * @return array
  162. */
  163. public function get_coupon_discount_tax_totals() {
  164. return (array) $this->coupon_discount_tax_totals;
  165. }
  166. /**
  167. * Return all calculated totals.
  168. *
  169. * @since 3.2.0
  170. * @return array
  171. */
  172. public function get_totals() {
  173. return empty( $this->totals ) ? $this->default_totals : $this->totals;
  174. }
  175. /**
  176. * Get a total.
  177. *
  178. * @since 3.2.0
  179. * @param string $key Key of element in $totals array.
  180. * @return mixed
  181. */
  182. protected function get_totals_var( $key ) {
  183. return isset( $this->totals[ $key ] ) ? $this->totals[ $key ] : $this->default_totals[ $key ];
  184. }
  185. /**
  186. * Get subtotal.
  187. *
  188. * @since 3.2.0
  189. * @return float
  190. */
  191. public function get_subtotal() {
  192. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal' ) );
  193. }
  194. /**
  195. * Get subtotal.
  196. *
  197. * @since 3.2.0
  198. * @return float
  199. */
  200. public function get_subtotal_tax() {
  201. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal_tax' ) );
  202. }
  203. /**
  204. * Get discount_total.
  205. *
  206. * @since 3.2.0
  207. * @return float
  208. */
  209. public function get_discount_total() {
  210. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_total' ) );
  211. }
  212. /**
  213. * Get discount_tax.
  214. *
  215. * @since 3.2.0
  216. * @return float
  217. */
  218. public function get_discount_tax() {
  219. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_tax' ) );
  220. }
  221. /**
  222. * Get shipping_total.
  223. *
  224. * @since 3.2.0
  225. * @return float
  226. */
  227. public function get_shipping_total() {
  228. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_total' ) );
  229. }
  230. /**
  231. * Get shipping_tax.
  232. *
  233. * @since 3.2.0
  234. * @return float
  235. */
  236. public function get_shipping_tax() {
  237. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_tax' ) );
  238. }
  239. /**
  240. * Gets cart total. This is the total of items in the cart, but after discounts. Subtotal is before discounts.
  241. *
  242. * @since 3.2.0
  243. * @return float
  244. */
  245. public function get_cart_contents_total() {
  246. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_total' ) );
  247. }
  248. /**
  249. * Gets cart tax amount.
  250. *
  251. * @since 3.2.0
  252. * @return float
  253. */
  254. public function get_cart_contents_tax() {
  255. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_tax' ) );
  256. }
  257. /**
  258. * Gets cart total after calculation.
  259. *
  260. * @since 3.2.0
  261. * @param string $context If the context is view, the value will be formatted for display. This keeps it compatible with pre-3.2 versions.
  262. * @return float
  263. */
  264. public function get_total( $context = 'view' ) {
  265. $total = apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total' ) );
  266. return 'view' === $context ? apply_filters( 'woocommerce_cart_total', wc_price( $total ) ) : $total;
  267. }
  268. /**
  269. * Get total tax amount.
  270. *
  271. * @since 3.2.0
  272. * @return float
  273. */
  274. public function get_total_tax() {
  275. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total_tax' ) );
  276. }
  277. /**
  278. * Get total fee amount.
  279. *
  280. * @since 3.2.0
  281. * @return float
  282. */
  283. public function get_fee_total() {
  284. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_total' ) );
  285. }
  286. /**
  287. * Get total fee tax amount.
  288. *
  289. * @since 3.2.0
  290. * @return float
  291. */
  292. public function get_fee_tax() {
  293. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_tax' ) );
  294. }
  295. /**
  296. * Get taxes.
  297. *
  298. * @since 3.2.0
  299. */
  300. public function get_shipping_taxes() {
  301. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_taxes' ) );
  302. }
  303. /**
  304. * Get taxes.
  305. *
  306. * @since 3.2.0
  307. */
  308. public function get_cart_contents_taxes() {
  309. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_taxes' ) );
  310. }
  311. /**
  312. * Get taxes.
  313. *
  314. * @since 3.2.0
  315. */
  316. public function get_fee_taxes() {
  317. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_taxes' ) );
  318. }
  319. /**
  320. * Return whether or not the cart is displaying prices including tax, rather than excluding tax.
  321. *
  322. * @since 3.3.0
  323. * @return bool
  324. */
  325. public function display_prices_including_tax() {
  326. return apply_filters( 'woocommerce_cart_' . __FUNCTION__, 'incl' === $this->tax_display_cart );
  327. }
  328. /*
  329. |--------------------------------------------------------------------------
  330. | Setters.
  331. |--------------------------------------------------------------------------
  332. |
  333. | Methods to set class properties and avoid direct access.
  334. */
  335. /**
  336. * Sets the contents of the cart.
  337. *
  338. * @param array $value Cart array.
  339. */
  340. public function set_cart_contents( $value ) {
  341. $this->cart_contents = (array) $value;
  342. }
  343. /**
  344. * Set items removed from the cart.
  345. *
  346. * @since 3.2.0
  347. * @param array $value Item array.
  348. */
  349. public function set_removed_cart_contents( $value = array() ) {
  350. $this->removed_cart_contents = (array) $value;
  351. }
  352. /**
  353. * Sets the array of applied coupon codes.
  354. *
  355. * @param array $value List of applied coupon codes.
  356. */
  357. public function set_applied_coupons( $value = array() ) {
  358. $this->applied_coupons = (array) $value;
  359. }
  360. /**
  361. * Return all calculated coupon totals.
  362. *
  363. * @since 3.2.0
  364. * @param array $value Value to set.
  365. */
  366. public function set_coupon_discount_totals( $value = array() ) {
  367. $this->coupon_discount_totals = (array) $value;
  368. }
  369. /**
  370. * Return all calculated coupon tax totals.
  371. *
  372. * @since 3.2.0
  373. * @param array $value Value to set.
  374. */
  375. public function set_coupon_discount_tax_totals( $value = array() ) {
  376. $this->coupon_discount_tax_totals = (array) $value;
  377. }
  378. /**
  379. * Set all calculated totals.
  380. *
  381. * @since 3.2.0
  382. * @param array $value Value to set.
  383. */
  384. public function set_totals( $value = array() ) {
  385. $this->totals = wp_parse_args( $value, $this->default_totals );
  386. }
  387. /**
  388. * Set subtotal.
  389. *
  390. * @since 3.2.0
  391. * @param string $value Value to set.
  392. */
  393. public function set_subtotal( $value ) {
  394. $this->totals['subtotal'] = wc_format_decimal( $value, wc_get_price_decimals() );
  395. }
  396. /**
  397. * Set subtotal.
  398. *
  399. * @since 3.2.0
  400. * @param string $value Value to set.
  401. */
  402. public function set_subtotal_tax( $value ) {
  403. $this->totals['subtotal_tax'] = wc_round_tax_total( $value );
  404. }
  405. /**
  406. * Set discount_total.
  407. *
  408. * @since 3.2.0
  409. * @param string $value Value to set.
  410. */
  411. public function set_discount_total( $value ) {
  412. $this->totals['discount_total'] = wc_cart_round_discount( $value, wc_get_price_decimals() );
  413. }
  414. /**
  415. * Set discount_tax.
  416. *
  417. * @since 3.2.0
  418. * @param string $value Value to set.
  419. */
  420. public function set_discount_tax( $value ) {
  421. $this->totals['discount_tax'] = wc_round_tax_total( $value );
  422. }
  423. /**
  424. * Set shipping_total.
  425. *
  426. * @since 3.2.0
  427. * @param string $value Value to set.
  428. */
  429. public function set_shipping_total( $value ) {
  430. $this->totals['shipping_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
  431. }
  432. /**
  433. * Set shipping_tax.
  434. *
  435. * @since 3.2.0
  436. * @param string $value Value to set.
  437. */
  438. public function set_shipping_tax( $value ) {
  439. $this->totals['shipping_tax'] = wc_round_tax_total( $value );
  440. }
  441. /**
  442. * Set cart_contents_total.
  443. *
  444. * @since 3.2.0
  445. * @param string $value Value to set.
  446. */
  447. public function set_cart_contents_total( $value ) {
  448. $this->totals['cart_contents_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
  449. }
  450. /**
  451. * Set cart tax amount.
  452. *
  453. * @since 3.2.0
  454. * @param string $value Value to set.
  455. */
  456. public function set_cart_contents_tax( $value ) {
  457. $this->totals['cart_contents_tax'] = wc_round_tax_total( $value );
  458. }
  459. /**
  460. * Set cart total.
  461. *
  462. * @since 3.2.0
  463. * @param string $value Value to set.
  464. */
  465. public function set_total( $value ) {
  466. $this->totals['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
  467. }
  468. /**
  469. * Set total tax amount.
  470. *
  471. * @since 3.2.0
  472. * @param string $value Value to set.
  473. */
  474. public function set_total_tax( $value ) {
  475. $this->totals['total_tax'] = wc_round_tax_total( $value );
  476. }
  477. /**
  478. * Set fee amount.
  479. *
  480. * @since 3.2.0
  481. * @param string $value Value to set.
  482. */
  483. public function set_fee_total( $value ) {
  484. $this->totals['fee_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
  485. }
  486. /**
  487. * Set fee tax.
  488. *
  489. * @since 3.2.0
  490. * @param string $value Value to set.
  491. */
  492. public function set_fee_tax( $value ) {
  493. $this->totals['fee_tax'] = wc_round_tax_total( $value );
  494. }
  495. /**
  496. * Set taxes.
  497. *
  498. * @since 3.2.0
  499. * @param array $value Tax values.
  500. */
  501. public function set_shipping_taxes( $value ) {
  502. $this->totals['shipping_taxes'] = (array) $value;
  503. }
  504. /**
  505. * Set taxes.
  506. *
  507. * @since 3.2.0
  508. * @param array $value Tax values.
  509. */
  510. public function set_cart_contents_taxes( $value ) {
  511. $this->totals['cart_contents_taxes'] = (array) $value;
  512. }
  513. /**
  514. * Set taxes.
  515. *
  516. * @since 3.2.0
  517. * @param array $value Tax values.
  518. */
  519. public function set_fee_taxes( $value ) {
  520. $this->totals['fee_taxes'] = (array) $value;
  521. }
  522. /*
  523. |--------------------------------------------------------------------------
  524. | Helper methods.
  525. |--------------------------------------------------------------------------
  526. */
  527. /**
  528. * Returns the cart and shipping taxes, merged.
  529. *
  530. * @return array merged taxes
  531. */
  532. public function get_taxes() {
  533. return apply_filters( 'woocommerce_cart_get_taxes', wc_array_merge_recursive_numeric( $this->get_shipping_taxes(), $this->get_cart_contents_taxes(), $this->get_fee_taxes() ), $this );
  534. }
  535. /**
  536. * Returns the contents of the cart in an array.
  537. *
  538. * @return array contents of the cart
  539. */
  540. public function get_cart() {
  541. if ( ! did_action( 'wp_loaded' ) ) {
  542. wc_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
  543. }
  544. if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) {
  545. $this->session->get_cart_from_session();
  546. }
  547. return array_filter( $this->get_cart_contents() );
  548. }
  549. /**
  550. * Returns a specific item in the cart.
  551. *
  552. * @param string $item_key Cart item key.
  553. * @return array Item data
  554. */
  555. public function get_cart_item( $item_key ) {
  556. return isset( $this->cart_contents[ $item_key ] ) ? $this->cart_contents[ $item_key ] : array();
  557. }
  558. /**
  559. * Checks if the cart is empty.
  560. *
  561. * @return bool
  562. */
  563. public function is_empty() {
  564. return 0 === count( $this->get_cart() );
  565. }
  566. /**
  567. * Empties the cart and optionally the persistent cart too.
  568. *
  569. * @param bool $clear_persistent_cart Should the persistant cart be cleared too. Defaults to true.
  570. */
  571. public function empty_cart( $clear_persistent_cart = true ) {
  572. $this->cart_contents = array();
  573. $this->removed_cart_contents = array();
  574. $this->shipping_methods = array();
  575. $this->coupon_discount_totals = array();
  576. $this->coupon_discount_tax_totals = array();
  577. $this->applied_coupons = array();
  578. $this->totals = $this->default_totals;
  579. if ( $clear_persistent_cart ) {
  580. $this->session->persistent_cart_destroy();
  581. }
  582. $this->fees_api->remove_all_fees();
  583. do_action( 'woocommerce_cart_emptied' );
  584. }
  585. /**
  586. * Get number of items in the cart.
  587. *
  588. * @return int
  589. */
  590. public function get_cart_contents_count() {
  591. return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) );
  592. }
  593. /**
  594. * Get weight of items in the cart.
  595. *
  596. * @since 2.5.0
  597. * @return int
  598. */
  599. public function get_cart_contents_weight() {
  600. $weight = 0;
  601. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  602. if ( $values['data']->has_weight() ) {
  603. $weight += (float) $values['data']->get_weight() * $values['quantity'];
  604. }
  605. }
  606. return apply_filters( 'woocommerce_cart_contents_weight', $weight );
  607. }
  608. /**
  609. * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
  610. *
  611. * @return array
  612. */
  613. public function get_cart_item_quantities() {
  614. $quantities = array();
  615. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  616. $product = $values['data'];
  617. $quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $values['quantity'] : $values['quantity'];
  618. }
  619. return $quantities;
  620. }
  621. /**
  622. * Check all cart items for errors.
  623. */
  624. public function check_cart_items() {
  625. $return = true;
  626. $result = $this->check_cart_item_validity();
  627. if ( is_wp_error( $result ) ) {
  628. wc_add_notice( $result->get_error_message(), 'error' );
  629. $return = false;
  630. }
  631. $result = $this->check_cart_item_stock();
  632. if ( is_wp_error( $result ) ) {
  633. wc_add_notice( $result->get_error_message(), 'error' );
  634. $return = false;
  635. }
  636. return $return;
  637. }
  638. /**
  639. * Check cart coupons for errors.
  640. */
  641. public function check_cart_coupons() {
  642. foreach ( $this->get_applied_coupons() as $code ) {
  643. $coupon = new WC_Coupon( $code );
  644. if ( ! $coupon->is_valid() ) {
  645. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
  646. $this->remove_coupon( $code );
  647. }
  648. }
  649. }
  650. /**
  651. * Looks through cart items and checks the posts are not trashed or deleted.
  652. *
  653. * @return bool|WP_Error
  654. */
  655. public function check_cart_item_validity() {
  656. $return = true;
  657. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  658. $product = $values['data'];
  659. if ( ! $product || ! $product->exists() || 'trash' === $product->get_status() ) {
  660. $this->set_quantity( $cart_item_key, 0 );
  661. $return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) );
  662. }
  663. }
  664. return $return;
  665. }
  666. /**
  667. * Looks through the cart to check each item is in stock. If not, add an error.
  668. *
  669. * @return bool|WP_Error
  670. */
  671. public function check_cart_item_stock() {
  672. global $wpdb;
  673. $error = new WP_Error();
  674. $product_qty_in_cart = $this->get_cart_item_quantities();
  675. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  676. $product = $values['data'];
  677. /**
  678. * Check stock based on stock-status.
  679. */
  680. if ( ! $product->is_in_stock() ) {
  681. /* translators: %s: product name */
  682. $error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
  683. return $error;
  684. }
  685. if ( ! $product->managing_stock() ) {
  686. continue;
  687. }
  688. /**
  689. * Check stock based on all items in the cart.
  690. */
  691. if ( ! $product->has_enough_stock( $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
  692. /* translators: 1: product name 2: quantity in stock */
  693. $error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s in stock). Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ) );
  694. return $error;
  695. }
  696. /**
  697. * Finally consider any held stock, from pending orders.
  698. */
  699. if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $product->backorders_allowed() ) {
  700. $order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
  701. $held_stock = $wpdb->get_var(
  702. $wpdb->prepare(
  703. "
  704. SELECT SUM( order_item_meta.meta_value ) AS held_qty
  705. FROM {$wpdb->posts} AS posts
  706. LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
  707. LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
  708. LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
  709. WHERE order_item_meta.meta_key = '_qty'
  710. AND order_item_meta2.meta_key = %s AND order_item_meta2.meta_value = %d
  711. AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' )
  712. AND posts.post_status = 'wc-pending'
  713. AND posts.ID != %d;",
  714. 'variation' === get_post_type( $product->get_stock_managed_by_id() ) ? '_variation_id' : '_product_id',
  715. $product->get_stock_managed_by_id(),
  716. $order_id
  717. )
  718. ); // WPCS: unprepared SQL ok.
  719. if ( $product->get_stock_quantity() < ( $held_stock + $product_qty_in_cart[ $product->get_stock_managed_by_id() ] ) ) {
  720. /* translators: 1: product name 2: minutes */
  721. $error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order right now. Please try again in %2$d minutes or edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), get_option( 'woocommerce_hold_stock_minutes' ) ) );
  722. return $error;
  723. }
  724. }
  725. }
  726. return true;
  727. }
  728. /**
  729. * Gets and formats a list of cart item data + variations for display on the frontend.
  730. *
  731. * @param array $cart_item Cart item object.
  732. * @param bool $flat Should the data be returned flat or in a list.
  733. * @return string
  734. */
  735. public function get_item_data( $cart_item, $flat = false ) {
  736. wc_deprecated_function( 'WC_Cart::get_item_data', '3.3', 'wc_get_formatted_cart_item_data' );
  737. return wc_get_formatted_cart_item_data( $cart_item, $flat );
  738. }
  739. /**
  740. * Gets cross sells based on the items in the cart.
  741. *
  742. * @return array cross_sells (item ids)
  743. */
  744. public function get_cross_sells() {
  745. $cross_sells = array();
  746. $in_cart = array();
  747. if ( ! $this->is_empty() ) {
  748. foreach ( $this->get_cart() as $cart_item_key => $values ) {
  749. if ( $values['quantity'] > 0 ) {
  750. $cross_sells = array_merge( $values['data']->get_cross_sell_ids(), $cross_sells );
  751. $in_cart[] = $values['product_id'];
  752. }
  753. }
  754. }
  755. $cross_sells = array_diff( $cross_sells, $in_cart );
  756. return apply_filters( 'woocommerce_cart_crosssell_ids', wp_parse_id_list( $cross_sells ), $this );
  757. }
  758. /**
  759. * Gets the url to remove an item from the cart.
  760. *
  761. * @param string $cart_item_key contains the id of the cart item.
  762. * @return string url to page
  763. */
  764. public function get_remove_url( $cart_item_key ) {
  765. wc_deprecated_function( 'WC_Cart::get_remove_url', '3.3', 'wc_get_cart_remove_url' );
  766. return wc_get_cart_remove_url( $cart_item_key );
  767. }
  768. /**
  769. * Gets the url to re-add an item into the cart.
  770. *
  771. * @param string $cart_item_key Cart item key to undo.
  772. * @return string url to page
  773. */
  774. public function get_undo_url( $cart_item_key ) {
  775. wc_deprecated_function( 'WC_Cart::get_undo_url', '3.3', 'wc_get_cart_undo_url' );
  776. return wc_get_cart_undo_url( $cart_item_key );
  777. }
  778. /**
  779. * Get taxes, merged by code, formatted ready for output.
  780. *
  781. * @return array
  782. */
  783. public function get_tax_totals() {
  784. $taxes = $this->get_taxes();
  785. $tax_totals = array();
  786. foreach ( $taxes as $key => $tax ) {
  787. $code = WC_Tax::get_rate_code( $key );
  788. if ( $code || apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) === $key ) {
  789. if ( ! isset( $tax_totals[ $code ] ) ) {
  790. $tax_totals[ $code ] = new stdClass();
  791. $tax_totals[ $code ]->amount = 0;
  792. }
  793. $tax_totals[ $code ]->tax_rate_id = $key;
  794. $tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key );
  795. $tax_totals[ $code ]->label = WC_Tax::get_rate_label( $key );
  796. $tax_totals[ $code ]->amount += wc_round_tax_total( $tax );
  797. $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ) );
  798. }
  799. }
  800. if ( apply_filters( 'woocommerce_cart_hide_zero_taxes', true ) ) {
  801. $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
  802. $tax_totals = array_intersect_key( $tax_totals, $amounts );
  803. }
  804. return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this );
  805. }
  806. /**
  807. * Get all tax classes for items in the cart.
  808. *
  809. * @return array
  810. */
  811. public function get_cart_item_tax_classes() {
  812. $found_tax_classes = array();
  813. foreach ( WC()->cart->get_cart() as $item ) {
  814. if ( $item['data'] && ( $item['data']->is_taxable() || $item['data']->is_shipping_taxable() ) ) {
  815. $found_tax_classes[] = $item['data']->get_tax_class();
  816. }
  817. }
  818. return array_unique( $found_tax_classes );
  819. }
  820. /**
  821. * Get all tax classes for shipping based on the items in the cart.
  822. *
  823. * @return array
  824. */
  825. public function get_cart_item_tax_classes_for_shipping() {
  826. $found_tax_classes = array();
  827. foreach ( WC()->cart->get_cart() as $item ) {
  828. if ( $item['data'] && ( $item['data']->is_shipping_taxable() ) ) {
  829. $found_tax_classes[] = $item['data']->get_tax_class();
  830. }
  831. }
  832. return array_unique( $found_tax_classes );
  833. }
  834. /**
  835. * Determines the value that the customer spent and the subtotal
  836. * displayed, used for things like coupon validation.
  837. *
  838. * Since the coupon lines are displayed based on the TAX DISPLAY value
  839. * of cart, this is used to determine the spend.
  840. *
  841. * If cart totals are shown including tax, use the subtotal.
  842. * If cart totals are shown excluding tax, use the subtotal ex tax
  843. * (tax is shown after coupons).
  844. *
  845. * @since 2.6.0
  846. * @return string
  847. */
  848. public function get_displayed_subtotal() {
  849. return $this->display_prices_including_tax() ? $this->get_subtotal() + $this->get_subtotal_tax() : $this->get_subtotal();
  850. }
  851. /**
  852. * Check if product is in the cart and return cart item key.
  853. *
  854. * Cart item key will be unique based on the item and its properties, such as variations.
  855. *
  856. * @param mixed $cart_id id of product to find in the cart.
  857. * @return string cart item key
  858. */
  859. public function find_product_in_cart( $cart_id = false ) {
  860. if ( false !== $cart_id ) {
  861. if ( is_array( $this->cart_contents ) && isset( $this->cart_contents[ $cart_id ] ) ) {
  862. return $cart_id;
  863. }
  864. }
  865. return '';
  866. }
  867. /**
  868. * Generate a unique ID for the cart item being added.
  869. *
  870. * @param int $product_id - id of the product the key is being generated for.
  871. * @param int $variation_id of the product the key is being generated for.
  872. * @param array $variation data for the cart item.
  873. * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart.
  874. * @return string cart item key
  875. */
  876. public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
  877. $id_parts = array( $product_id );
  878. if ( $variation_id && 0 !== $variation_id ) {
  879. $id_parts[] = $variation_id;
  880. }
  881. if ( is_array( $variation ) && ! empty( $variation ) ) {
  882. $variation_key = '';
  883. foreach ( $variation as $key => $value ) {
  884. $variation_key .= trim( $key ) . trim( $value );
  885. }
  886. $id_parts[] = $variation_key;
  887. }
  888. if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
  889. $cart_item_data_key = '';
  890. foreach ( $cart_item_data as $key => $value ) {
  891. if ( is_array( $value ) || is_object( $value ) ) {
  892. $value = http_build_query( $value );
  893. }
  894. $cart_item_data_key .= trim( $key ) . trim( $value );
  895. }
  896. $id_parts[] = $cart_item_data_key;
  897. }
  898. return apply_filters( 'woocommerce_cart_id', md5( implode( '_', $id_parts ) ), $product_id, $variation_id, $variation, $cart_item_data );
  899. }
  900. /**
  901. * Add a product to the cart.
  902. *
  903. * @throws Exception Plugins can throw an exception to prevent adding to cart.
  904. * @param int $product_id contains the id of the product to add to the cart.
  905. * @param int $quantity contains the quantity of the item to add.
  906. * @param int $variation_id ID of the variation being added to the cart.
  907. * @param array $variation attribute values.
  908. * @param array $cart_item_data extra cart item data we want to pass into the item.
  909. * @return string|bool $cart_item_key
  910. */
  911. public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
  912. try {
  913. $product_id = absint( $product_id );
  914. $variation_id = absint( $variation_id );
  915. // Ensure we don't add a variation to the cart directly by variation ID.
  916. if ( 'product_variation' === get_post_type( $product_id ) ) {
  917. $variation_id = $product_id;
  918. $product_id = wp_get_post_parent_id( $variation_id );
  919. }
  920. $product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
  921. $quantity = apply_filters( 'woocommerce_add_to_cart_quantity', $quantity, $product_id );
  922. if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->get_status() ) {
  923. return false;
  924. }
  925. // Load cart item data - may be added by other plugins.
  926. $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity );
  927. // Generate a ID based on product ID, variation ID, variation data, and other cart item data.
  928. $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
  929. // Find the cart item key in the existing cart.
  930. $cart_item_key = $this->find_product_in_cart( $cart_id );
  931. // Force quantity to 1 if sold individually and check for existing item in cart.
  932. if ( $product_data->is_sold_individually() ) {
  933. $quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
  934. $found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $this->cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id );
  935. if ( $found_in_cart ) {
  936. /* translators: %s: product name */
  937. throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() ) ) );
  938. }
  939. }
  940. if ( ! $product_data->is_purchasable() ) {
  941. throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) );
  942. }
  943. // Stock check - only check if we're managing stock and backorders are not allowed.
  944. if ( ! $product_data->is_in_stock() ) {
  945. /* translators: %s: product name */
  946. throw new Exception( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ) );
  947. }
  948. if ( ! $product_data->has_enough_stock( $quantity ) ) {
  949. /* translators: 1: product name 2: quantity in stock */
  950. throw new Exception( sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ) ) );
  951. }
  952. // Stock check - this time accounting for whats already in-cart.
  953. if ( $product_data->managing_stock() ) {
  954. $products_qty_in_cart = $this->get_cart_item_quantities();
  955. if ( isset( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] ) && ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) {
  956. throw new Exception(
  957. sprintf(
  958. '<a href="%s" class="button wc-forward">%s</a> %s',
  959. wc_get_cart_url(),
  960. __( 'View Cart', 'woocommerce' ),
  961. /* translators: 1: quantity in stock 2: current quantity */
  962. sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ), wc_format_stock_quantity_for_display( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ], $product_data ) )
  963. )
  964. );
  965. }
  966. }
  967. // If cart_item_key is set, the item is already in the cart.
  968. if ( $cart_item_key ) {
  969. $new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity'];
  970. $this->set_quantity( $cart_item_key, $new_quantity, false );
  971. } else {
  972. $cart_item_key = $cart_id;
  973. // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item.
  974. $this->cart_contents[ $cart_item_key ] = apply_filters(
  975. 'woocommerce_add_cart_item', array_merge(
  976. $cart_item_data, array(
  977. 'key' => $cart_item_key,
  978. 'product_id' => $product_id,
  979. 'variation_id' => $variation_id,
  980. 'variation' => $variation,
  981. 'quantity' => $quantity,
  982. 'data' => $product_data,
  983. 'data_hash' => wc_get_cart_item_data_hash( $product_data ),
  984. )
  985. ), $cart_item_key
  986. );
  987. }
  988. do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
  989. return $cart_item_key;
  990. } catch ( Exception $e ) {
  991. if ( $e->getMessage() ) {
  992. wc_add_notice( $e->getMessage(), 'error' );
  993. }
  994. return false;
  995. }
  996. }
  997. /**
  998. * Remove a cart item.
  999. *
  1000. * @since 2.3.0
  1001. * @param string $cart_item_key Cart item key to remove from the cart.
  1002. * @return bool
  1003. */
  1004. public function remove_cart_item( $cart_item_key ) {
  1005. if ( isset( $this->cart_contents[ $cart_item_key ] ) ) {
  1006. $this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ];
  1007. unset( $this->removed_cart_contents[ $cart_item_key ]['data'] );
  1008. do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this );
  1009. unset( $this->cart_contents[ $cart_item_key ] );
  1010. do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this );
  1011. return true;
  1012. }
  1013. return false;
  1014. }
  1015. /**
  1016. * Restore a cart item.
  1017. *
  1018. * @param string $cart_item_key Cart item key to restore to the cart.
  1019. * @return bool
  1020. */
  1021. public function restore_cart_item( $cart_item_key ) {
  1022. if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) {
  1023. $restore_item = $this->removed_cart_contents[ $cart_item_key ];
  1024. $this->cart_contents[ $cart_item_key ] = $restore_item;
  1025. $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $restore_item['variation_id'] ? $restore_item['variation_id'] : $restore_item['product_id'] );
  1026. do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this );
  1027. unset( $this->removed_cart_contents[ $cart_item_key ] );
  1028. do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this );
  1029. return true;
  1030. }
  1031. return false;
  1032. }
  1033. /**
  1034. * Set the quantity for an item in the cart.
  1035. *
  1036. * @param string $cart_item_key contains the id of the cart item.
  1037. * @param int $quantity contains the quantity of the item.
  1038. * @param bool $refresh_totals whether or not to calculate totals after setting the new qty.
  1039. * @return bool
  1040. */
  1041. public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
  1042. if ( 0 === $quantity || $quantity < 0 ) {
  1043. do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key, $this );
  1044. unset( $this->cart_contents[ $cart_item_key ] );
  1045. } else {
  1046. $old_quantity = $this->cart_contents[ $cart_item_key ]['quantity'];
  1047. $this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
  1048. do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity, $this );
  1049. }
  1050. if ( $refresh_totals ) {
  1051. $this->calculate_totals();
  1052. }
  1053. return true;
  1054. }
  1055. /**
  1056. * Get cart's owner.
  1057. *
  1058. * @since 3.2.0
  1059. * @return WC_Customer
  1060. */
  1061. public function get_customer() {
  1062. return WC()->customer;
  1063. }
  1064. /**
  1065. * Calculate totals for the items in the cart.
  1066. *
  1067. * @uses WC_Cart_Totals
  1068. */
  1069. public function calculate_totals() {
  1070. $this->reset_totals();
  1071. if ( $this->is_empty() ) {
  1072. $this->session->set_session();
  1073. return;
  1074. }
  1075. do_action( 'woocommerce_before_calculate_totals', $this );
  1076. new WC_Cart_Totals( $this );
  1077. do_action( 'woocommerce_after_calculate_totals', $this );
  1078. }
  1079. /**
  1080. * Looks at the totals to see if payment is actually required.
  1081. *
  1082. * @return bool
  1083. */
  1084. public function needs_payment() {
  1085. return apply_filters( 'woocommerce_cart_needs_payment', 0 < $this->get_total( 'edit' ), $this );
  1086. }
  1087. /*
  1088. * Shipping related functions.
  1089. */
  1090. /**
  1091. * Uses the shipping class to calculate shipping then gets the totals when its finished.
  1092. */
  1093. public function calculate_shipping() {
  1094. $this->shipping_methods = $this->needs_shipping() ? $this->get_chosen_shipping_methods( WC()->shipping->calculate_shipping( $this->get_shipping_packages() ) ) : array();
  1095. $shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' );
  1096. $merged_taxes = array();
  1097. foreach ( $shipping_taxes as $taxes ) {
  1098. foreach ( $taxes as $tax_id => $tax_amount ) {
  1099. if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
  1100. $merged_taxes[ $tax_id ] = 0;
  1101. }
  1102. $merged_taxes[ $tax_id ] += $tax_amount;
  1103. }
  1104. }
  1105. $this->set_shipping_total( array_sum( wp_list_pluck( $this->shipping_methods, 'cost' ) ) );
  1106. $this->set_shipping_tax( array_sum( $merged_taxes ) );
  1107. $this->set_shipping_taxes( $merged_taxes );
  1108. return $this->shipping_methods;
  1109. }
  1110. /**
  1111. * Given a set of packages with rates, get the chosen ones only.
  1112. *
  1113. * @since 3.2.0
  1114. * @param array $calculated_shipping_packages Array of packages.
  1115. * @return array
  1116. */
  1117. protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) {
  1118. $chosen_methods = array();
  1119. // Get chosen methods for each package to get our totals.
  1120. foreach ( $calculated_shipping_packages as $key => $package ) {
  1121. $chosen_method = wc_get_chosen_shipping_method_for_package( $key, $package );
  1122. if ( $chosen_method ) {
  1123. $chosen_methods[ $key ] = $package['rates'][ $chosen_method ];
  1124. }
  1125. }
  1126. return $chosen_methods;
  1127. }
  1128. /**
  1129. * Filter items needing shipping callback.
  1130. *
  1131. * @since 3.0.0
  1132. * @param array $item Item to check for shipping.
  1133. * @return bool
  1134. */
  1135. protected function filter_items_needing_shipping( $item ) {
  1136. $product = $item['data'];
  1137. return $product && $product->needs_shipping();
  1138. }
  1139. /**
  1140. * Get only items that need shipping.
  1141. *
  1142. * @since 3.0.0
  1143. * @return array
  1144. */
  1145. protected function get_items_needing_shipping() {
  1146. return array_filter( $this->get_cart(), array( $this, 'filter_items_needing_shipping' ) );
  1147. }
  1148. /**
  1149. * Get packages to calculate shipping for.
  1150. *
  1151. * This lets us calculate costs for carts that are shipped to multiple locations.
  1152. *
  1153. * Shipping methods are responsible for looping through these packages.
  1154. *
  1155. * By default we pass the cart itself as a package - plugins can change this.
  1156. * through the filter and break it up.
  1157. *
  1158. * @since 1.5.4
  1159. * @return array of cart items
  1160. */
  1161. public function get_shipping_packages() {
  1162. return apply_filters(
  1163. 'woocommerce_cart_shipping_packages',
  1164. array(
  1165. array(
  1166. 'contents' => $this->get_items_needing_shipping(),
  1167. 'contents_cost' => array_sum( wp_list_pluck( $this->get_items_needing_shipping(), 'line_total' ) ),
  1168. 'applied_coupons' => $this->get_applied_coupons(),
  1169. 'user' => array(
  1170. 'ID' => get_current_user_id(),
  1171. ),
  1172. 'destination' => array(
  1173. 'country' => $this->get_customer()->get_shipping_country(),
  1174. 'state' => $this->get_customer()->get_shipping_state(),
  1175. 'postcode' => $this->get_customer()->get_shipping_postcode(),
  1176. 'city' => $this->get_customer()->get_shipping_city(),
  1177. 'address' => $this->get_customer()->get_shipping_address(),
  1178. 'address_2' => $this->get_customer()->get_shipping_address_2(),
  1179. ),
  1180. 'cart_subtotal' => $this->get_displayed_subtotal(),
  1181. ),
  1182. )
  1183. );
  1184. }
  1185. /**
  1186. * Looks through the cart to see if shipping is actually required.
  1187. *
  1188. * @return bool whether or not the cart needs shipping
  1189. */
  1190. public function needs_shipping() {
  1191. if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) ) {
  1192. return false;
  1193. }
  1194. $needs_shipping = false;
  1195. foreach ( $this->get_cart_contents() as $cart_item_key => $values ) {
  1196. if ( $values['data']->needs_shipping() ) {
  1197. $needs_shipping = true;
  1198. break;
  1199. }
  1200. }
  1201. return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping );
  1202. }
  1203. /**
  1204. * Should the shipping address form be shown.
  1205. *
  1206. * @return bool
  1207. */
  1208. public function needs_shipping_address() {
  1209. return apply_filters( 'woocommerce_cart_needs_shipping_address', true === $this->needs_shipping() && ! wc_ship_to_billing_address_only() );
  1210. }
  1211. /**
  1212. * Sees if the customer has entered enough data to calc the shipping yet.
  1213. *
  1214. * @return bool
  1215. */
  1216. public function show_shipping() {
  1217. if ( ! wc_shipping_enabled() || ! $this->get_cart_contents() ) {
  1218. return false;
  1219. }
  1220. if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
  1221. if ( ! $this->get_customer()->has_calculated_shipping() ) {
  1222. if ( ! $this->get_customer()->get_shipping_country() || ( ! $this->get_customer()->get_shipping_state() && ! $this->get_customer()->get_shipping_postcode() ) ) {
  1223. return false;
  1224. }
  1225. }
  1226. }
  1227. return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true );
  1228. }
  1229. /**
  1230. * Gets the shipping total (after calculation).
  1231. *
  1232. * @return string price or string for the shipping total
  1233. */
  1234. public function get_cart_shipping_total() {
  1235. if ( 0 < $this->get_shipping_total() ) {
  1236. if ( $this->display_prices_including_tax() ) {
  1237. $return = wc_price( $this->shipping_total + $this->shipping_tax_total );
  1238. if ( $this->shipping_tax_total > 0 && ! wc_prices_include_tax() ) {
  1239. $return .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
  1240. }
  1241. return $return;
  1242. } else {
  1243. $return = wc_price( $this->shipping_total );
  1244. if ( $this->shipping_tax_total > 0 && wc_prices_include_tax() ) {
  1245. $return .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
  1246. }
  1247. return $return;
  1248. }
  1249. } else {
  1250. return __( 'Free!', 'woocommerce' );
  1251. }
  1252. }
  1253. /**
  1254. * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
  1255. *
  1256. * Checks two types of coupons:
  1257. * 1. Where a list of customer emails are set (limits coupon usage to those defined).
  1258. * 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email).
  1259. *
  1260. * @param array $posted Post data.
  1261. */
  1262. public function check_customer_coupons( $posted ) {
  1263. foreach ( $this->get_applied_coupons() as $code ) {
  1264. $coupon = new WC_Coupon( $code );
  1265. if ( $coupon->is_valid() ) {
  1266. // Get user and posted emails to compare.
  1267. $current_user = wp_get_current_user();
  1268. $check_emails = array_unique(
  1269. array_filter(
  1270. array_map(
  1271. 'strtolower', array_map(
  1272. 'sanitize_email', array(
  1273. $posted['billing_email'],
  1274. $current_user->user_email,
  1275. )
  1276. )
  1277. )
  1278. )
  1279. );
  1280. // Limit to defined email addresses.
  1281. $restrictions = $coupon->get_email_restrictions();
  1282. if ( is_array( $restrictions ) && 0 < count( $restrictions ) && ! $this->is_coupon_emails_allowed( $check_emails, $restrictions ) ) {
  1283. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
  1284. $this->remove_coupon( $code );
  1285. }
  1286. // Usage limits per user - check against billing and user email and user ID.
  1287. $limit_per_user = $coupon->get_usage_limit_per_user();
  1288. if ( 0 < $limit_per_user ) {
  1289. $used_by = $coupon->get_used_by();
  1290. $usage_count = 0;
  1291. $user_id_matches = array( get_current_user_id() );
  1292. // Check usage against emails.
  1293. foreach ( $check_emails as $check_email ) {
  1294. $usage_count += count( array_keys( $used_by, $check_email, true ) );
  1295. $user = get_user_by( 'email', $check_email );
  1296. $user_id_matches[] = $user ? $user->ID : 0;
  1297. }
  1298. // Check against billing emails of existing users.
  1299. $users_query = new WP_User_Query(
  1300. array(
  1301. 'fields' => 'ID',
  1302. 'meta_query' => array(
  1303. array(
  1304. 'key' => '_billing_email',
  1305. 'value' => $check_emails,
  1306. 'compare' => 'IN',
  1307. ),
  1308. ),
  1309. )
  1310. ); // WPCS: slow query ok.
  1311. $user_id_matches = array_unique( array_filter( array_merge( $user_id_matches, $users_query->get_results() ) ) );
  1312. foreach ( $user_id_matches as $user_id ) {
  1313. $usage_count += count( array_keys( $used_by, (string) $user_id, true ) );
  1314. }
  1315. if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
  1316. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
  1317. $this->remove_coupon( $code );
  1318. }
  1319. }
  1320. }
  1321. }
  1322. }
  1323. /**
  1324. * Checks if the given email address(es) matches the ones specified on the coupon.
  1325. *
  1326. * @param array $check_emails Array of customer email addresses.
  1327. * @param array $restrictions Array of allowed email addresses.
  1328. * @return bool
  1329. */
  1330. public function is_coupon_emails_allowed( $check_emails, $restrictions ) {
  1331. foreach ( $check_emails as $check_email ) {
  1332. // With a direct match we return true.
  1333. if ( in_array( $check_email, $restrictions, true ) ) {
  1334. return true;
  1335. }
  1336. // Go through the allowed emails and return true if the email matches a wildcard.
  1337. foreach ( $restrictions as $restriction ) {
  1338. // Convert to PHP-regex syntax.
  1339. $regex = '/' . str_replace( '*', '(.+)?', $restriction ) . '/';
  1340. preg_match( $regex, $check_email, $match );
  1341. if ( ! empty( $match ) ) {
  1342. return true;
  1343. }
  1344. }
  1345. }
  1346. // No matches, this one isn't allowed.
  1347. return false;
  1348. }
  1349. /**
  1350. * Returns whether or not a discount has been applied.
  1351. *
  1352. * @param string $coupon_code Coupon code to check.
  1353. * @return bool
  1354. */
  1355. public function has_discount( $coupon_code = '' ) {
  1356. return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons, true ) : count( $this->applied_coupons ) > 0;
  1357. }
  1358. /**
  1359. * Applies a coupon code passed to the method.
  1360. *
  1361. * @param string $coupon_code - The code to apply.
  1362. * @return bool True if the coupon is applied, false if it does not exist or cannot be applied.
  1363. */
  1364. public function apply_coupon( $coupon_code ) {
  1365. // Coupons are globally disabled.
  1366. if ( ! wc_coupons_enabled() ) {
  1367. return false;
  1368. }
  1369. // Sanitize coupon code.
  1370. $coupon_code = wc_format_coupon_code( $coupon_code );
  1371. // Get the coupon.
  1372. $the_coupon = new WC_Coupon( $coupon_code );
  1373. // Prevent adding coupons by post ID.
  1374. if ( $the_coupon->get_code() !== $coupon_code ) {
  1375. $the_coupon->set_code( $coupon_code );
  1376. $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_EXIST );
  1377. return false;
  1378. }
  1379. // Check it can be used with cart.
  1380. if ( ! $the_coupon->is_valid() ) {
  1381. wc_add_notice( $the_coupon->get_error_message(), 'error' );
  1382. return false;
  1383. }
  1384. // Check if applied.
  1385. if ( $this->has_discount( $coupon_code ) ) {
  1386. $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED );
  1387. return false;
  1388. }
  1389. // If its individual use then remove other coupons.
  1390. if ( $the_coupon->get_individual_use() ) {
  1391. $coupons_to_keep = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons );
  1392. foreach ( $this->applied_coupons as $applied_coupon ) {
  1393. $keep_key = array_search( $applied_coupon, $coupons_to_keep, true );
  1394. if ( false === $keep_key ) {
  1395. $this->remove_coupon( $applied_coupon );
  1396. } else {
  1397. unset( $coupons_to_keep[ $keep_key ] );
  1398. }
  1399. }
  1400. if ( ! empty( $coupons_to_keep ) ) {
  1401. $this->applied_coupons += $coupons_to_keep;
  1402. }
  1403. }
  1404. // Check to see if an individual use coupon is set.
  1405. if ( $this->applied_coupons ) {
  1406. foreach ( $this->applied_coupons as $code ) {
  1407. $coupon = new WC_Coupon( $code );
  1408. if ( $coupon->get_individual_use() && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) {
  1409. // Reject new coupon.
  1410. $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY );
  1411. return false;
  1412. }
  1413. }
  1414. }
  1415. $this->applied_coupons[] = $coupon_code;
  1416. // Choose free shipping.
  1417. if ( $the_coupon->get_free_shipping() ) {
  1418. $packages = WC()->shipping->get_packages();
  1419. $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
  1420. foreach ( $packages as $i => $package ) {
  1421. $chosen_shipping_methods[ $i ] = 'free_shipping';
  1422. }
  1423. WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
  1424. }
  1425. $the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS );
  1426. do_action( 'woocommerce_applied_coupon', $coupon_code );
  1427. return true;
  1428. }
  1429. /**
  1430. * Get array of applied coupon objects and codes.
  1431. *
  1432. * @param null $deprecated No longer used.
  1433. * @return array of applied coupons
  1434. */
  1435. public function get_coupons( $deprecated = null ) {
  1436. $coupons = array();
  1437. if ( 'order' === $deprecated ) {
  1438. return $coupons;
  1439. }
  1440. foreach ( $this->get_applied_coupons() as $code ) {
  1441. $coupon = new WC_Coupon( $code );
  1442. $coupons[ $code ] = $coupon;
  1443. }
  1444. return $coupons;
  1445. }
  1446. /**
  1447. * Get the discount amount for a used coupon.
  1448. *
  1449. * @param string $code coupon code.
  1450. * @param bool $ex_tax inc or ex tax.
  1451. * @return float discount amount
  1452. */
  1453. public function get_coupon_discount_amount( $code, $ex_tax = true ) {
  1454. $totals = $this->get_coupon_discount_totals();
  1455. $discount_amount = isset( $totals[ $code ] ) ? $totals[ $code ] : 0;
  1456. if ( ! $ex_tax ) {
  1457. $discount_amount += $this->get_coupon_discount_tax_amount( $code );
  1458. }
  1459. return wc_cart_round_discount( $discount_amount, wc_get_price_decimals() );
  1460. }
  1461. /**
  1462. * Get the discount tax amount for a used coupon (for tax inclusive prices).
  1463. *
  1464. * @param string $code coupon code.
  1465. * @return float discount amount
  1466. */
  1467. public function get_coupon_discount_tax_amount( $code ) {
  1468. $totals = $this->get_coupon_discount_tax_totals();
  1469. return wc_cart_round_discount( isset( $totals[ $code ] ) ? $totals[ $code ] : 0, wc_get_price_decimals() );
  1470. }
  1471. /**
  1472. * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
  1473. *
  1474. * @param null $deprecated No longer used.
  1475. */
  1476. public function remove_coupons( $deprecated = null ) {
  1477. $this->set_coupon_discount_totals( array() );
  1478. $this->set_coupon_discount_tax_totals( array() );
  1479. $this->set_applied_coupons( array() );
  1480. $this->session->set_session();
  1481. }
  1482. /**
  1483. * Remove a single coupon by code.
  1484. *
  1485. * @param string $coupon_code Code of the coupon to remove.
  1486. * @return bool
  1487. */
  1488. public function remove_coupon( $coupon_code ) {
  1489. $coupon_code = wc_format_coupon_code( $coupon_code );
  1490. $position = array_search( $coupon_code, $this->get_applied_coupons(), true );
  1491. if ( false !== $position ) {
  1492. unset( $this->applied_coupons[ $position ] );
  1493. }
  1494. WC()->session->set( 'refresh_totals', true );
  1495. do_action( 'woocommerce_removed_coupon', $coupon_code );
  1496. return true;
  1497. }
  1498. /**
  1499. * Trigger an action so 3rd parties can add custom fees.
  1500. *
  1501. * @since 2.0.0
  1502. */
  1503. public function calculate_fees() {
  1504. do_action( 'woocommerce_cart_calculate_fees', $this );
  1505. }
  1506. /**
  1507. * Return reference to fees API.
  1508. *
  1509. * @since 3.2.0
  1510. * @return WC_Cart_Fees
  1511. */
  1512. public function fees_api() {
  1513. return $this->fees_api;
  1514. }
  1515. /**
  1516. * Add additional fee to the cart.
  1517. *
  1518. * This method should be called on a callback attached to the
  1519. * woocommerce_cart_calculate_fees action during cart/checkout. Fees do not
  1520. * persist.
  1521. *
  1522. * @uses WC_Cart_Fees::add_fee
  1523. * @param string $name Unique name for the fee. Multiple fees of the same name cannot be added.
  1524. * @param float $amount Fee amount (do not enter negative amounts).
  1525. * @param bool $taxable Is the fee taxable? (default: false).
  1526. * @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: '').
  1527. */
  1528. public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
  1529. $this->fees_api()->add_fee(
  1530. array(
  1531. 'name' => $name,
  1532. 'amount' => (float) $amount,
  1533. 'taxable' => $taxable,
  1534. 'tax_class' => $tax_class,
  1535. )
  1536. );
  1537. }
  1538. /**
  1539. * Return all added fees from the Fees API.
  1540. *
  1541. * @uses WC_Cart_Fees::get_fees
  1542. * @return array
  1543. */
  1544. public function get_fees() {
  1545. $fees = $this->fees_api()->get_fees();
  1546. if ( property_exists( $this, 'fees' ) ) {
  1547. $fees = $fees + (array) $this->fees;
  1548. }
  1549. return $fees;
  1550. }
  1551. /**
  1552. * Gets the total excluding taxes.
  1553. *
  1554. * @return string formatted price
  1555. */
  1556. public function get_total_ex_tax() {
  1557. return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( max( 0, $this->get_total( 'edit' ) - $this->get_total_tax() ) ) );
  1558. }
  1559. /**
  1560. * Gets the cart contents total (after calculation).
  1561. *
  1562. * @return string formatted price
  1563. */
  1564. public function get_cart_total() {
  1565. return apply_filters( 'woocommerce_cart_contents_total', wc_price( wc_prices_include_tax() ? $this->get_cart_contents_total() + $this->get_cart_contents_tax() : $this->get_cart_contents_total() ) );
  1566. }
  1567. /**
  1568. * Gets the sub total (after calculation).
  1569. *
  1570. * @param bool $compound whether to include compound taxes.
  1571. * @return string formatted price
  1572. */
  1573. public function get_cart_subtotal( $compound = false ) {
  1574. /**
  1575. * If the cart has compound tax, we want to show the subtotal as cart + shipping + non-compound taxes (after discount).
  1576. */
  1577. if ( $compound ) {
  1578. $cart_subtotal = wc_price( $this->get_cart_contents_total() + $this->get_shipping_total() + $this->get_taxes_total( false, false ) );
  1579. } elseif ( $this->display_prices_including_tax() ) {
  1580. $cart_subtotal = wc_price( $this->get_subtotal() + $this->get_subtotal_tax() );
  1581. if ( $this->get_subtotal_tax() > 0 && ! wc_prices_include_tax() ) {
  1582. $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
  1583. }
  1584. } else {
  1585. $cart_subtotal = wc_price( $this->get_subtotal() );
  1586. if ( $this->get_subtotal_tax() > 0 && wc_prices_include_tax() ) {
  1587. $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
  1588. }
  1589. }
  1590. return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this );
  1591. }
  1592. /**
  1593. * Get the product row price per item.
  1594. *
  1595. * @param WC_Product $product Product object.
  1596. * @return string formatted price
  1597. */
  1598. public function get_product_price( $product ) {
  1599. if ( $this->display_prices_including_tax() ) {
  1600. $product_price = wc_get_price_including_tax( $product );
  1601. } else {
  1602. $product_price = wc_get_price_excluding_tax( $product );
  1603. }
  1604. return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $product );
  1605. }
  1606. /**
  1607. * Get the product row subtotal.
  1608. *
  1609. * Gets the tax etc to avoid rounding issues.
  1610. *
  1611. * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
  1612. *
  1613. * @param WC_Product $product Product object.
  1614. * @param int $quantity Quantity being purchased.
  1615. * @return string formatted price
  1616. */
  1617. public function get_product_subtotal( $product, $quantity ) {
  1618. $price = $product->get_price();
  1619. if ( $product->is_taxable() ) {
  1620. if ( $this->display_prices_including_tax() ) {
  1621. $row_price = wc_get_price_including_tax( $product, array( 'qty' => $quantity ) );
  1622. $product_subtotal = wc_price( $row_price );
  1623. if ( ! wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
  1624. $product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
  1625. }
  1626. } else {
  1627. $row_price = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
  1628. $product_subtotal = wc_price( $row_price );
  1629. if ( wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
  1630. $product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
  1631. }
  1632. }
  1633. } else {
  1634. $row_price = $price * $quantity;
  1635. $product_subtotal = wc_price( $row_price );
  1636. }
  1637. return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this );
  1638. }
  1639. /**
  1640. * Gets the cart tax (after calculation).
  1641. *
  1642. * @return string formatted price
  1643. */
  1644. public function get_cart_tax() {
  1645. $cart_total_tax = wc_round_tax_total( $this->get_cart_contents_tax() + $this->get_shipping_tax() + $this->get_fee_tax() );
  1646. return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' );
  1647. }
  1648. /**
  1649. * Get a tax amount.
  1650. *
  1651. * @param string $tax_rate_id ID of the tax rate to get taxes for.
  1652. * @return float amount
  1653. */
  1654. public function get_tax_amount( $tax_rate_id ) {
  1655. $taxes = wc_array_merge_recursive_numeric( $this->get_cart_contents_taxes(), $this->get_fee_taxes() );
  1656. return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
  1657. }
  1658. /**
  1659. * Get a tax amount.
  1660. *
  1661. * @param string $tax_rate_id ID of the tax rate to get taxes for.
  1662. * @return float amount
  1663. */
  1664. public function get_shipping_tax_amount( $tax_rate_id ) {
  1665. $taxes = $this->get_shipping_taxes();
  1666. return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
  1667. }
  1668. /**
  1669. * Get tax row amounts with or without compound taxes includes.
  1670. *
  1671. * @param bool $compound True if getting compound taxes.
  1672. * @param bool $display True if getting total to display.
  1673. * @return float price
  1674. */
  1675. public function get_taxes_total( $compound = true, $display = true ) {
  1676. $total = 0;
  1677. $taxes = $this->get_taxes();
  1678. foreach ( $taxes as $key => $tax ) {
  1679. if ( ! $compound && WC_Tax::is_compound( $key ) ) {
  1680. continue;
  1681. }
  1682. $total += $tax;
  1683. }
  1684. if ( $display ) {
  1685. $total = wc_round_tax_total( $total );
  1686. }
  1687. return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this );
  1688. }
  1689. /**
  1690. * Gets the total discount amount.
  1691. *
  1692. * @return mixed formatted price or false if there are none
  1693. */
  1694. public function get_total_discount() {
  1695. return apply_filters( 'woocommerce_cart_total_discount', $this->get_discount_total() ? wc_price( $this->get_discount_total() ) : false, $this );
  1696. }
  1697. /**
  1698. * Reset cart totals to the defaults. Useful before running calculations.
  1699. *
  1700. * @access private
  1701. */
  1702. private function reset_totals() {
  1703. $this->totals = $this->default_totals;
  1704. $this->fees_api->remove_all_fees();
  1705. do_action( 'woocommerce_cart_reset', $this, false );
  1706. }
  1707. }