rule = $rule; $this->mutableItemsCollection = new CartItemsCollection($rule->getId()); $this->packages = array(); } public function registerCheckExecutionTimeFunction($callable, $context) { $this->checkExecutionTimeCallback = array( 'callable' => $callable, 'context' => $context, ); } private function checkExecutionTime() { if ( ! isset($this->checkExecutionTimeCallback['callable']) && $this->checkExecutionTimeCallback['context']) { return; } $callable = $this->checkExecutionTimeCallback['callable']; $context = $this->checkExecutionTimeCallback['context']; call_user_func($callable, $context); } /** * @param array $mutableItems */ public function addItems($mutableItems) { foreach ($mutableItems as $index => $cartItem) { $this->mutableItemsCollection->add($cartItem); } } /** * @param $cart Cart * * @throws Exception */ public function applyFilters($cart) { $packages = array(); // hashes with highest priority $typeProductsHashes = array(); $this->previousFilter = null; foreach ($this->rule->getPackages() as $package) { $packages[] = $this->preparePackage($cart, $package, $typeProductsHashes); } if (count($packages) === count($this->rule->getPackages())) { $this->packages = $packages; } foreach ($this->packages as &$filter) { $isProductFilter = $filter['is_product_filter']; unset($filter['is_product_filter']); /** Do not reorder 'exact products' filter hashes */ if ($isProductFilter) { continue; } foreach (array_reverse($typeProductsHashes) as $hash) { foreach ($filter['valid_hashes'] as $index => $validHash) { if ($hash === $validHash) { unset($filter['valid_hashes'][$index]); $filter['valid_hashes'][] = $hash; $filter['valid_hashes'] = array_values($filter['valid_hashes']); break; } } } } } /** * @param Cart $cart * @param PackageItem $package * @param array $typeProductsHashes * * @return array */ protected function preparePackage($cart, $package, &$typeProductsHashes) { $filters = $package->getFilters(); /** * @var $productFiltering ProductFiltering */ $productFiltering = Factory::get("Core_RuleProcessor_ProductFiltering", $cart->getContext()->getGlobalContext()); $limitation = $package->getLimitation(); $valid_hashes = array(); foreach ($this->mutableItemsCollection->get_items() as $cartItem) { /** * @var $cartItem BasicCartItem */ $wcCartItemFacade = $cartItem->getWcItem(); $product = $wcCartItemFacade->getProduct(); /** * Item must match all filters */ $match = true; foreach ($filters as $filter) { if ($filter->isExcludeWcOnSale() && $product->is_on_sale('')) { $match = false; break; } if ($filter->isExcludeAlreadyAffected() && $cartItem->areRuleApplied()) { $match = false; break; } foreach ($filter->getExcludeFilters() as $excludeFilter) { $productFiltering->prepare($excludeFilter->getType(), $excludeFilter->getValue(), $excludeFilter->getMethod()); if ($productFiltering->checkProductSuitability($product, $wcCartItemFacade->getData())) { $match = false; break 2; } } $productFiltering->prepare($filter->getType(), $filter->getValue(), $filter->getMethod()); if ( ! $productFiltering->checkProductSuitability($product, $wcCartItemFacade->getData())) { $match = false; break; } } if ($match) { $valid_hashes[] = $cartItem->getHash(); if ($productFiltering->isType('products')) { $typeProductsHashes[] = $cartItem->getHash(); } } } return array( 'valid_hashes' => $valid_hashes, 'is_product_filter' => $productFiltering->isType('products'), 'package' => $package, 'limitation' => $limitation, ); } /** * @param $cart Cart * @param $mode string * * @return CartSetCollection|false * @throws Exception */ public function collectSets(&$cart, $mode = 'legacy') { if ('legacy' === $mode) { $collection = $this->collectSetsLegacy($cart); } else { $collection = false; } return $collection; } /** * @param string $limitation * @param array $validHashes * @param Range $range * * @return array|null */ private function handleUniqueLimitations($limitation, $validHashes, $range, &$collectedQtyInCart = 0) { $packageSetItems = array(); /** @var ICartItem[] $validItemsGrouped */ $validItemsGrouped = []; foreach ($validHashes as $index => $validCartItemHash) { $cartItem = $this->mutableItemsCollection->getNotEmptyItemWithReferenceByHash($validCartItemHash); if ( ! $cartItem) { continue; } $product = $cartItem->getWcItem()->getProduct(); if ($limitation === PackageItem::LIMITATION_UNIQUE_PRODUCT) { $productId = $product->get_parent_id() ? $product->get_parent_id() : $product->get_id(); if ( ! isset($validItemsGrouped[$productId])) { $validItemsGrouped[$productId] = $cartItem; } } elseif ($limitation === PackageItem::LIMITATION_UNIQUE_VARIATION) { if ( ! isset($validItemsGrouped[$product->get_id()])) { $validItemsGrouped[$product->get_id()] = $cartItem; } } elseif ($limitation === PackageItem::LIMITATION_UNIQUE_HASH) { $validItemsGrouped[] = $cartItem; } } $collectedQtyInCart = count( array_filter( $validItemsGrouped, function ($v) { return !$v->hasAttr(CartItemAttributeEnum::TEMPORARY()); } ) ); if ($range->isLess(count($validItemsGrouped))) { return null; } elseif ($range->isIn(count($validItemsGrouped))) { // do nothing } elseif ($range->isGreater(count($validItemsGrouped))) { if ( ! is_infinite($range->getTo())) { $validItemsGrouped = array_slice($validItemsGrouped, 0, $range->getTo()); } } foreach ($validItemsGrouped as $validItem) { $requireQty = 1; $setItem = clone $validItem; $setItem->setQty($setItem->getQty() - ($validItem->getQty() - $requireQty)); $validItem->setQty($validItem->getQty() - $requireQty); $packageSetItems[] = $setItem; } return $packageSetItems; } /** * @param Cart $cart * * @return CartSetCollection * @throws Exception */ public function collectSetsLegacy(&$cart) { $collection = new CartSetCollection(); $applied = true; while ($applied && $collection->getTotalSetsQty() !== $this->rule->getPackagesCountLimit()) { $setItems = array(); foreach ($this->packages as $filter_key => &$filter) { $validHashes = ! empty($filter['valid_hashes']) ? $filter['valid_hashes'] : array(); $limitation = ! empty($filter['limitation']) ? $filter['limitation'] : PackageItem::LIMITATION_NONE; $isExcludeMatchedPreviousFilters = !empty($filter['excludeMatchedPreviousFilters']); $isSamePreviousFilters = !empty($filter['isSamePreviousFilters']); $package = $filter['package']; /** @var $package PackageItem */ $range = new Range($package->getQty(), $package->getQtyEnd(), $validHashes); $collectedQtyInCart = 0; if (in_array($limitation, array( PackageItem::LIMITATION_UNIQUE_PRODUCT, PackageItem::LIMITATION_UNIQUE_VARIATION, PackageItem::LIMITATION_UNIQUE_HASH, ))) { if ($packageSetItems = $this->handleUniqueLimitations($limitation, $validHashes, $range, $collectedQtyInCart)) { $applied = $applied && count($packageSetItems); $setItems[] = $packageSetItems; } else { $applied = false; } foreach($package->getFilters() as $packageFilter) { $packageFilter->setCollectedQtyInCart($collectedQtyInCart); } continue; } $valid_hashes_grouped = array(); if ($limitation === PackageItem::LIMITATION_SAME_VARIATION) { foreach ($validHashes as $index => $validCartItemHash) { $cartItem = $this->mutableItemsCollection->getNotEmptyItemWithReferenceByHash($validCartItemHash); if ( ! $cartItem) { continue; } $product = $cartItem->getWcItem()->getProduct(); if ( ! isset($valid_hashes_grouped[$product->get_id()])) { $valid_hashes_grouped[$product->get_id()] = array(); } $valid_hashes_grouped[$product->get_id()][] = $validCartItemHash; } } elseif ($limitation === PackageItem::LIMITATION_SAME_PRODUCT) { foreach ($validHashes as $index => $validCartItemHash) { $cartItem = $this->mutableItemsCollection->getNotEmptyItemWithReferenceByHash($validCartItemHash); if ( ! $cartItem) { continue; } $product = $cartItem->getWcItem()->getProduct(); $productId = $product->get_parent_id() ? $product->get_parent_id() : $product->get_id(); if ( ! isset($valid_hashes_grouped[$productId])) { $valid_hashes_grouped[$productId] = array(); } $valid_hashes_grouped[$productId][] = $validCartItemHash; } } elseif ($limitation === PackageItem::LIMITATION_SAME_HASH) { foreach ($validHashes as $index => $validCartItemHash) { $valid_hashes_grouped[] = array($validCartItemHash); } } else { $valid_hashes_grouped[] = $validHashes; } $filterApplied = false; foreach ($valid_hashes_grouped as $validHashes) { $filterApplied = false; $filterSetItems = array(); foreach ($validHashes as $index => $validCartItemHash) { $cartItem = $this->mutableItemsCollection->getNotEmptyItemWithReferenceByHash($validCartItemHash); if (is_null($cartItem)) { unset($validHashes[$index]); continue; } if ( $isExcludeMatchedPreviousFilters ) { $atLeastOneInTheSet = false; foreach ($setItems as $tmpFilterSetItems) { foreach ($tmpFilterSetItems as $setItem) { if ( ( apply_filters('adp_only_variation_parent', false) || $setItem->getWcItem()->getVariationId() == $cartItem->getWcItem()->getVariationId() ) && $setItem->getWcItem()->getProductId() == $cartItem->getWcItem()->getProductId() ) { $atLeastOneInTheSet = true; } } } if ( $atLeastOneInTheSet ) { continue; } } if ($isSamePreviousFilters) { $tmpFilterSetItems = end($setItems); if ($tmpFilterSetItems) { /** @var CartItem[] $tmpFilterSetItems */ $prevHashes = []; foreach ($tmpFilterSetItems as $setItem) { $prevHashes[] = $setItem->getWcItem()->getKey(); } if (!in_array($cartItem->getWcItem()->getKey(), $prevHashes, true)) { /** * A workaround to enable the `same as previous` filter. * * Return all collected set items into cart and pretend that filter has been applied. * In other words, we intentionally restart the `while` loop ignoring the fact that * previous run was unsuccessful. */ $this->returnSetItemsToCart($setItems, $cart); $setItems = []; $filterApplied = true; break; } } } $collectedQty = 0; $collectedQtyInCart = 0; foreach ($filterSetItems as $filterSetItem) { /** * @var $filterSetItem BasicCartItem */ if (!$filterSetItem->hasAttr(CartItemAttributeEnum::TEMPORARY())) { $collectedQtyInCart += $filterSetItem->getQty(); } $collectedQty += $filterSetItem->getQty(); } if (!$cartItem->hasAttr(CartItemAttributeEnum::TEMPORARY())) { $collectedQtyInCart += $cartItem->getQty(); } $collectedQty += $cartItem->getQty(); if ( ! $range->isValid()) { continue; } if ($range->isLess($collectedQty)) { $setCartItem = clone $cartItem; $cartItem->setQty(0); $filterSetItems[] = $setCartItem; } elseif ($range->isIn($collectedQty)) { $setCartItem = clone $cartItem; $cartItem->setQty(0); $filterSetItems[] = $setCartItem; $filterApplied = true; if ($range->getTo() === $collectedQty) { break; } } elseif ($range->isGreater($collectedQty)) { $modeValueTo = $range->getTo(); if (is_infinite($modeValueTo)) { continue; } $requireQty = $modeValueTo + $cartItem->getQty() - $collectedQty; $setCartItem = clone $cartItem; $setCartItem->setQty($setCartItem->getQty() - ($cartItem->getQty() - $requireQty)); $cartItem->setQty($cartItem->getQty() - $requireQty); $filterSetItems[] = $setCartItem; $filterApplied = true; break; } } if ($filterSetItems) { if ($filterApplied) { $setItems[] = $filterSetItems; } else { /** * For range filters, try to put remaining items in set * * If range 'to' equals infinity or 'to' not equal 'from' */ if ($range->getQty() === false || $range->getQty()) { $collectedQtyInCart = 0; $collectedQty = 0; foreach ($filterSetItems as $filterSetItem) { /** * @var $filterSetItem BasicCartItem */ $collectedQtyInCart += $filterSetItem->getQty(); $collectedQty += $filterSetItem->getQty(); } if ($range->isIn($collectedQty)) { $setItems[] = $filterSetItems; $filterSetItems = array(); $filterApplied = true; } } foreach ($filterSetItems as $item) { /** * @var $item BasicCartItem */ $this->mutableItemsCollection->add($item); } } $filterSetItems = array(); } if ($filterApplied) { break; } } foreach($package->getFilters() as $packageFilter) { $packageFilter->setCollectedQtyInCart($collectedQtyInCart); } $applied = $applied && $filterApplied; } if ($setItems && $applied) { $collection->add(new CartSet($this->rule->getId(), $setItems)); $setItems = array(); } $this->checkExecutionTime(); } if ( ! empty($setItems)) { $this->returnSetItemsToCart($setItems, $cart); } if ( ! empty($filterSetItems)) { foreach ($filterSetItems as $item) { $cart->addToCart($item); } } foreach ($this->mutableItemsCollection->get_items() as $item) { /** * @var $item ICartItem */ $cart->addToCart($item); } return $collection; } /** * @param array> $setItems * @param Cart $cart * @return void */ private function returnSetItemsToCart($setItems, $cart) { foreach ($setItems as $tmpFilterSetItems) { foreach ($tmpFilterSetItems as $item) { $cart->addToCart($item); } } } }