context = adp_context(); $this->listener = new Listener(); if($this->context->isDebug()) { $this->listener->subscribe(new ProductCalculatorListener()); } $calc = $contextOrCalc instanceof ICartCalculator ? $contextOrCalc : $deprecated; if ($calc instanceof ICartCalculator) { $this->calc = $calc; } else { $this->calc = Factory::callStaticMethod("Core_CartCalculator", 'make', $this->listener); /** @see CartCalculator::make() */ } $this->cartItemConverter = new CartItemConverter(); } public function withContext(Context $context) { $this->context = $context; $this->listener->withContext($context); } /** * @param Cart $cart */ public function withCart(Cart $cart) { $items = $cart->getItems(); foreach ($items as $index => $cartItem) { $items[$index] = clone $cartItem; } $cart->setItems($items); $this->cart = $cart; } protected function isCartExists(): bool { return isset($this->cart); } /** * @param WC_Product|int $theProduct * @param float $qty * @param array $cartItemData * * @return ProcessedProductSimple|ProcessedVariableProduct|ProcessedGroupedProduct|ProcessedProductContainer|null */ public function calculateProduct($theProduct, $qty = 1.0, $cartItemData = array()) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- read-only check of which WP-Admin inline-edit action is running, no data is processed if (is_admin() && wp_doing_ajax() && isset($_POST['action']) && sanitize_text_field(wp_unslash($_POST['action'])) === 'inline-save') { return null; } static $last_product_key=''; static $last_product_result; $must_clone = false; if (is_numeric($theProduct)) { $product = CacheHelper::getWcProduct($theProduct); } elseif ($theProduct instanceof WC_Product) { $product = $theProduct; $must_clone = true; } else { $this->context->handleError(new Exception("Product does not exists", self::ERR_PRODUCT_DOES_NOT_EXISTS)); return null; } //try cache // $key = array( $product->get_id() , $qty, json_encode($cartItemData) ); // foreach ($this->cart->getItems() as $item) { // $key[] = "T".$item->getHash(); // } // foreach ($this->calc->getRulesCollection()->getRules() as $rule) { // $key[] = "R".$rule->getId(); // } // foreach ($this->cart->getContext()->getCustomer()->getRoles() as $role) { // $key[] = "U".$role; // } // $key = apply_filters("adp_calculate_product_hash", $key); // $key = md5(implode('_', $key)); $key = CacheHelper::calcHashProcessedProduct($product, [], $qty, $cartItemData, $this->cart, $this->calc); if( $key == $last_product_key ) return $last_product_result; // clone object only for real calculations if($must_clone) $product = clone $theProduct; $last_product_key = $key; $last_product_result = $this->calculateWithProductWrapper( new WcProductCalculationWrapper($product, $cartItemData, []), $qty ); return $last_product_result; } /** * @param WcProductCalculationWrapper $wrapper * @param float $qty * * @return ProcessedGroupedProduct|ProcessedProductSimple|ProcessedVariableProduct|null */ public function calculateWithProductWrapper(WcProductCalculationWrapper $wrapper, float $qty = 1.0) { $product = $wrapper->getWcProduct(); $cartItemData = $wrapper->getCartItemData(); if ($product instanceof WC_Product_Grouped) { /** @var $processed ProcessedGroupedProduct */ $processed = Factory::get("PriceDisplay_ProcessedGroupedProduct", $product, $qty); $children = array_filter( array_map('wc_get_product', $product->get_children()), 'wc_products_array_filter_visible_grouped' ); foreach ($children as $childId) { $processedChild = $this->checkCacheForProcessedProduct($childId, $qty, [], $cartItemData); if (is_null($processedChild)) { $processedChild = $this->calculateSimpleProductWrapper( new WcProductCalculationWrapper( WcProductProcessorHelper::buildWcProductFromChildId($childId, $product), $cartItemData, $wrapper->getAddons() ), $qty ); } if (is_null($processedChild)) { continue; } $processed->useChild($processedChild); } } elseif ($product instanceof WC_Product_Variable) { /** @var $processed ProcessedVariableProduct */ $processed = Factory::get("PriceDisplay_ProcessedVariableProduct", $this->context, $product, $qty); $children = $product->get_visible_children(); // try optimize calculations at Shop/Category/Tag pages only $context = $this->context; $req_variations = $this->context->getOption('req_variations_for_optimization_at_shop'); if( $req_variations AND count($children) >= $req_variations AND !is_product() AND ( is_shop() OR is_product_category() OR is_product_tag() OR $context->is($context::PRODUCT_LOOP) ) ) { $children = $this->getMinMaxCostChilds($children,$product); } foreach ($children as $childId) { $processedChild = $this->checkCacheForProcessedProduct($childId, $qty, [], $cartItemData); if (is_null($processedChild)) { $processedChild = $this->calculateSimpleProductWrapper( new WcProductCalculationWrapper( WcProductProcessorHelper::buildWcProductFromChildId($childId, $product), $cartItemData, $wrapper->getAddons() ), $qty ); } if (is_null($processedChild)) { continue; } $processed->useChild($processedChild); } } elseif ( WcProductProcessorHelper::isCalculatingPartOfContainerProduct($product) ) { $containerProduct = WcProductProcessorHelper::getBundleProductFromBundled($product); $processedParent = $this->checkCacheForProcessedProduct($containerProduct, $qty, [], $cartItemData); if ( is_null($processedParent) ) { $processedParent = $this->calculateSimpleProductWrapper( new WcProductCalculationWrapper($containerProduct, $cartItemData, $wrapper->getAddons()), $qty ); } $processed = null; if ($processedParent instanceof ProcessedProductContainer) { foreach ($processedParent->getContainerItemsByPos() as $containerItem) { if ($containerItem->getProduct()->get_id() === $product->get_id()) { $processed = $containerItem; } } } } else { $processed = $this->checkCacheForProcessedProduct($product, $qty, [], $cartItemData); if ( is_null($processed) ) { $processed = $this->calculateSimpleProductWrapper( new WcProductCalculationWrapper($product, $cartItemData, $wrapper->getAddons()), $qty ); } } return $processed; } protected function getMinMaxCostChilds($variations,$product){ $price_mode = $this->context->getOption('discount_for_onsale'); $maxDiscountRate = $maxDiscountAmount = 0; if($price_mode == "compare_discounted_and_sale") $this->calc->findPossibleMaxDiscountsForProducts($maxDiscountRate,$maxDiscountAmount); $result = []; $min_price = $min_id = $max_price = $max_id = null; foreach($variations as $variation_id) { $variation = WcProductProcessorHelper::buildWcProductFromChildId($variation_id, $product); $sale_price = floatval($variation->get_sale_price('edit')); $regular_price = floatval($variation->get_regular_price('edit')); if( $price_mode =='discount_regular') $price = $regular_price; elseif( $price_mode =='sale_price' OR $price_mode =='discount_sale') $price = $sale_price ? $sale_price : $regular_price; elseif( $price_mode =='compare_discounted_and_sale'){//TODO incorrect? $price = $regular_price; if($sale_price AND $sale_price != $regular_price) { $discountAmount = $regular_price - $sale_price; $discountRate = 100 - round ($sale_price/$regular_price * 100); if($discountRate>$maxDiscountRate OR $discountAmount>$maxDiscountAmount ) $price = $sale_price; //echo "%% $discountRate VS $maxDiscountRate , USD $discountAmount VS $maxDiscountAmount
\n"; } } if( !isset($min_cost) OR $price<$min_cost ) { $min_id = $variation_id; $min_cost = $price; } if( !isset($max_cost) OR $price>$max_cost ) { $max_id = $variation_id; $max_cost = $price; } } if($min_id) $result[] = $min_id; if($max_id AND $max_id!=$min_id) $result[] = $max_id; return $result; } protected function checkCacheForProcessedProduct($prodID, $qty, $variationAttributes, $cartItemData) { if ($prodID && $processedProduct = CacheHelper::maybeGetProcessedProductToDisplay( $prodID, $variationAttributes, $qty, $cartItemData, $this->cart, $this->calc )) { return $processedProduct; } else { return null; } } /** * @param WcProductCalculationWrapper $wrapper * @param float $qty * * @return ProcessedProductSimple|ProcessedProductContainer|null */ protected function calculateSimpleProductWrapper( WcProductCalculationWrapper $wrapper, float $qty = 1.0 ): ?ProcessedProductSimple { if (!$this->isCartExists()) { $this->context->handleError(new Exception("Cart does not exists", self::ERR_CART_DOES_NOT_EXISTS)); return null; } $product = $wrapper->getWcProduct(); $cartItemData = $wrapper->getCartItemData(); $prodID = $product->get_id(); $variationAttributes = $product instanceof \WC_Product_Variation ? $product->get_variation_attributes() : []; if ($prodID && $processedProduct = CacheHelper::maybeGetProcessedProductToDisplay( $prodID, $variationAttributes, $qty, $cartItemData, $this->cart, $this->calc )) { return $processedProduct; } $productExt = new ProductExtension($this->context, $product); $productExt->withContext($this->context); if ($product->get_price('edit') === '') { $this->context->handleError(new Exception("Empty price", self::ERR_PRODUCT_WITH_NO_PRICE)); return null; } $cartItemData = apply_filters('adp_calculate_product_price_data', $cartItemData, $product, $this->context); if ($productExt->getCustomPrice() === null) { $productExt->setCustomPrice( apply_filters("adp_product_get_price", null, $product, $variationAttributes, 1, array(), null) ); } $currencySwitcher = $this->context->currencyController; /** * Why do we use '==' instead of '==='? * @see \ADP\BaseVersion\Includes\CurrencyController::isCurrencyChanged() */ if ($this->cart->getCurrency() == $currencySwitcher->getDefaultCurrency()) { if ($productExt->getCustomPrice() !== null) { $product->set_price($productExt->getCustomPrice()); } else { $product->set_price($currencySwitcher->getDefaultCurrencyProductPrice($product)); } $salePrice = $currencySwitcher->getDefaultCurrencyProductSalePrice($product); if ($salePrice !== null) { $product->set_sale_price($salePrice); } $product->set_regular_price($currencySwitcher->getDefaultCurrencyProductRegularPrice($product)); } elseif ($this->cart->getCurrency() == $currencySwitcher->getCurrentCurrency()) { if ($productExt->getCustomPrice() !== null) { $product->set_price( $currencySwitcher->getCurrentCurrencyProductPriceWithCustomPrice( $product, $productExt->getCustomPrice() ) ); } else { $product->set_price($currencySwitcher->getCurrentCurrencyProductPrice($product)); } $salePrice = $currencySwitcher->getCurrentCurrencyProductSalePrice($product); if ($salePrice !== null) { $product->set_sale_price($salePrice); } $product->set_regular_price($currencySwitcher->getCurrentCurrencyProductRegularPrice($product)); $product->add_meta_data('adp_price_converted', true); } $cart = clone $this->cart; foreach ($cart->getItems() as $existingItem) { $existingItem->cleanAllAdjustments(); } $item = (new ToPricingCartItemAdapter())->adaptWcProduct($wrapper); $item->setQty($qty); $item->addAttr(CartItemAttributeEnum::TEMPORARY()); $item->setAddons($wrapper->getAddons()); $cart->addToCart($item); $this->listener->startCartProcessProduct($product); $this->calc->processItem($cart, $item); $this->listener->finishCartProcessProduct($product); $tmpItems = array(); $qtyAlreadyInCart = floatval(0); foreach ($cart->getItems() as $loopCartItem) { if ($loopCartItem->getWcItem()->getKey() === $item->getWcItem()->getKey()) { if ($loopCartItem->hasAttr(CartItemAttributeEnum::TEMPORARY())) { $tmpItems[] = $loopCartItem; } } if ($loopCartItem->getWcItem()->getProduct()->get_id() === $item->getWcItem()->getProduct()->get_id()) { $qtyAlreadyInCart += $loopCartItem->getQty(); } } $tmpFreeItems = array(); foreach ($cart->getFreeItems() as $loopCartItem) { if ($loopCartItem->hasAttr($loopCartItem::ATTR_TEMP)) { $tmpFreeItems[] = $loopCartItem; } } $tmpListOfFreeCartItemChoices = array(); foreach ($cart->getListOfFreeCartItemChoices() as $freeCartItemChoices) { if ($freeCartItemChoices->hasAttr($freeCartItemChoices::ATTR_TEMP)) { $tmpListOfFreeCartItemChoices[] = $freeCartItemChoices; } } $qtyAlreadyInCart = $qtyAlreadyInCart - array_sum(array_map(function ($item) { return $item->getQty(); }, $tmpItems)); if (count($tmpItems) === 0) { $this->context->handleError(new Exception("Temporary item is missing", self::ERR_TMP_ITEM_MISSING)); return null; } $tmpItems = apply_filters("adp_before_processed_product", $tmpItems, $this); $processedProduct = WcProductProcessorHelper::tmpItemsToProcessedProduct( $this->context, $product, $tmpItems, $tmpFreeItems, $tmpListOfFreeCartItemChoices ); $processedProduct->setQtyAlreadyInCart($qtyAlreadyInCart); CacheHelper::addProcessedProductToDisplay($item->getWcItem(), $qty, $processedProduct, $this->cart, $this->calc); if( $this->context->getOption("show_debug_bar") ) $this->listener->processedProduct($processedProduct); return $processedProduct; } /** * @return Listener */ public function getListener(): Listener { return $this->listener; } /** * @return Cart */ public function getCart(): Cart { return $this->cart; } }