*/ protected $disabledWcCoupons; /** * @var bool */ protected $disableAllInRuleWcCoupons; /** * @var CartContext */ protected $cartContext; protected $wcCouponExternalExcludeCodes = []; protected const CONFIG_TRY_TO_LOAD_WC_COUPONS_OPTION = 'try_to_load_wc_coupons_when_adapting_pricing_coupons'; protected $config = []; public function __construct($deprecated = null) { $this->context = adp_context(); $this->config = [ self::CONFIG_TRY_TO_LOAD_WC_COUPONS_OPTION => true ]; $this->purge(); } public function withContext(Context $context) { $this->context = $context; } public function disableAllWcCoupons(): bool { return boolval($this->disableAllInRuleWcCoupons); } public function installActions() { $this->setFilterToInstallCouponsData(); $this->setFilterToSuppressDisabledWcCoupons(); $this->setFiltersCoupon(); } public function setFilterToInstallCouponsData() { add_filter('woocommerce_get_shop_coupon_data', array($this, 'getCouponData'), 10, 3); } public function removeFilterToInstallCouponsData() { remove_filter('woocommerce_get_shop_coupon_data', array($this, 'getCouponData'), 10); } public function setFilterToSuppressDisabledWcCoupons() { add_filter('woocommerce_coupon_is_valid', array($this, 'hookDisabledCouponsIsValidForCart'), 10, 3); } public function setFiltersCoupon() { add_filter('woocommerce_cart_coupon_types', array($this, 'addCouponCartType'), 10, 1); add_filter('woocommerce_coupon_discount_types', array($this, 'addCouponDiscountType'), 10, 1); add_filter('woocommerce_coupon_custom_discounts_array', array($this, 'calculateCouponDiscountsArray'), 10, 2); } public function init() { } public function prepareConfig() { if ( $this->context->getOption('external_cart_coupons_behavior') === "best_between_coupon_and_rule" ) { $this->config[self::CONFIG_TRY_TO_LOAD_WC_COUPONS_OPTION] = false; } } public function sanitize(WC_Cart $wcCart) { // $this->purge(); // return; $appliedCoupons = $wcCart->applied_coupons; $adpCoupons = []; foreach ( InMemoryAdpMergedCouponStorage::getInstance()->getAllKeys() as $couponCode ) { $mergeCoupon = WcAdpMergedCouponHelper::loadOfCouponCode($couponCode); if ($mergeCoupon !== null && $mergeCoupon->hasAdpPart()) { $adpCoupons[] = $mergeCoupon->getCode(); } } foreach ($appliedCoupons as $index => $couponCode) { if (in_array($couponCode, $adpCoupons, true)) { unset($appliedCoupons[$index]); } } $wcCart->applied_coupons = array_values($appliedCoupons); $this->purge(); } public function updateTotals(WC_Cart $wcCart) { /** * Put non-merged coupons into 'merge' storage. * This allows to store and then fetch discounts per item for non-merged coupons. */ $wcDiscounts = new \WC_Discounts($wcCart); foreach ($wcCart->get_coupons() as $wcCoupon) { $wcDiscounts->apply_coupon($wcCoupon); } $reflection = new \ReflectionClass($wcDiscounts); $discountsProperty = $reflection->getProperty('discounts'); if (\PHP_VERSION_ID < 80100) { $discountsProperty->setAccessible(true); } $adpCoupons = []; foreach ($wcCart->get_coupons() as $wcCoupon) { /** @var $wcCoupon WC_Coupon */ $couponCode = $wcCoupon->get_code('edit'); $mergedCoupon = WcAdpMergedCouponHelper::loadOfCoupon($wcCoupon); if (!$mergedCoupon->hasAdpPart()) { $mergedCoupon->setParts( [ new ExternalWcCoupon( new WC_Coupon($couponCode), $discountsProperty->getValue($wcDiscounts)[$couponCode] ?? [] ) ] ); WcAdpMergedCouponHelper::store($mergedCoupon); } else { $adpCoupons[] = $couponCode; } } $this->cartContext->getSession()->insertCouponsData([], [], [], $adpCoupons); } public function applyCouponsToWcCart(Cart $cart, WC_Cart $wcCart) { $wcCart->applied_coupons = []; $this->purge(); $this->processCouponAdjustments($cart, $wcCart); $this->processOriginCoupons($cart, $wcCart); $this->processRuleTriggerCoupons($cart, $wcCart); $this->processCartCoupons($cart, $wcCart); $this->processIndividualUseCoupons($cart, $wcCart); $wcCart->applied_coupons = array_map('strval', array_keys($this->mergedCoupons)); } protected function processOriginCoupons(Cart $cart, WC_Cart $wcCart) { foreach ($cart->getOriginCoupons() as $couponCode) { $wcCoupon = $this->loadWcCouponByCode($couponCode); if ($this->isWcCouponValid($cart, $wcCart, $wcCoupon) && ! $this->disableAllWcCoupons()) { $this->addToMerged($couponCode, new WcCouponExternal($wcCoupon)); } else { $this->wcCouponExternalExcludeCodes[] = $couponCode; $this->notifyRemovedWcCoupon($couponCode); $this->replaceCouponNotices($couponCode); } } } protected function processRuleTriggerCoupons(Cart $cart, WC_Cart $wcCart) { $ruleIdByActivationCouponCode = []; foreach (CacheHelper::loadActiveRules($this->context)->getRules() as $activeRule) { if ($activeRule->getActivationCouponCode() !== null) { $ruleIdByActivationCouponCode[$activeRule->getActivationCouponCode()] = $activeRule->getId(); } } foreach ($cart->getRuleTriggerCoupons() as $couponCode) { if ($this->disableAllInRuleWcCoupons || in_array($couponCode, $this->disabledWcCoupons, true)) { continue; } $this->addToMerged( $couponCode, new CouponRuleTrigger($couponCode, $ruleIdByActivationCouponCode[$couponCode] ?? 0) ); $this->addExternalWcCouponWithSameCodeIfPossible($cart, $wcCart, $couponCode); } } public function processCouponAdjustments(Cart $cart, WC_Cart $wcCart) { $this->cartContext = $cart->getContext(); $this->disabledWcCoupons = []; $this->disableAllInRuleWcCoupons = false; foreach ($cart->getCouponsAdjustments() as $coupon) { if ($coupon instanceof DisableWcCouponsCart) { $this->disabledWcCoupons[] = $coupon->getCode(); } elseif ($coupon instanceof DisableAllWcCouponsCart) { $this->disableAllInRuleWcCoupons = true; } } } public function processCartCoupons(Cart $cart, WC_Cart $wcCart) { $context = $cart->getContext(); $this->cartContext = $cart->getContext(); foreach ($cart->getCoupons() as $coupon) { $coupon = clone $coupon; if (empty($coupon->getCode())) { continue; } if ($coupon instanceof CouponCart && $coupon->getValue()) { if ($coupon->isType($coupon::TYPE_FIXED_VALUE) && $context->isCombineMultipleDiscounts()) { $coupon->setCode(strtolower($context->getOption('default_discount_name'))); } $this->addToMerged($coupon->getCode(), $coupon); $this->addExternalWcCouponWithSameCodeIfPossible($cart, $wcCart, $coupon->getCode()); } elseif ($coupon instanceof CouponCartItem && $coupon->getValue()) { if ($context->isCombineMultipleDiscounts()) { $coupon->setCode(strtolower($context->getOption('default_discount_name'))); } $this->addToMerged($coupon->getCode(), $coupon); $this->addExternalWcCouponWithSameCodeIfPossible($cart, $wcCart, $coupon->getCode()); } elseif ($coupon instanceof WcCouponCart) { if (!$this->disableAllInRuleWcCoupons && !in_array($coupon->getCode(), $this->disabledWcCoupons, true) ) { $this->addToMerged($coupon->getCode(), $coupon); } } } } protected function processIndividualUseCoupons(Cart $cart, WC_Cart $wcCart) { $hasIndividual = false; $mergedCoupons = []; foreach ($this->mergedCoupons as $couponCode => $coupons) { foreach ($coupons as $coupon) { if ($coupon instanceof WcCouponCart) { $wcCoupon = $this->loadWcCouponByCode($coupon->getCode()); if ($wcCoupon->get_individual_use("edit")) { $mergedCoupons = [$couponCode => [$coupon]]; $hasIndividual = true; break 2; } } elseif ($coupon instanceof WcCouponExternal) { $wcCoupon = $coupon->getWcCoupon(); if ($wcCoupon->get_individual_use("edit")) { $mergedCoupons = [$couponCode => [$coupon]]; $hasIndividual = true; break 2; } } } } if($hasIndividual) { if(!$this->context->getOption('individual_wc_coupon_suppress_coupons')) { foreach ($this->mergedCoupons as $couponCode => $coupons) { foreach ($coupons as $coupon) { if ($coupon instanceof CouponCart) { $mergedCoupons[$couponCode] = array_merge($mergedCoupons[$couponCode] ?? [], [$coupon]); } } } } $this->mergedCoupons = $mergedCoupons; } } protected function addExternalWcCouponWithSameCodeIfPossible(Cart $cart, WC_Cart $wcCart, string $couponCode) { if ( $this->config[self::CONFIG_TRY_TO_LOAD_WC_COUPONS_OPTION] === false ) { return; } $wcCoupon = $this->loadWcCouponByCode($couponCode); if ($this->isWcCouponValid($cart, $wcCart, $wcCoupon)) { $this->addToMerged($couponCode, new WcCouponExternal($wcCoupon)); } } protected function addToMerged($code, $coupon) { if (!isset($this->mergedCoupons[$code])) { $this->mergedCoupons[$code] = []; } foreach ($this->mergedCoupons[$code] as $loopCoupon) { if ($coupon instanceof WcCouponExternal && $loopCoupon instanceof WcCouponExternal) { return; } if ($coupon instanceof CouponCartItem && $loopCoupon instanceof CouponCartItem && $coupon->equals($loopCoupon)) { $loopCoupon->setAffectedCartItemQty( $loopCoupon->getAffectedCartItemQty() + $coupon->getAffectedCartItemQty() ); return; } } $this->mergedCoupons[$code][] = $coupon; } protected function isWcCouponValid(Cart $cart, WC_Cart $wcCart, \WC_Coupon $wcCoupon): bool { return (new WcCouponCartProcessorFilterChain($wcCoupon)) ->filterIsValid($wcCart) ->filterByExternalCartCouponsBehaviorOption($cart) ->filterByExternalProductCouponsBehaviorOption($cart) ->getFilteredValue(); } /** * This filter allows custom coupon objects to be created on the fly. * * @param false $couponData * @param mixed $couponCode Coupon code * @param WC_Coupon $wcCoupon * * @return array|mixed */ public function getCouponData($couponData, $couponCode, $wcCoupon) { if (!is_string($couponCode)) { return $couponData; } if ($couponCode === "") { return $couponData; } $couponCode = wc_format_coupon_code($couponCode); $isFreeShipping = $this->hasAtLeastOneFreeShippingCouponInMergedCoupons($couponCode); $mergeCoupon = WcAdpMergedCouponHelper::loadOfCouponCode($couponCode); if ($mergeCoupon === null || $mergeCoupon->hasAdpPart() === false) { $currentCoupons = $this->mergedCoupons[$couponCode] ?? []; if (count($currentCoupons) === 0) { return $couponData; } $hasAdpPart = false; foreach ($currentCoupons as $currentCoupon) { if ($currentCoupon instanceof AdpCouponInterface) { $hasAdpPart = true; } } return $hasAdpPart ? [ 'discount_type' => WcAdpMergedCoupon::COUPON_DISCOUNT_TYPE, 'amount' => 0.0, 'free_shipping' => $isFreeShipping, 'virtual' => true, ] : $couponData; } return [ 'discount_type' => WcAdpMergedCoupon::COUPON_DISCOUNT_TYPE, 'amount' => 0.0, 'free_shipping' => $isFreeShipping, 'virtual' => true, ]; } protected function hasAtLeastOneFreeShippingCouponInMergedCoupons(string $couponCode): bool { $isFreeShipping = false; foreach ($this->mergedCoupons[$couponCode] ?? [] as $coupon) { if ($coupon instanceof WcCouponExternal && $coupon->getWcCoupon()->get_free_shipping("edit") === true ) { $isFreeShipping = true; } } return $isFreeShipping; } /** * @param array $discountTypes * * @return array */ public function addCouponDiscountType($discountTypes) { $discountTypes[WcAdpMergedCoupon::COUPON_DISCOUNT_TYPE] = __( 'WDP Coupon', 'advanced-dynamic-pricing-for-woocommerce' ); return $discountTypes; } /** * @param array $discountTypes * * @return array */ public function addCouponCartType($discountTypes) { $discountTypes[] = WcAdpMergedCoupon::COUPON_DISCOUNT_TYPE; return $discountTypes; } /** * * * @param array $couponDiscountsArray * @param \WC_Coupon $wcCoupon * * @return array */ public function calculateCouponDiscountsArray($couponDiscountsArray, $wcCoupon) { $couponCode = $wcCoupon->get_code('edit'); $couponsToMerge = $this->mergedCoupons[$couponCode] ?? []; if (count($couponsToMerge) === 0) { return $couponDiscountsArray; } $discountsCalc = new MergeDiscountsCalculator( $this, $couponDiscountsArray, WC()->cart ); foreach ($couponsToMerge as $coupon) { $discountsCalc->applyCoupon($coupon); } $resultAmounts = array_map(function ($coupon) { return $coupon->totalsPerItem(); }, $discountsCalc->getCoupons()); $newCouponDiscountsArray = []; foreach ($resultAmounts as $discountAmounts) { foreach ($discountAmounts as $cartItemKey => $amount) { if (!isset($newCouponDiscountsArray[$cartItemKey])) { $newCouponDiscountsArray[$cartItemKey] = 0.0; } $newCouponDiscountsArray[$cartItemKey] += $amount; } } foreach ($couponDiscountsArray as $cartItemKey => $amount) { if (!isset($newCouponDiscountsArray[$cartItemKey])) { $newCouponDiscountsArray[$cartItemKey] = 0.0; } } $mergeCoupon = new WcAdpMergedCoupon($couponCode); $mergeCoupon->setParts($discountsCalc->getCoupons()); WcAdpMergedCouponHelper::store($mergeCoupon); return $newCouponDiscountsArray; } /** * @param bool $valid * @param \WC_Coupon $wcCoupon * @param \WC_Discounts $wcDiscounts * * @return bool */ public function hookDisabledCouponsIsValidForCart($valid, $wcCoupon, $wcDiscounts) { $mergedCoupon = WcAdpMergedCouponHelper::loadOfCoupon($wcCoupon); if ($this->disableAllInRuleWcCoupons === true) { $currentCoupons = $this->mergedCoupons[$wcCoupon->get_code("edit")] ?? []; if ($mergedCoupon->hasOnlyInternalWcCouponPart() || count($currentCoupons) === 1 && $currentCoupons[0] instanceof WcCouponExternal ) { throw new \Exception( esc_html( WcUtils::getCouponNotApplicableMessage($wcCoupon->get_code()) ) ); } } if (in_array($wcCoupon->get_code(), $this->disabledWcCoupons, true)) { if (in_array($wcCoupon->get_code(), WC()->cart->get_applied_coupons(), true)) { WC()->cart->remove_coupon($wcCoupon->get_code()); $this->replaceDisabledCouponNotices($wcCoupon->get_code()); } throw new \Exception( esc_html( WcUtils::getCouponNotApplicableMessage($wcCoupon->get_code()) ) ); } return $valid; } protected function purge() { $this->mergedCoupons = []; $this->disabledWcCoupons = array(); $this->wcCouponExternalExcludeCodes = array(); $this->disableAllInRuleWcCoupons = false; InMemoryAdpMergedCouponStorage::getInstance()->purge(); } protected function loadWcCouponByCode($couponCode): WC_Coupon { $this->removeFilterToInstallCouponsData(); $wcCoupon = new WC_Coupon($couponCode); $this->setFilterToInstallCouponsData(); return $wcCoupon; } /** * Replace notice in case of removing coupons later * If coupons won't be removed, notice will be replaced back */ protected function replaceCouponNotices($couponCode) { WcUtils::replaceWcNotice( array( //phpcs:ignore WordPress.WP.I18n.TextDomainMismatch 'text' => __('Coupon code applied successfully.', 'woocommerce'), 'type' => 'success', ), array( 'text' => WcUtils::getWcCouponCantApplyMessage($couponCode), 'type' => 'error', ) ); } /** * * @param string $couponCode */ protected function notifyRemovedWcCoupon($couponCode) { if (!WC()->session) { return; } //phpcs:ignore WordPress.WP.I18n.TextDomainMismatch if (wc_has_notice(__('Coupon code applied successfully.', 'woocommerce'), 'success') || wc_has_notice(WcUtils::getWcCouponCantApplyMessage($couponCode), 'error') ) { return; } $message = WcUtils::getWcCouponRemovedMessage($couponCode); if (!wc_has_notice($message, 'error')) { wc_add_notice($message, 'error'); } } protected function replaceDisabledCouponNotices($couponCode) { WcUtils::replaceWcNotice( array( //phpcs:ignore WordPress.WP.I18n.TextDomainMismatch 'text' => __('Coupon code applied successfully.', 'woocommerce'), 'type' => 'success', ), array( 'text' => WcUtils::getCouponNotApplicableMessage($couponCode), 'type' => 'error', ) ); } public function checkDisabledCoupons(Cart $cart, WC_Cart $wcCart) { $appliedDisabledCoupons = array_intersect($this->disabledWcCoupons, $wcCart->get_applied_coupons()); if (count($appliedDisabledCoupons)) { foreach ($appliedDisabledCoupons as $couponCode) { $wcCart->remove_coupon($couponCode); $this->replaceDisabledCouponNotices($couponCode); wc_add_notice(WcUtils::getCouponNotApplicableMessage($couponCode), 'error'); } } } }