PageRenderTime 63ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/woocommerce-payments/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php

https://gitlab.com/remyvianne/krowkaramel
PHP | 378 lines | 198 code | 49 blank | 131 comment | 54 complexity | 7b97bc5f765728a03412b840ad84c832 MD5 | raw file
  1. <?php
  2. /**
  3. * Class WooCommerceSubscriptions
  4. *
  5. * @package WCPay\MultiCurrency\Compatibility
  6. */
  7. namespace WCPay\MultiCurrency\Compatibility;
  8. use WC_Payments_Features;
  9. use WCPay\MultiCurrency\MultiCurrency;
  10. use WCPay\MultiCurrency\Utils;
  11. /**
  12. * Class that controls Multi Currency Compatibility with WooCommerce Subscriptions Plugin and WCPay Subscriptions.
  13. */
  14. class WooCommerceSubscriptions extends BaseCompatibility {
  15. /**
  16. * Subscription switch cart item.
  17. *
  18. * @var string
  19. */
  20. public $switch_cart_item = '';
  21. /**
  22. * Init the class.
  23. *
  24. * @return void
  25. */
  26. protected function init() {
  27. // Add needed actions and filters if WC Subscriptions or WCPay Subscriptions are active.
  28. if ( class_exists( 'WC_Subscriptions' ) || WC_Payments_Features::is_wcpay_subscriptions_enabled() ) {
  29. if ( ! is_admin() && ! defined( 'DOING_CRON' ) ) {
  30. add_filter( 'woocommerce_subscriptions_product_price', [ $this, 'get_subscription_product_price' ], 50, 2 );
  31. add_filter( 'woocommerce_product_get__subscription_sign_up_fee', [ $this, 'get_subscription_product_signup_fee' ], 50, 2 );
  32. add_filter( 'woocommerce_product_variation_get__subscription_sign_up_fee', [ $this, 'get_subscription_product_signup_fee' ], 50, 2 );
  33. add_filter( 'option_woocommerce_subscriptions_multiple_purchase', [ $this, 'maybe_disable_mixed_cart' ], 50 );
  34. add_filter( MultiCurrency::FILTER_PREFIX . 'override_selected_currency', [ $this, 'override_selected_currency' ], 50 );
  35. add_filter( MultiCurrency::FILTER_PREFIX . 'should_convert_product_price', [ $this, 'should_convert_product_price' ], 50, 2 );
  36. add_filter( MultiCurrency::FILTER_PREFIX . 'should_convert_coupon_amount', [ $this, 'should_convert_coupon_amount' ], 50, 2 );
  37. add_filter( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets', [ $this, 'should_hide_widgets' ], 50 );
  38. }
  39. }
  40. }
  41. /**
  42. * Converts subscription prices, if needed.
  43. *
  44. * @param mixed $price The price to be filtered.
  45. * @param object $product The product that will have a filtered price.
  46. *
  47. * @return mixed The price as a string or float.
  48. */
  49. public function get_subscription_product_price( $price, $product ) {
  50. if ( ! $price || ! $this->should_convert_product_price( true, $product ) ) {
  51. return $price;
  52. }
  53. return $this->multi_currency->get_price( $price, 'product' );
  54. }
  55. /**
  56. * Converts subscription sign up prices, if needed.
  57. *
  58. * @param mixed $price The price to be filtered.
  59. * @param object $product The product that will have a filtered price.
  60. *
  61. * @return mixed The price as a string or float.
  62. */
  63. public function get_subscription_product_signup_fee( $price, $product ) {
  64. if ( ! $price ) {
  65. return $price;
  66. }
  67. $switch_cart_items = $this->get_subscription_switch_cart_items();
  68. if ( 0 < count( $switch_cart_items ) ) {
  69. // There should only ever be one item, so use that item.
  70. $item = array_shift( $switch_cart_items );
  71. $item_id = isset( $item['variation_id'] ) ? $item['variation_id'] : $item['product_id'];
  72. $switch_cart_item = $this->switch_cart_item;
  73. $this->switch_cart_item = $item['key'];
  74. if ( $product->get_id() === $item_id ) {
  75. /**
  76. * These tests get mildly complex due to, when switching, the sign up fee is queried
  77. * several times to determine prorated costs. This means we have to test to see when
  78. * the fee actually needs be converted.
  79. */
  80. if ( $this->utils->is_call_in_backtrace( [ 'WC_Subscriptions_Cart::set_subscription_prices_for_calculation' ] ) ) {
  81. return $price;
  82. }
  83. // Check to see if it's currently determining prorated prices.
  84. if ( $this->utils->is_call_in_backtrace( [ 'WC_Subscriptions_Product::get_sign_up_fee' ] )
  85. && $this->utils->is_call_in_backtrace( [ 'WC_Cart->calculate_totals' ] )
  86. && $item['key'] === $switch_cart_item
  87. && ! $this->utils->is_call_in_backtrace( [ 'WCS_Switch_Totals_Calculator->apportion_sign_up_fees' ] ) ) {
  88. return $price;
  89. }
  90. // Check to see if the _subscription_sign_up_fee meta for the product has already been updated.
  91. if ( $item['key'] === $switch_cart_item ) {
  92. foreach ( $product->get_meta_data() as $meta ) {
  93. if ( '_subscription_sign_up_fee' === $meta->get_data()['key'] && 0 < count( $meta->get_changes() ) ) {
  94. return $price;
  95. }
  96. }
  97. }
  98. }
  99. }
  100. return $this->multi_currency->get_price( $price, 'product' );
  101. }
  102. /**
  103. * Disables the mixed cart if needed.
  104. *
  105. * @param string|bool $value Option from the database, or false.
  106. *
  107. * @return mixed False, yes, or no.
  108. */
  109. public function maybe_disable_mixed_cart( $value ) {
  110. // If there's a subscription switch in the cart, disable multiple items in the cart.
  111. // This is so that subscriptions with different currencies cannot be added to the cart.
  112. if ( 0 < count( $this->get_subscription_switch_cart_items() ) ) {
  113. return 'no';
  114. }
  115. return $value;
  116. }
  117. /**
  118. * Checks to see if the if the selected currency needs to be overridden.
  119. *
  120. * @param mixed $return Default is false, but could be three letter currency code.
  121. *
  122. * @return mixed Three letter currency code or false if not.
  123. */
  124. public function override_selected_currency( $return ) {
  125. // If it's not false, return it.
  126. if ( $return ) {
  127. return $return;
  128. }
  129. $subscription_renewal = $this->cart_contains_renewal();
  130. if ( $subscription_renewal ) {
  131. return get_post_meta( $subscription_renewal['subscription_renewal']['renewal_order_id'], '_order_currency', true );
  132. }
  133. $switch_id = $this->get_subscription_switch_id_from_superglobal();
  134. if ( $switch_id ) {
  135. return get_post_meta( $switch_id, '_order_currency', true );
  136. }
  137. $switch_cart_items = $this->get_subscription_switch_cart_items();
  138. if ( 0 < count( $switch_cart_items ) ) {
  139. $switch_cart_item = array_shift( $switch_cart_items );
  140. return get_post_meta( $switch_cart_item['subscription_switch']['subscription_id'], '_order_currency', true );
  141. }
  142. $subscription_resubscribe = $this->cart_contains_resubscribe();
  143. if ( $subscription_resubscribe ) {
  144. return get_post_meta( $subscription_resubscribe['subscription_resubscribe']['subscription_id'], '_order_currency', true );
  145. }
  146. return $return;
  147. }
  148. /**
  149. * Checks to see if the product's price should be converted.
  150. *
  151. * @param bool $return Whether to convert the product's price or not. Default is true.
  152. * @param object $product Product object to test.
  153. *
  154. * @return bool True if it should be converted.
  155. */
  156. public function should_convert_product_price( bool $return, $product ): bool {
  157. // If it's already false, return it.
  158. if ( ! $return ) {
  159. return $return;
  160. }
  161. // Check for subscription renewal or resubscribe.
  162. if ( $this->is_product_subscription_type_in_cart( $product, 'renewal' )
  163. || $this->is_product_subscription_type_in_cart( $product, 'resubscribe' ) ) {
  164. $calls = [
  165. 'WC_Cart_Totals->calculate_item_totals',
  166. 'WC_Cart->get_product_subtotal',
  167. 'wc_get_price_excluding_tax',
  168. 'wc_get_price_including_tax',
  169. ];
  170. if ( $this->utils->is_call_in_backtrace( $calls ) ) {
  171. return false;
  172. }
  173. }
  174. // WCPay Subs does a check against the product price and the total, we need to return the actual product price for this check.
  175. if ( $this->utils->is_call_in_backtrace( [ 'WC_Payments_Subscription_Service->get_recurring_item_data_for_subscription' ] )
  176. && $this->utils->is_call_in_backtrace( [ 'WC_Product->get_price' ] ) ) {
  177. return false;
  178. }
  179. return $return;
  180. }
  181. /**
  182. * Checks to see if the coupon's amount should be converted.
  183. *
  184. * @param bool $return Whether to convert the coupon's price or not. Default is true.
  185. * @param object $coupon Coupon object to test.
  186. *
  187. * @return bool True if it should be converted.
  188. */
  189. public function should_convert_coupon_amount( bool $return, $coupon ): bool {
  190. // If it's already false, return it.
  191. if ( ! $return ) {
  192. return $return;
  193. }
  194. // We do not need to convert percentage coupons.
  195. if ( $this->is_coupon_type( $coupon, 'subscription_percent' ) ) {
  196. return false;
  197. }
  198. // If there's not a renewal in the cart, we can convert.
  199. $subscription_renewal = $this->cart_contains_renewal();
  200. if ( ! $subscription_renewal ) {
  201. return true;
  202. }
  203. /**
  204. * We need to allow the early renewal to convert the cost, as it pulls the original value of the coupon.
  205. * Subsequent queries for the amount use the first converted amount.
  206. * This also works for normal manual renewals.
  207. */
  208. if ( ! $this->utils->is_call_in_backtrace( [ 'WCS_Cart_Early_Renewal->setup_cart' ] )
  209. && $this->utils->is_call_in_backtrace( [ 'WC_Discounts->apply_coupon' ] )
  210. && $this->is_coupon_type( $coupon, 'subscription_recurring' ) ) {
  211. return false;
  212. }
  213. return $return;
  214. }
  215. /**
  216. * Checks to see if the widgets should be hidden.
  217. *
  218. * @param bool $return Whether widgets should be hidden or not. Default is false.
  219. *
  220. * @return bool
  221. */
  222. public function should_hide_widgets( bool $return ): bool {
  223. // If it's already true, return it.
  224. if ( $return ) {
  225. return $return;
  226. }
  227. if ( $this->cart_contains_renewal()
  228. || $this->get_subscription_switch_id_from_superglobal()
  229. || 0 < count( $this->get_subscription_switch_cart_items() )
  230. || $this->cart_contains_resubscribe() ) {
  231. return true;
  232. }
  233. return $return;
  234. }
  235. /**
  236. * Checks the cart to see if it contains a subscription product renewal.
  237. *
  238. * @return mixed The cart item containing the renewal as an array, else false.
  239. */
  240. private function cart_contains_renewal() {
  241. if ( ! function_exists( 'wcs_cart_contains_renewal' ) ) {
  242. return false;
  243. }
  244. return wcs_cart_contains_renewal();
  245. }
  246. /**
  247. * Gets the subscription switch items out of the cart.
  248. *
  249. * @return array Empty array or the cart items in an array..
  250. */
  251. private function get_subscription_switch_cart_items(): array {
  252. if ( ! function_exists( 'wcs_get_order_type_cart_items' ) ) {
  253. return [];
  254. }
  255. return wcs_get_order_type_cart_items( 'switch' );
  256. }
  257. /**
  258. * Checks $_GET superglobal for a switch id and returns it if found.
  259. *
  260. * @return mixed Id of the sub being switched, or false.
  261. */
  262. private function get_subscription_switch_id_from_superglobal() {
  263. if ( isset( $_GET['_wcsnonce'] ) && wp_verify_nonce( sanitize_key( $_GET['_wcsnonce'] ), 'wcs_switch_request' ) ) {
  264. if ( isset( $_GET['switch-subscription'] ) ) {
  265. return (int) $_GET['switch-subscription'];
  266. }
  267. }
  268. return false;
  269. }
  270. /**
  271. * Checks the cart to see if it contains a resubscription.
  272. *
  273. * @return mixed The cart item containing the resubscription as an array, else false.
  274. */
  275. private function cart_contains_resubscribe() {
  276. if ( ! function_exists( 'wcs_cart_contains_resubscribe' ) ) {
  277. return false;
  278. }
  279. return wcs_cart_contains_resubscribe();
  280. }
  281. /**
  282. * Checks to see if the product passed is in the cart as a subscription type.
  283. *
  284. * @param object $product Product to test.
  285. * @param string $type Type of subscription.
  286. *
  287. * @return bool True if found in the cart, false if not.
  288. */
  289. private function is_product_subscription_type_in_cart( $product, $type ): bool {
  290. $subscription = false;
  291. switch ( $type ) {
  292. case 'renewal':
  293. $subscription = $this->cart_contains_renewal();
  294. break;
  295. case 'resubscribe':
  296. $subscription = $this->cart_contains_resubscribe();
  297. break;
  298. }
  299. if ( $subscription && $product ) {
  300. if ( ( isset( $subscription['variation_id'] ) && $subscription['variation_id'] === $product->get_id() )
  301. || $subscription['product_id'] === $product->get_id() ) {
  302. return true;
  303. }
  304. }
  305. return false;
  306. }
  307. /**
  308. * Checks to see if the coupon passed is of a specified type.
  309. *
  310. * @param \WC_Coupon $coupon Coupon to test.
  311. * @param string $type Type of coupon to test for.
  312. *
  313. * @return bool True on match.
  314. */
  315. private function is_coupon_type( $coupon, string $type ) {
  316. switch ( $type ) {
  317. case 'subscription_percent':
  318. $types = [ 'recurring_percent', 'sign_up_fee_percent', 'renewal_percent' ];
  319. break;
  320. case 'subscription_recurring':
  321. $types = [ 'recurring_fee', 'recurring_percent', 'renewal_fee', 'renewal_percent', 'renewal_cart' ];
  322. break;
  323. }
  324. if ( in_array( $coupon->get_discount_type(), $types, true ) ) {
  325. return true;
  326. }
  327. return false;
  328. }
  329. }