abstract-wc-order.php 57 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924
  1. <?php
  2. /**
  3. * Abstract Order
  4. *
  5. * Handles generic order data and database interaction which is extended by both
  6. * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
  7. *
  8. * @class WC_Abstract_Order
  9. * @version 3.0.0
  10. * @package WooCommerce/Classes
  11. */
  12. defined( 'ABSPATH' ) || exit;
  13. require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
  14. /**
  15. * WC_Abstract_Order class.
  16. */
  17. abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
  18. /**
  19. * Order Data array. This is the core order data exposed in APIs since 3.0.0.
  20. *
  21. * Notes: cart_tax = cart_tax is the new name for the legacy 'order_tax'
  22. * which is the tax for items only, not shipping.
  23. *
  24. * @since 3.0.0
  25. * @var array
  26. */
  27. protected $data = array(
  28. 'parent_id' => 0,
  29. 'status' => '',
  30. 'currency' => '',
  31. 'version' => '',
  32. 'prices_include_tax' => false,
  33. 'date_created' => null,
  34. 'date_modified' => null,
  35. 'discount_total' => 0,
  36. 'discount_tax' => 0,
  37. 'shipping_total' => 0,
  38. 'shipping_tax' => 0,
  39. 'cart_tax' => 0,
  40. 'total' => 0,
  41. 'total_tax' => 0,
  42. );
  43. /**
  44. * Order items will be stored here, sometimes before they persist in the DB.
  45. *
  46. * @since 3.0.0
  47. * @var array
  48. */
  49. protected $items = array();
  50. /**
  51. * Order items that need deleting are stored here.
  52. *
  53. * @since 3.0.0
  54. * @var array
  55. */
  56. protected $items_to_delete = array();
  57. /**
  58. * Stores meta in cache for future reads.
  59. *
  60. * A group must be set to to enable caching.
  61. *
  62. * @var string
  63. */
  64. protected $cache_group = 'orders';
  65. /**
  66. * Which data store to load.
  67. *
  68. * @var string
  69. */
  70. protected $data_store_name = 'order';
  71. /**
  72. * This is the name of this object type.
  73. *
  74. * @var string
  75. */
  76. protected $object_type = 'order';
  77. /**
  78. * Get the order if ID is passed, otherwise the order is new and empty.
  79. * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
  80. * should be used. It is possible, but the aforementioned are preferred and are the only.
  81. * methods that will be maintained going forward.
  82. *
  83. * @param int|object|WC_Order $order Order to read.
  84. */
  85. public function __construct( $order = 0 ) {
  86. parent::__construct( $order );
  87. if ( is_numeric( $order ) && $order > 0 ) {
  88. $this->set_id( $order );
  89. } elseif ( $order instanceof self ) {
  90. $this->set_id( $order->get_id() );
  91. } elseif ( ! empty( $order->ID ) ) {
  92. $this->set_id( $order->ID );
  93. } else {
  94. $this->set_object_read( true );
  95. }
  96. $this->data_store = WC_Data_Store::load( $this->data_store_name );
  97. if ( $this->get_id() > 0 ) {
  98. $this->data_store->read( $this );
  99. }
  100. }
  101. /**
  102. * Get internal type.
  103. *
  104. * @return string
  105. */
  106. public function get_type() {
  107. return 'shop_order';
  108. }
  109. /**
  110. * Get all class data in array format.
  111. *
  112. * @since 3.0.0
  113. * @return array
  114. */
  115. public function get_data() {
  116. return array_merge(
  117. array(
  118. 'id' => $this->get_id(),
  119. ),
  120. $this->data,
  121. array(
  122. 'meta_data' => $this->get_meta_data(),
  123. 'line_items' => $this->get_items( 'line_item' ),
  124. 'tax_lines' => $this->get_items( 'tax' ),
  125. 'shipping_lines' => $this->get_items( 'shipping' ),
  126. 'fee_lines' => $this->get_items( 'fee' ),
  127. 'coupon_lines' => $this->get_items( 'coupon' ),
  128. )
  129. );
  130. }
  131. /*
  132. |--------------------------------------------------------------------------
  133. | CRUD methods
  134. |--------------------------------------------------------------------------
  135. |
  136. | Methods which create, read, update and delete orders from the database.
  137. | Written in abstract fashion so that the way orders are stored can be
  138. | changed more easily in the future.
  139. |
  140. | A save method is included for convenience (chooses update or create based
  141. | on if the order exists yet).
  142. |
  143. */
  144. /**
  145. * Save data to the database.
  146. *
  147. * @since 3.0.0
  148. * @return int order ID
  149. */
  150. public function save() {
  151. if ( $this->data_store ) {
  152. // Trigger action before saving to the DB. Allows you to adjust object props before save.
  153. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
  154. if ( $this->get_id() ) {
  155. $this->data_store->update( $this );
  156. } else {
  157. $this->data_store->create( $this );
  158. }
  159. }
  160. $this->save_items();
  161. return $this->get_id();
  162. }
  163. /**
  164. * Save all order items which are part of this order.
  165. */
  166. protected function save_items() {
  167. $items_changed = false;
  168. foreach ( $this->items_to_delete as $item ) {
  169. $item->delete();
  170. $items_changed = true;
  171. }
  172. $this->items_to_delete = array();
  173. // Add/save items.
  174. foreach ( $this->items as $item_group => $items ) {
  175. if ( is_array( $items ) ) {
  176. $items = array_filter( $items );
  177. foreach ( $items as $item_key => $item ) {
  178. $item->set_order_id( $this->get_id() );
  179. $item_id = $item->save();
  180. // If ID changed (new item saved to DB)...
  181. if ( $item_id !== $item_key ) {
  182. $this->items[ $item_group ][ $item_id ] = $item;
  183. unset( $this->items[ $item_group ][ $item_key ] );
  184. $items_changed = true;
  185. }
  186. }
  187. }
  188. }
  189. if ( $items_changed ) {
  190. delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
  191. }
  192. }
  193. /*
  194. |--------------------------------------------------------------------------
  195. | Getters
  196. |--------------------------------------------------------------------------
  197. */
  198. /**
  199. * Get parent order ID.
  200. *
  201. * @since 3.0.0
  202. * @param string $context View or edit context.
  203. * @return integer
  204. */
  205. public function get_parent_id( $context = 'view' ) {
  206. return $this->get_prop( 'parent_id', $context );
  207. }
  208. /**
  209. * Gets order currency.
  210. *
  211. * @param string $context View or edit context.
  212. * @return string
  213. */
  214. public function get_currency( $context = 'view' ) {
  215. return $this->get_prop( 'currency', $context );
  216. }
  217. /**
  218. * Get order_version.
  219. *
  220. * @param string $context View or edit context.
  221. * @return string
  222. */
  223. public function get_version( $context = 'view' ) {
  224. return $this->get_prop( 'version', $context );
  225. }
  226. /**
  227. * Get prices_include_tax.
  228. *
  229. * @param string $context View or edit context.
  230. * @return bool
  231. */
  232. public function get_prices_include_tax( $context = 'view' ) {
  233. return $this->get_prop( 'prices_include_tax', $context );
  234. }
  235. /**
  236. * Get date_created.
  237. *
  238. * @param string $context View or edit context.
  239. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  240. */
  241. public function get_date_created( $context = 'view' ) {
  242. return $this->get_prop( 'date_created', $context );
  243. }
  244. /**
  245. * Get date_modified.
  246. *
  247. * @param string $context View or edit context.
  248. * @return WC_DateTime|NULL object if the date is set or null if there is no date.
  249. */
  250. public function get_date_modified( $context = 'view' ) {
  251. return $this->get_prop( 'date_modified', $context );
  252. }
  253. /**
  254. * Return the order statuses without wc- internal prefix.
  255. *
  256. * @param string $context View or edit context.
  257. * @return string
  258. */
  259. public function get_status( $context = 'view' ) {
  260. $status = $this->get_prop( 'status', $context );
  261. if ( empty( $status ) && 'view' === $context ) {
  262. // In view context, return the default status if no status has been set.
  263. $status = apply_filters( 'woocommerce_default_order_status', 'pending' );
  264. }
  265. return $status;
  266. }
  267. /**
  268. * Get discount_total.
  269. *
  270. * @param string $context View or edit context.
  271. * @return string
  272. */
  273. public function get_discount_total( $context = 'view' ) {
  274. return $this->get_prop( 'discount_total', $context );
  275. }
  276. /**
  277. * Get discount_tax.
  278. *
  279. * @param string $context View or edit context.
  280. * @return string
  281. */
  282. public function get_discount_tax( $context = 'view' ) {
  283. return $this->get_prop( 'discount_tax', $context );
  284. }
  285. /**
  286. * Get shipping_total.
  287. *
  288. * @param string $context View or edit context.
  289. * @return string
  290. */
  291. public function get_shipping_total( $context = 'view' ) {
  292. return $this->get_prop( 'shipping_total', $context );
  293. }
  294. /**
  295. * Get shipping_tax.
  296. *
  297. * @param string $context View or edit context.
  298. * @return string
  299. */
  300. public function get_shipping_tax( $context = 'view' ) {
  301. return $this->get_prop( 'shipping_tax', $context );
  302. }
  303. /**
  304. * Gets cart tax amount.
  305. *
  306. * @param string $context View or edit context.
  307. * @return float
  308. */
  309. public function get_cart_tax( $context = 'view' ) {
  310. return $this->get_prop( 'cart_tax', $context );
  311. }
  312. /**
  313. * Gets order grand total. incl. taxes. Used in gateways.
  314. *
  315. * @param string $context View or edit context.
  316. * @return float
  317. */
  318. public function get_total( $context = 'view' ) {
  319. return $this->get_prop( 'total', $context );
  320. }
  321. /**
  322. * Get total tax amount. Alias for get_order_tax().
  323. *
  324. * @param string $context View or edit context.
  325. * @return float
  326. */
  327. public function get_total_tax( $context = 'view' ) {
  328. return $this->get_prop( 'total_tax', $context );
  329. }
  330. /*
  331. |--------------------------------------------------------------------------
  332. | Non-CRUD Getters
  333. |--------------------------------------------------------------------------
  334. */
  335. /**
  336. * Gets the total discount amount.
  337. *
  338. * @param bool $ex_tax Show discount excl any tax.
  339. * @return float
  340. */
  341. public function get_total_discount( $ex_tax = true ) {
  342. if ( $ex_tax ) {
  343. $total_discount = $this->get_discount_total();
  344. } else {
  345. $total_discount = $this->get_discount_total() + $this->get_discount_tax();
  346. }
  347. return apply_filters( 'woocommerce_order_get_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
  348. }
  349. /**
  350. * Gets order subtotal.
  351. *
  352. * @return float
  353. */
  354. public function get_subtotal() {
  355. $subtotal = 0;
  356. foreach ( $this->get_items() as $item ) {
  357. $subtotal += $item->get_subtotal();
  358. }
  359. return apply_filters( 'woocommerce_order_get_subtotal', (double) $subtotal, $this );
  360. }
  361. /**
  362. * Get taxes, merged by code, formatted ready for output.
  363. *
  364. * @return array
  365. */
  366. public function get_tax_totals() {
  367. $tax_totals = array();
  368. foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
  369. $code = $tax->get_rate_code();
  370. if ( ! isset( $tax_totals[ $code ] ) ) {
  371. $tax_totals[ $code ] = new stdClass();
  372. $tax_totals[ $code ]->amount = 0;
  373. }
  374. $tax_totals[ $code ]->id = $key;
  375. $tax_totals[ $code ]->rate_id = $tax->get_rate_id();
  376. $tax_totals[ $code ]->is_compound = $tax->is_compound();
  377. $tax_totals[ $code ]->label = $tax->get_label();
  378. $tax_totals[ $code ]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
  379. $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
  380. }
  381. if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
  382. $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
  383. $tax_totals = array_intersect_key( $tax_totals, $amounts );
  384. }
  385. return apply_filters( 'woocommerce_order_get_tax_totals', $tax_totals, $this );
  386. }
  387. /**
  388. * Get all valid statuses for this order
  389. *
  390. * @since 3.0.0
  391. * @return array Internal status keys e.g. 'wc-processing'
  392. */
  393. protected function get_valid_statuses() {
  394. return array_keys( wc_get_order_statuses() );
  395. }
  396. /**
  397. * Get user ID. Used by orders, not other order types like refunds.
  398. *
  399. * @param string $context View or edit context.
  400. * @return int
  401. */
  402. public function get_user_id( $context = 'view' ) {
  403. return 0;
  404. }
  405. /**
  406. * Get user. Used by orders, not other order types like refunds.
  407. *
  408. * @return WP_User|false
  409. */
  410. public function get_user() {
  411. return false;
  412. }
  413. /*
  414. |--------------------------------------------------------------------------
  415. | Setters
  416. |--------------------------------------------------------------------------
  417. |
  418. | Functions for setting order data. These should not update anything in the
  419. | database itself and should only change what is stored in the class
  420. | object. However, for backwards compatibility pre 3.0.0 some of these
  421. | setters may handle both.
  422. */
  423. /**
  424. * Set parent order ID.
  425. *
  426. * @since 3.0.0
  427. * @param int $value Value to set.
  428. * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
  429. */
  430. public function set_parent_id( $value ) {
  431. if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
  432. $this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
  433. }
  434. $this->set_prop( 'parent_id', absint( $value ) );
  435. }
  436. /**
  437. * Set order status.
  438. *
  439. * @since 3.0.0
  440. * @param string $new_status Status to change the order to. No internal wc- prefix is required.
  441. * @return array details of change
  442. */
  443. public function set_status( $new_status ) {
  444. $old_status = $this->get_status();
  445. $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
  446. // If setting the status, ensure it's set to a valid status.
  447. if ( true === $this->object_read ) {
  448. // Only allow valid new status.
  449. if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status ) {
  450. $new_status = 'pending';
  451. }
  452. // If the old status is set but unknown (e.g. draft) assume its pending for action usage.
  453. if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) {
  454. $old_status = 'pending';
  455. }
  456. }
  457. $this->set_prop( 'status', $new_status );
  458. return array(
  459. 'from' => $old_status,
  460. 'to' => $new_status,
  461. );
  462. }
  463. /**
  464. * Set order_version.
  465. *
  466. * @param string $value Value to set.
  467. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  468. */
  469. public function set_version( $value ) {
  470. $this->set_prop( 'version', $value );
  471. }
  472. /**
  473. * Set order_currency.
  474. *
  475. * @param string $value Value to set.
  476. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  477. */
  478. public function set_currency( $value ) {
  479. if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) {
  480. $this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
  481. }
  482. $this->set_prop( 'currency', $value ? $value : get_woocommerce_currency() );
  483. }
  484. /**
  485. * Set prices_include_tax.
  486. *
  487. * @param bool $value Value to set.
  488. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  489. */
  490. public function set_prices_include_tax( $value ) {
  491. $this->set_prop( 'prices_include_tax', (bool) $value );
  492. }
  493. /**
  494. * Set date_created.
  495. *
  496. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
  497. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  498. */
  499. public function set_date_created( $date = null ) {
  500. $this->set_date_prop( 'date_created', $date );
  501. }
  502. /**
  503. * Set date_modified.
  504. *
  505. * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
  506. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  507. */
  508. public function set_date_modified( $date = null ) {
  509. $this->set_date_prop( 'date_modified', $date );
  510. }
  511. /**
  512. * Set discount_total.
  513. *
  514. * @param string $value Value to set.
  515. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  516. */
  517. public function set_discount_total( $value ) {
  518. $this->set_prop( 'discount_total', wc_format_decimal( $value ) );
  519. }
  520. /**
  521. * Set discount_tax.
  522. *
  523. * @param string $value Value to set.
  524. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  525. */
  526. public function set_discount_tax( $value ) {
  527. $this->set_prop( 'discount_tax', wc_format_decimal( $value ) );
  528. }
  529. /**
  530. * Set shipping_total.
  531. *
  532. * @param string $value Value to set.
  533. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  534. */
  535. public function set_shipping_total( $value ) {
  536. $this->set_prop( 'shipping_total', wc_format_decimal( $value ) );
  537. }
  538. /**
  539. * Set shipping_tax.
  540. *
  541. * @param string $value Value to set.
  542. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  543. */
  544. public function set_shipping_tax( $value ) {
  545. $this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
  546. $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
  547. }
  548. /**
  549. * Set cart tax.
  550. *
  551. * @param string $value Value to set.
  552. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  553. */
  554. public function set_cart_tax( $value ) {
  555. $this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
  556. $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
  557. }
  558. /**
  559. * Sets order tax (sum of cart and shipping tax). Used internally only.
  560. *
  561. * @param string $value Value to set.
  562. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  563. */
  564. protected function set_total_tax( $value ) {
  565. $this->set_prop( 'total_tax', wc_format_decimal( $value ) );
  566. }
  567. /**
  568. * Set total.
  569. *
  570. * @param string $value Value to set.
  571. * @param string $deprecated Function used to set different totals based on this.
  572. *
  573. * @return bool|void
  574. * @throws WC_Data_Exception Exception may be thrown if value is invalid.
  575. */
  576. public function set_total( $value, $deprecated = '' ) {
  577. if ( $deprecated ) {
  578. wc_deprecated_argument( 'total_type', '3.0', 'Use dedicated total setter methods instead.' );
  579. return $this->legacy_set_total( $value, $deprecated );
  580. }
  581. $this->set_prop( 'total', wc_format_decimal( $value, wc_get_price_decimals() ) );
  582. }
  583. /*
  584. |--------------------------------------------------------------------------
  585. | Order Item Handling
  586. |--------------------------------------------------------------------------
  587. |
  588. | Order items are used for products, taxes, shipping, and fees within
  589. | each order.
  590. */
  591. /**
  592. * Remove all line items (products, coupons, shipping, taxes) from the order.
  593. *
  594. * @param string $type Order item type. Default null.
  595. */
  596. public function remove_order_items( $type = null ) {
  597. if ( ! empty( $type ) ) {
  598. $this->data_store->delete_items( $this, $type );
  599. $group = $this->type_to_group( $type );
  600. if ( $group ) {
  601. unset( $this->items[ $group ] );
  602. }
  603. } else {
  604. $this->data_store->delete_items( $this );
  605. $this->items = array();
  606. }
  607. }
  608. /**
  609. * Convert a type to a types group.
  610. *
  611. * @param string $type type to lookup.
  612. * @return string
  613. */
  614. protected function type_to_group( $type ) {
  615. $type_to_group = apply_filters( 'woocommerce_order_type_to_group', array(
  616. 'line_item' => 'line_items',
  617. 'tax' => 'tax_lines',
  618. 'shipping' => 'shipping_lines',
  619. 'fee' => 'fee_lines',
  620. 'coupon' => 'coupon_lines',
  621. ) );
  622. return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
  623. }
  624. /**
  625. * Return an array of items/products within this order.
  626. *
  627. * @param string|array $types Types of line items to get (array or string).
  628. * @return WC_Order_Item[]
  629. */
  630. public function get_items( $types = 'line_item' ) {
  631. $items = array();
  632. $types = array_filter( (array) $types );
  633. foreach ( $types as $type ) {
  634. $group = $this->type_to_group( $type );
  635. if ( $group ) {
  636. if ( ! isset( $this->items[ $group ] ) ) {
  637. $this->items[ $group ] = array_filter( $this->data_store->read_items( $this, $type ) );
  638. }
  639. // Don't use array_merge here because keys are numeric.
  640. $items = $items + $this->items[ $group ];
  641. }
  642. }
  643. return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
  644. }
  645. /**
  646. * Return an array of fees within this order.
  647. *
  648. * @return WC_Order_item_Fee[]
  649. */
  650. public function get_fees() {
  651. return $this->get_items( 'fee' );
  652. }
  653. /**
  654. * Return an array of taxes within this order.
  655. *
  656. * @return WC_Order_Item_Tax[]
  657. */
  658. public function get_taxes() {
  659. return $this->get_items( 'tax' );
  660. }
  661. /**
  662. * Return an array of shipping costs within this order.
  663. *
  664. * @return WC_Order_Item_Shipping[]
  665. */
  666. public function get_shipping_methods() {
  667. return $this->get_items( 'shipping' );
  668. }
  669. /**
  670. * Gets formatted shipping method title.
  671. *
  672. * @return string
  673. */
  674. public function get_shipping_method() {
  675. $names = array();
  676. foreach ( $this->get_shipping_methods() as $shipping_method ) {
  677. $names[] = $shipping_method->get_name();
  678. }
  679. return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
  680. }
  681. /**
  682. * Get coupon codes only.
  683. *
  684. * @return array
  685. */
  686. public function get_used_coupons() {
  687. $coupon_codes = array();
  688. $coupons = $this->get_items( 'coupon' );
  689. if ( $coupons ) {
  690. foreach ( $coupons as $coupon ) {
  691. $coupon_codes[] = $coupon->get_code();
  692. }
  693. }
  694. return $coupon_codes;
  695. }
  696. /**
  697. * Gets the count of order items of a certain type.
  698. *
  699. * @param string $item_type Item type to lookup.
  700. * @return int|string
  701. */
  702. public function get_item_count( $item_type = '' ) {
  703. $items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
  704. $count = 0;
  705. foreach ( $items as $item ) {
  706. $count += $item->get_quantity();
  707. }
  708. return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
  709. }
  710. /**
  711. * Get an order item object, based on its type.
  712. *
  713. * @since 3.0.0
  714. * @param int $item_id ID of item to get.
  715. * @param bool $load_from_db Prior to 3.2 this item was loaded direct from WC_Order_Factory, not this object. This param is here for backwards compatility with that. If false, uses the local items variable instead.
  716. * @return WC_Order_Item|false
  717. */
  718. public function get_item( $item_id, $load_from_db = true ) {
  719. if ( $load_from_db ) {
  720. return WC_Order_Factory::get_order_item( $item_id );
  721. }
  722. // Search for item id.
  723. if ( $this->items ) {
  724. foreach ( $this->items as $group => $items ) {
  725. if ( isset( $items[ $item_id ] ) ) {
  726. return $items[ $item_id ];
  727. }
  728. }
  729. }
  730. // Load all items of type and cache.
  731. $type = $this->data_store->get_order_item_type( $this, $item_id );
  732. if ( ! $type ) {
  733. return false;
  734. }
  735. $items = $this->get_items( $type );
  736. return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
  737. }
  738. /**
  739. * Get key for where a certain item type is stored in _items.
  740. *
  741. * @since 3.0.0
  742. * @param string $item object Order item (product, shipping, fee, coupon, tax).
  743. * @return string
  744. */
  745. protected function get_items_key( $item ) {
  746. if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
  747. return 'line_items';
  748. } elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
  749. return 'fee_lines';
  750. } elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
  751. return 'shipping_lines';
  752. } elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
  753. return 'tax_lines';
  754. } elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
  755. return 'coupon_lines';
  756. }
  757. return apply_filters( 'woocommerce_get_items_key', '', $item );
  758. }
  759. /**
  760. * Remove item from the order.
  761. *
  762. * @param int $item_id Item ID to delete.
  763. * @return false|void
  764. */
  765. public function remove_item( $item_id ) {
  766. $item = $this->get_item( $item_id, false );
  767. $items_key = $item ? $this->get_items_key( $item ) : false;
  768. if ( ! $items_key ) {
  769. return false;
  770. }
  771. // Unset and remove later.
  772. $this->items_to_delete[] = $item;
  773. unset( $this->items[ $items_key ][ $item->get_id() ] );
  774. }
  775. /**
  776. * Adds an order item to this order. The order item will not persist until save.
  777. *
  778. * @since 3.0.0
  779. * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax).
  780. * @return false|void
  781. */
  782. public function add_item( $item ) {
  783. $items_key = $this->get_items_key( $item );
  784. if ( ! $items_key ) {
  785. return false;
  786. }
  787. // Make sure existing items are loaded so we can append this new one.
  788. if ( ! isset( $this->items[ $items_key ] ) ) {
  789. $this->items[ $items_key ] = $this->get_items( $item->get_type() );
  790. }
  791. // Set parent.
  792. $item->set_order_id( $this->get_id() );
  793. // Append new row with generated temporary ID.
  794. $item_id = $item->get_id();
  795. if ( $item_id ) {
  796. $this->items[ $items_key ][ $item_id ] = $item;
  797. } else {
  798. $this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item;
  799. }
  800. }
  801. /**
  802. * Apply a coupon to the order and recalculate totals.
  803. *
  804. * @since 3.2.0
  805. * @param string|WC_Coupon $raw_coupon Coupon code or object.
  806. * @return true|WP_Error True if applied, error if not.
  807. */
  808. public function apply_coupon( $raw_coupon ) {
  809. if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
  810. $coupon = $raw_coupon;
  811. } elseif ( is_string( $raw_coupon ) ) {
  812. $code = wc_format_coupon_code( $raw_coupon );
  813. $coupon = new WC_Coupon( $code );
  814. if ( $coupon->get_code() !== $code ) {
  815. return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
  816. }
  817. $discounts = new WC_Discounts( $this );
  818. $valid = $discounts->is_coupon_valid( $coupon );
  819. if ( is_wp_error( $valid ) ) {
  820. return $valid;
  821. }
  822. } else {
  823. return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
  824. }
  825. // Check to make sure coupon is not already applied.
  826. $applied_coupons = $this->get_items( 'coupon' );
  827. foreach ( $applied_coupons as $applied_coupon ) {
  828. if ( $applied_coupon->get_code() === $coupon->get_code() ) {
  829. return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
  830. }
  831. }
  832. $discounts = new WC_Discounts( $this );
  833. $applied = $discounts->apply_coupon( $coupon );
  834. if ( is_wp_error( $applied ) ) {
  835. return $applied;
  836. }
  837. $this->set_coupon_discount_amounts( $discounts );
  838. $this->set_item_discount_amounts( $discounts );
  839. // Recalculate totals and taxes.
  840. $this->calculate_totals( true );
  841. // Record usage so counts and validation is correct.
  842. $used_by = $this->get_user_id();
  843. if ( ! $used_by ) {
  844. $used_by = $this->get_billing_email();
  845. }
  846. $coupon->increase_usage_count( $used_by );
  847. return true;
  848. }
  849. /**
  850. * Remove a coupon from the order and recalculate totals.
  851. *
  852. * Coupons affect line item totals, but there is no relationship between
  853. * coupon and line total, so to remove a coupon we need to work from the
  854. * line subtotal (price before discount) and re-apply all coupons in this
  855. * order.
  856. *
  857. * Manual discounts are not affected; those are separate and do not affect
  858. * stored line totals.
  859. *
  860. * @since 3.2.0
  861. * @param string $code Coupon code.
  862. * @return void
  863. */
  864. public function remove_coupon( $code ) {
  865. $coupons = $this->get_items( 'coupon' );
  866. // Remove the coupon line.
  867. foreach ( $coupons as $item_id => $coupon ) {
  868. if ( $coupon->get_code() === $code ) {
  869. $this->remove_item( $item_id );
  870. $coupon_object = new WC_Coupon( $code );
  871. $coupon_object->decrease_usage_count( $this->get_user_id() );
  872. $this->recalculate_coupons();
  873. break;
  874. }
  875. }
  876. }
  877. /**
  878. * Apply all coupons in this order again to all line items.
  879. *
  880. * @since 3.2.0
  881. */
  882. protected function recalculate_coupons() {
  883. // Reset line item totals.
  884. foreach ( $this->get_items() as $item ) {
  885. $item->set_total( $item->get_subtotal() );
  886. $item->set_total_tax( $item->get_subtotal_tax() );
  887. }
  888. $discounts = new WC_Discounts( $this );
  889. foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
  890. $coupon_code = $coupon_item->get_code();
  891. $coupon_id = wc_get_coupon_id_by_code( $coupon_code );
  892. // If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID.
  893. if ( $coupon_id ) {
  894. $coupon_object = new WC_Coupon( $coupon_id );
  895. } else {
  896. // If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
  897. $coupon_object = new WC_Coupon();
  898. $coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
  899. $coupon_object->set_code( $coupon_code );
  900. $coupon_object->set_virtual( true );
  901. // If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
  902. if ( ! $coupon_object->get_amount() ) {
  903. // If the order originally had prices including tax, remove the discount + discount tax.
  904. if ( $this->get_prices_include_tax() ) {
  905. $coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
  906. } else {
  907. $coupon_object->set_amount( $coupon_item->get_discount() );
  908. }
  909. $coupon_object->set_discount_type( 'fixed_cart' );
  910. }
  911. }
  912. /**
  913. * Allow developers to filter this coupon before it get's re-applied to the order.
  914. *
  915. * @since 3.2.0
  916. */
  917. $coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
  918. if ( $coupon_object ) {
  919. $discounts->apply_coupon( $coupon_object, false );
  920. }
  921. }
  922. $this->set_coupon_discount_amounts( $discounts );
  923. $this->set_item_discount_amounts( $discounts );
  924. // Recalculate totals and taxes.
  925. $this->calculate_totals( true );
  926. }
  927. /**
  928. * After applying coupons via the WC_Discounts class, update line items.
  929. *
  930. * @since 3.2.0
  931. * @param WC_Discounts $discounts Discounts class.
  932. */
  933. protected function set_item_discount_amounts( $discounts ) {
  934. $item_discounts = $discounts->get_discounts_by_item();
  935. if ( $item_discounts ) {
  936. foreach ( $item_discounts as $item_id => $amount ) {
  937. $item = $this->get_item( $item_id, false );
  938. // If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
  939. if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
  940. $amount_tax = WC_Tax::get_tax_total( WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true ) );
  941. $amount -= $amount_tax;
  942. $item->set_total( max( 0, $item->get_total() - $amount ) );
  943. } else {
  944. $item->set_total( max( 0, $item->get_total() - $amount ) );
  945. }
  946. }
  947. }
  948. }
  949. /**
  950. * After applying coupons via the WC_Discounts class, update or create coupon items.
  951. *
  952. * @since 3.2.0
  953. * @param WC_Discounts $discounts Discounts class.
  954. */
  955. protected function set_coupon_discount_amounts( $discounts ) {
  956. $coupons = $this->get_items( 'coupon' );
  957. $coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
  958. $all_discounts = $discounts->get_discounts();
  959. $coupon_discounts = $discounts->get_discounts_by_coupon();
  960. if ( $coupon_discounts ) {
  961. foreach ( $coupon_discounts as $coupon_code => $amount ) {
  962. $item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
  963. if ( ! $item_id ) {
  964. $coupon_item = new WC_Order_Item_Coupon();
  965. $coupon_item->set_code( $coupon_code );
  966. } else {
  967. $coupon_item = $this->get_item( $item_id, false );
  968. }
  969. $discount_tax = 0;
  970. // Work out how much tax has been removed as a result of the discount from this coupon.
  971. foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
  972. $item = $this->get_item( $item_id, false );
  973. if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
  974. $amount_tax = array_sum( WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true ) );
  975. $discount_tax += $amount_tax;
  976. $amount = $amount - $amount_tax;
  977. } else {
  978. $discount_tax += array_sum( WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) ) );
  979. }
  980. }
  981. $coupon_item->set_discount( $amount );
  982. $coupon_item->set_discount_tax( $discount_tax );
  983. $this->add_item( $coupon_item );
  984. }
  985. }
  986. }
  987. /**
  988. * Add a product line item to the order. This is the only line item type with
  989. * its own method because it saves looking up order amounts (costs are added up for you).
  990. *
  991. * @param WC_Product $product Product object.
  992. * @param int $qty Quantity to add.
  993. * @param array $args Args for the added product.
  994. * @return int
  995. * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
  996. */
  997. public function add_product( $product, $qty = 1, $args = array() ) {
  998. if ( $product ) {
  999. $default_args = array(
  1000. 'name' => $product->get_name(),
  1001. 'tax_class' => $product->get_tax_class(),
  1002. 'product_id' => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
  1003. 'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
  1004. 'variation' => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
  1005. 'subtotal' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
  1006. 'total' => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
  1007. 'quantity' => $qty,
  1008. );
  1009. } else {
  1010. $default_args = array(
  1011. 'quantity' => $qty,
  1012. );
  1013. }
  1014. $args = wp_parse_args( $args, $default_args );
  1015. // BW compatibility with old args.
  1016. if ( isset( $args['totals'] ) ) {
  1017. foreach ( $args['totals'] as $key => $value ) {
  1018. if ( 'tax' === $key ) {
  1019. $args['total_tax'] = $value;
  1020. } elseif ( 'tax_data' === $key ) {
  1021. $args['taxes'] = $value;
  1022. } else {
  1023. $args[ $key ] = $value;
  1024. }
  1025. }
  1026. }
  1027. $item = new WC_Order_Item_Product();
  1028. $item->set_props( $args );
  1029. $item->set_backorder_meta();
  1030. $item->set_order_id( $this->get_id() );
  1031. $item->save();
  1032. $this->add_item( $item );
  1033. wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' );
  1034. delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
  1035. return $item->get_id();
  1036. }
  1037. /*
  1038. |--------------------------------------------------------------------------
  1039. | Payment Token Handling
  1040. |--------------------------------------------------------------------------
  1041. |
  1042. | Payment tokens are hashes used to take payments by certain gateways.
  1043. |
  1044. */
  1045. /**
  1046. * Add a payment token to an order
  1047. *
  1048. * @since 2.6
  1049. * @param WC_Payment_Token $token Payment token object.
  1050. * @return boolean|int The new token ID or false if it failed.
  1051. */
  1052. public function add_payment_token( $token ) {
  1053. if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
  1054. return false;
  1055. }
  1056. $token_ids = $this->data_store->get_payment_token_ids( $this );
  1057. $token_ids[] = $token->get_id();
  1058. $this->data_store->update_payment_token_ids( $this, $token_ids );
  1059. do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
  1060. return $token->get_id();
  1061. }
  1062. /**
  1063. * Returns a list of all payment tokens associated with the current order
  1064. *
  1065. * @since 2.6
  1066. * @return array An array of payment token objects
  1067. */
  1068. public function get_payment_tokens() {
  1069. return $this->data_store->get_payment_token_ids( $this );
  1070. }
  1071. /*
  1072. |--------------------------------------------------------------------------
  1073. | Calculations.
  1074. |--------------------------------------------------------------------------
  1075. |
  1076. | These methods calculate order totals and taxes based on the current data.
  1077. |
  1078. */
  1079. /**
  1080. * Calculate shipping total.
  1081. *
  1082. * @since 2.2
  1083. * @return float
  1084. */
  1085. public function calculate_shipping() {
  1086. $shipping_total = 0;
  1087. foreach ( $this->get_shipping_methods() as $shipping ) {
  1088. $shipping_total += $shipping->get_total();
  1089. }
  1090. $this->set_shipping_total( $shipping_total );
  1091. $this->save();
  1092. return $this->get_shipping_total();
  1093. }
  1094. /**
  1095. * Get all tax classes for items in the order.
  1096. *
  1097. * @since 2.6.3
  1098. * @return array
  1099. */
  1100. public function get_items_tax_classes() {
  1101. $found_tax_classes = array();
  1102. foreach ( $this->get_items() as $item ) {
  1103. if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
  1104. $found_tax_classes[] = $item->get_tax_class();
  1105. }
  1106. }
  1107. return array_unique( $found_tax_classes );
  1108. }
  1109. /**
  1110. * Get tax location for this order.
  1111. *
  1112. * @since 3.2.0
  1113. * @param array $args array Override the location.
  1114. * @return array
  1115. */
  1116. protected function get_tax_location( $args = array() ) {
  1117. $tax_based_on = get_option( 'woocommerce_tax_based_on' );
  1118. if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
  1119. $tax_based_on = 'billing';
  1120. }
  1121. $args = wp_parse_args( $args, array(
  1122. 'country' => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
  1123. 'state' => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
  1124. 'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
  1125. 'city' => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
  1126. ) );
  1127. // Default to base.
  1128. if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
  1129. $default = wc_get_base_location();
  1130. $args['country'] = $default['country'];
  1131. $args['state'] = $default['state'];
  1132. $args['postcode'] = '';
  1133. $args['city'] = '';
  1134. }
  1135. return $args;
  1136. }
  1137. /**
  1138. * Calculate taxes for all line items and shipping, and store the totals and tax rows.
  1139. *
  1140. * If by default the taxes are based on the shipping address and the current order doesn't
  1141. * have any, it would use the billing address rather than using the Shopping base location.
  1142. *
  1143. * Will use the base country unless customer addresses are set.
  1144. *
  1145. * @param array $args Added in 3.0.0 to pass things like location.
  1146. */
  1147. public function calculate_taxes( $args = array() ) {
  1148. do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
  1149. $calculate_tax_for = $this->get_tax_location( $args );
  1150. $shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
  1151. if ( 'inherit' === $shipping_tax_class ) {
  1152. $found_classes = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
  1153. $shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
  1154. }
  1155. $is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
  1156. // Trigger tax recalculation for all items.
  1157. foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
  1158. if ( ! $is_vat_exempt ) {
  1159. $item->calculate_taxes( $calculate_tax_for );
  1160. } else {
  1161. $item->set_taxes( false );
  1162. }
  1163. }
  1164. foreach ( $this->get_shipping_methods() as $item_id => $item ) {
  1165. if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
  1166. $item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
  1167. } else {
  1168. $item->set_taxes( false );
  1169. }
  1170. }
  1171. $this->update_taxes();
  1172. }
  1173. /**
  1174. * Update tax lines for the order based on the line item taxes themselves.
  1175. */
  1176. public function update_taxes() {
  1177. $cart_taxes = array();
  1178. $shipping_taxes = array();
  1179. $existing_taxes = $this->get_taxes();
  1180. $saved_rate_ids = array();
  1181. foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
  1182. $taxes = $item->get_taxes();
  1183. foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
  1184. $tax_amount = (float) $tax;
  1185. if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
  1186. $tax_amount = wc_round_tax_total( $tax_amount );
  1187. }
  1188. $cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
  1189. }
  1190. }
  1191. foreach ( $this->get_shipping_methods() as $item_id => $item ) {
  1192. $taxes = $item->get_taxes();
  1193. foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
  1194. $tax_amount = (float) $tax;
  1195. if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
  1196. $tax_amount = wc_round_tax_total( $tax_amount );
  1197. }
  1198. $shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
  1199. }
  1200. }
  1201. foreach ( $existing_taxes as $tax ) {
  1202. // Remove taxes which no longer exist for cart/shipping.
  1203. if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
  1204. $this->remove_item( $tax->get_id() );
  1205. continue;
  1206. }
  1207. $saved_rate_ids[] = $tax->get_rate_id();
  1208. $tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
  1209. $tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
  1210. $tax->save();
  1211. }
  1212. $new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
  1213. // New taxes.
  1214. foreach ( $new_rate_ids as $tax_rate_id ) {
  1215. $item = new WC_Order_Item_Tax();
  1216. $item->set_rate( $tax_rate_id );
  1217. $item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
  1218. $item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
  1219. $this->add_item( $item );
  1220. }
  1221. if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
  1222. $this->set_shipping_tax( wc_round_tax_total( array_sum( array_map( 'wc_round_tax_total', $shipping_taxes ) ) ) );
  1223. $this->set_cart_tax( wc_round_tax_total( array_sum( array_map( 'wc_round_tax_total', $cart_taxes ) ) ) );
  1224. } else {
  1225. $this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
  1226. $this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
  1227. }
  1228. $this->save();
  1229. }
  1230. /**
  1231. * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
  1232. *
  1233. * @since 2.2
  1234. * @param bool $and_taxes Calc taxes if true.
  1235. * @return float calculated grand total.
  1236. */
  1237. public function calculate_totals( $and_taxes = true ) {
  1238. do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
  1239. $cart_subtotal = 0;
  1240. $cart_total = 0;
  1241. $fee_total = 0;
  1242. $shipping_total = 0;
  1243. $cart_subtotal_tax = 0;
  1244. $cart_total_tax = 0;
  1245. // Sum line item costs.
  1246. foreach ( $this->get_items() as $item ) {
  1247. $cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
  1248. $cart_total += round( $item->get_total(), wc_get_price_decimals() );
  1249. }
  1250. // Sum shipping costs.
  1251. foreach ( $this->get_shipping_methods() as $shipping ) {
  1252. $shipping_total += round( $shipping->get_total(), wc_get_price_decimals() );
  1253. }
  1254. $this->set_shipping_total( $shipping_total );
  1255. // Sum fee costs.
  1256. foreach ( $this->get_fees() as $item ) {
  1257. $amount = $item->get_amount();
  1258. if ( 0 > $amount ) {
  1259. $item->set_total( $amount );
  1260. $max_discount = round( $cart_total + $fee_total + $shipping_total, wc_get_price_decimals() ) * -1;
  1261. if ( $item->get_total() < $max_discount ) {
  1262. $item->set_total( $max_discount );
  1263. }
  1264. }
  1265. $fee_total += $item->get_total();
  1266. }
  1267. // Calculate taxes for items, shipping, discounts. Note; this also triggers save().
  1268. if ( $and_taxes ) {
  1269. $this->calculate_taxes();
  1270. }
  1271. // Sum taxes.
  1272. foreach ( $this->get_items() as $item ) {
  1273. $cart_subtotal_tax += $item->get_subtotal_tax();
  1274. $cart_total_tax += $item->get_total_tax();
  1275. }
  1276. $this->set_discount_total( $cart_subtotal - $cart_total );
  1277. $this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
  1278. $this->set_total( round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
  1279. do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
  1280. $this->save();
  1281. return $this->get_total();
  1282. }
  1283. /**
  1284. * Get item subtotal - this is the cost before discount.
  1285. *
  1286. * @param object $item Item to get total from.
  1287. * @param bool $inc_tax (default: false).
  1288. * @param bool $round (default: true).
  1289. * @return float
  1290. */
  1291. public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
  1292. $subtotal = 0;
  1293. if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
  1294. if ( $inc_tax ) {
  1295. $subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
  1296. } else {
  1297. $subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
  1298. }
  1299. $subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
  1300. }
  1301. return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
  1302. }
  1303. /**
  1304. * Get line subtotal - this is the cost before discount.
  1305. *
  1306. * @param object $item Item to get total from.
  1307. * @param bool $inc_tax (default: false).
  1308. * @param bool $round (default: true).
  1309. * @return float
  1310. */
  1311. public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
  1312. $subtotal = 0;
  1313. if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
  1314. if ( $inc_tax ) {
  1315. $subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
  1316. } else {
  1317. $subtotal = $item->get_subtotal();
  1318. }
  1319. $subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
  1320. }
  1321. return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
  1322. }
  1323. /**
  1324. * Calculate item cost - useful for gateways.
  1325. *
  1326. * @param object $item Item to get total from.
  1327. * @param bool $inc_tax (default: false).
  1328. * @param bool $round (default: true).
  1329. * @return float
  1330. */
  1331. public function get_item_total( $item, $inc_tax = false, $round = true ) {
  1332. $total = 0;
  1333. if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
  1334. if ( $inc_tax ) {
  1335. $total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
  1336. } else {
  1337. $total = floatval( $item->get_total() ) / $item->get_quantity();
  1338. }
  1339. $total = $round ? round( $total, wc_get_price_decimals() ) : $total;
  1340. }
  1341. return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
  1342. }
  1343. /**
  1344. * Calculate line total - useful for gateways.
  1345. *
  1346. * @param object $item Item to get total from.
  1347. * @param bool $inc_tax (default: false).
  1348. * @param bool $round (default: true).
  1349. * @return float
  1350. */
  1351. public function get_line_total( $item, $inc_tax = false, $round = true ) {
  1352. $total = 0;
  1353. if ( is_callable( array( $item, 'get_total' ) ) ) {
  1354. // Check if we need to add line tax to the line total.
  1355. $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
  1356. // Check if we need to round.
  1357. $total = $round ? round( $total, wc_get_price_decimals() ) : $total;
  1358. }
  1359. return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
  1360. }
  1361. /**
  1362. * Get item tax - useful for gateways.
  1363. *
  1364. * @param mixed $item Item to get total from.
  1365. * @param bool $round (default: true).
  1366. * @return float
  1367. */
  1368. public function get_item_tax( $item, $round = true ) {
  1369. $tax = 0;
  1370. if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
  1371. $tax = $item->get_total_tax() / $item->get_quantity();
  1372. $tax = $round ? wc_round_tax_total( $tax ) : $tax;
  1373. }
  1374. return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
  1375. }
  1376. /**
  1377. * Get line tax - useful for gateways.
  1378. *
  1379. * @param mixed $item Item to get total from.
  1380. * @return float
  1381. */
  1382. public function get_line_tax( $item ) {
  1383. return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this );
  1384. }
  1385. /**
  1386. * Gets line subtotal - formatted for display.
  1387. *
  1388. * @param array $item Item to get total from.
  1389. * @param string $tax_display Incl or excl tax display mode.
  1390. * @return string
  1391. */
  1392. public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
  1393. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1394. if ( 'excl' === $tax_display ) {
  1395. $ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
  1396. $subtotal = wc_price( $this->get_line_subtotal( $item ), array(
  1397. 'ex_tax_label' => $ex_tax_label,
  1398. 'currency' => $this->get_currency(),
  1399. ) );
  1400. } else {
  1401. $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
  1402. }
  1403. return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
  1404. }
  1405. /**
  1406. * Gets order total - formatted for display.
  1407. *
  1408. * @return string
  1409. */
  1410. public function get_formatted_order_total() {
  1411. $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
  1412. return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
  1413. }
  1414. /**
  1415. * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
  1416. *
  1417. * @param bool $compound (default: false).
  1418. * @param string $tax_display (default: the tax_display_cart value).
  1419. * @return string
  1420. */
  1421. public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
  1422. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1423. $subtotal = 0;
  1424. if ( ! $compound ) {
  1425. foreach ( $this->get_items() as $item ) {
  1426. $subtotal += $item->get_subtotal();
  1427. if ( 'incl' === $tax_display ) {
  1428. $subtotal += $item->get_subtotal_tax();
  1429. }
  1430. }
  1431. $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
  1432. if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
  1433. $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
  1434. }
  1435. } else {
  1436. if ( 'incl' === $tax_display ) {
  1437. return '';
  1438. }
  1439. foreach ( $this->get_items() as $item ) {
  1440. $subtotal += $item->get_subtotal();
  1441. }
  1442. // Add Shipping Costs.
  1443. $subtotal += $this->get_shipping_total();
  1444. // Remove non-compound taxes.
  1445. foreach ( $this->get_taxes() as $tax ) {
  1446. if ( $tax->is_compound() ) {
  1447. continue;
  1448. }
  1449. $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
  1450. }
  1451. // Remove discounts.
  1452. $subtotal = $subtotal - $this->get_total_discount();
  1453. $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
  1454. }
  1455. return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
  1456. }
  1457. /**
  1458. * Gets shipping (formatted).
  1459. *
  1460. * @param string $tax_display Excl or incl tax display mode.
  1461. * @return string
  1462. */
  1463. public function get_shipping_to_display( $tax_display = '' ) {
  1464. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1465. if ( 0 < (float) $this->get_shipping_total() ) {
  1466. if ( 'excl' === $tax_display ) {
  1467. // Show shipping excluding tax.
  1468. $shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
  1469. if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
  1470. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
  1471. }
  1472. } else {
  1473. // Show shipping including tax.
  1474. $shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
  1475. if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
  1476. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
  1477. }
  1478. }
  1479. /* translators: %s: method */
  1480. $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this );
  1481. } elseif ( $this->get_shipping_method() ) {
  1482. $shipping = $this->get_shipping_method();
  1483. } else {
  1484. $shipping = __( 'Free!', 'woocommerce' );
  1485. }
  1486. return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
  1487. }
  1488. /**
  1489. * Get the discount amount (formatted).
  1490. *
  1491. * @since 2.3.0
  1492. * @param string $tax_display Excl or incl tax display mode.
  1493. * @return string
  1494. */
  1495. public function get_discount_to_display( $tax_display = '' ) {
  1496. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1497. return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this );
  1498. }
  1499. /**
  1500. * Add total row for subtotal.
  1501. *
  1502. * @param array $total_rows Reference to total rows array.
  1503. * @param string $tax_display Excl or incl tax display mode.
  1504. */
  1505. protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
  1506. $subtotal = $this->get_subtotal_to_display( false, $tax_display );
  1507. if ( $subtotal ) {
  1508. $total_rows['cart_subtotal'] = array(
  1509. 'label' => __( 'Subtotal:', 'woocommerce' ),
  1510. 'value' => $subtotal,
  1511. );
  1512. }
  1513. }
  1514. /**
  1515. * Add total row for discounts.
  1516. *
  1517. * @param array $total_rows Reference to total rows array.
  1518. * @param string $tax_display Excl or incl tax display mode.
  1519. */
  1520. protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
  1521. if ( $this->get_total_discount() > 0 ) {
  1522. $total_rows['discount'] = array(
  1523. 'label' => __( 'Discount:', 'woocommerce' ),
  1524. 'value' => '-' . $this->get_discount_to_display( $tax_display ),
  1525. );
  1526. }
  1527. }
  1528. /**
  1529. * Add total row for shipping.
  1530. *
  1531. * @param array $total_rows Reference to total rows array.
  1532. * @param string $tax_display Excl or incl tax display mode.
  1533. */
  1534. protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
  1535. if ( $this->get_shipping_method() ) {
  1536. $total_rows['shipping'] = array(
  1537. 'label' => __( 'Shipping:', 'woocommerce' ),
  1538. 'value' => $this->get_shipping_to_display( $tax_display ),
  1539. );
  1540. }
  1541. }
  1542. /**
  1543. * Add total row for fees.
  1544. *
  1545. * @param array $total_rows Reference to total rows array.
  1546. * @param string $tax_display Excl or incl tax display mode.
  1547. */
  1548. protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
  1549. $fees = $this->get_fees();
  1550. if ( $fees ) {
  1551. foreach ( $fees as $id => $fee ) {
  1552. if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
  1553. continue;
  1554. }
  1555. $total_rows[ 'fee_' . $fee->get_id() ] = array(
  1556. 'label' => $fee->get_name() . ':',
  1557. 'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
  1558. );
  1559. }
  1560. }
  1561. }
  1562. /**
  1563. * Add total row for taxes.
  1564. *
  1565. * @param array $total_rows Reference to total rows array.
  1566. * @param string $tax_display Excl or incl tax display mode.
  1567. */
  1568. protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) {
  1569. // Tax for tax exclusive prices.
  1570. if ( 'excl' === $tax_display ) {
  1571. if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
  1572. foreach ( $this->get_tax_totals() as $code => $tax ) {
  1573. $total_rows[ sanitize_title( $code ) ] = array(
  1574. 'label' => $tax->label . ':',
  1575. 'value' => $tax->formatted_amount,
  1576. );
  1577. }
  1578. } else {
  1579. $total_rows['tax'] = array(
  1580. 'label' => WC()->countries->tax_or_vat() . ':',
  1581. 'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
  1582. );
  1583. }
  1584. }
  1585. }
  1586. /**
  1587. * Add total row for grand total.
  1588. *
  1589. * @param array $total_rows Reference to total rows array.
  1590. * @param string $tax_display Excl or incl tax display mode.
  1591. */
  1592. protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
  1593. $total_rows['order_total'] = array(
  1594. 'label' => __( 'Total:', 'woocommerce' ),
  1595. 'value' => $this->get_formatted_order_total( $tax_display ),
  1596. );
  1597. }
  1598. /**
  1599. * Get totals for display on pages and in emails.
  1600. *
  1601. * @param mixed $tax_display Excl or incl tax display mode.
  1602. * @return array
  1603. */
  1604. public function get_order_item_totals( $tax_display = '' ) {
  1605. $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
  1606. $total_rows = array();
  1607. $this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
  1608. $this->add_order_item_totals_discount_row( $total_rows, $tax_display );
  1609. $this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
  1610. $this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
  1611. $this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
  1612. $this->add_order_item_totals_total_row( $total_rows, $tax_display );
  1613. return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
  1614. }
  1615. /*
  1616. |--------------------------------------------------------------------------
  1617. | Conditionals
  1618. |--------------------------------------------------------------------------
  1619. |
  1620. | Checks if a condition is true or false.
  1621. |
  1622. */
  1623. /**
  1624. * Checks the order status against a passed in status.
  1625. *
  1626. * @param array|string $status Status to check.
  1627. * @return bool
  1628. */
  1629. public function has_status( $status ) {
  1630. return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
  1631. }
  1632. /**
  1633. * Check whether this order has a specific shipping method or not.
  1634. *
  1635. * @param string $method_id Method ID to check.
  1636. * @return bool
  1637. */
  1638. public function has_shipping_method( $method_id ) {
  1639. foreach ( $this->get_shipping_methods() as $shipping_method ) {
  1640. if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
  1641. return true;
  1642. }
  1643. }
  1644. return false;
  1645. }
  1646. /**
  1647. * Returns true if the order contains a free product.
  1648. *
  1649. * @since 2.5.0
  1650. * @return bool
  1651. */
  1652. public function has_free_item() {
  1653. foreach ( $this->get_items() as $item ) {
  1654. if ( ! $item->get_total() ) {
  1655. return true;
  1656. }
  1657. }
  1658. return false;
  1659. }
  1660. }