%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/sale/lib/discount/gift/ |
Current File : //home/bitrix/www/bitrix/modules/sale/lib/discount/gift/manager.php |
<?php namespace Bitrix\Sale\Discount\Gift; use Bitrix\Main\Config\Option; use Bitrix\Main\Error; use Bitrix\Main\ErrorCollection; use Bitrix\Main\SystemException; use Bitrix\Sale\Basket; use Bitrix\Sale\BasketItem; use Bitrix\Sale\Discount\Analyzer; use Bitrix\Sale\Order; use CCatalogSKU; use CSaleDiscountActionApply; use SplObjectStorage; final class Manager { /** @var ErrorCollection */ protected $errorCollection; /** @var SplObjectStorage */ protected $basketCloneCache; /** @var SplObjectStorage */ protected $basketAddedProduct; /** @var Manager */ private static $instance; /** @var int */ private $userId; private function __construct() { $this->errorCollection = new ErrorCollection; $this->basketCloneCache = new SplObjectStorage; $this->basketAddedProduct = new SplObjectStorage; } /** * @param Basket $basket * @return Basket * @throws \Bitrix\Main\SystemException */ private function getBasketCopy(Basket $basket) { if(!$this->basketCloneCache->contains($basket)) { $this->basketCloneCache[$basket] = $basket->copy(); } if($this->basketAddedProduct->contains($this->basketCloneCache[$basket])) { foreach($this->basketAddedProduct[$this->basketCloneCache[$basket]] as $product) { $this->deleteProductFromBasket($this->basketCloneCache[$basket], $product); } $this->basketAddedProduct->detach($this->basketCloneCache[$basket]); } return $this->basketCloneCache[$basket]; } /** * @return int */ public function getUserId() { return $this->userId; } /** * @param int $userId * @return $this */ public function setUserId($userId) { $this->userId = $userId; return $this; } private function __clone() {} /** * Returns Singleton of Manager * @return Manager */ public static function getInstance() { if (!isset(self::$instance)) { self::$instance = new self; } return self::$instance; } /** * Returns list of collections with gifts by basket. * For performance you may set arguments $discounts and $appliedDiscounts, which respond to basket. * * @param Basket $basket Target basket. * @param array|null $discounts List of all discounts for basket, which already succeed conditions. * @param array|null $appliedDiscounts List of all discounts for basket, which already applied to basket. * @return array */ public function getCollectionsByBasket(Basket $basket, array $discounts = null, array $appliedDiscounts = null) { $this->errorCollection->clear(); if(!$this->existsDiscountsWithGift()) { return array(); } if($discounts === null || $appliedDiscounts === null) { list($discounts, $appliedDiscounts) = $this->getDiscounts($basket); } $appliedList = array(); foreach($appliedDiscounts as $discount) { $appliedList[$discount['ID']] = $discount; } unset($discount, $appliedDiscounts); if(!$discounts) { return array(); } $potentialGiftData = $this->getPotentialGiftData($discounts, $appliedList); $collections = array(); foreach($potentialGiftData as $giftData) { $giftData['GiftValue'] = is_array($giftData['GiftValue'])? $giftData['GiftValue'] : array($giftData['GiftValue']); $giftCollection = new Collection(array(), $giftData['Type']); foreach($giftData['GiftValue'] as $value) { $giftCollection[] = new Gift($value); } unset($value); $collections[] = $giftCollection; } unset($giftData); return $collections; } private function getGiftedProductIdsByAppliedDiscount(array $discount) { if(empty($discount['RESULT']['BASKET'])) { return array(); } $giftedProducts = array(); foreach($discount['RESULT']['BASKET'] as $item) { if(empty($item['VALUE_PERCENT']) || $item['VALUE_PERCENT'] != 100) { continue; } //todo today we work only with catalog items. In future we will move the method to gifter and there //will return gifted products. if(empty($item['MODULE']) || $item['MODULE'] !== 'catalog') { continue; } $giftedProducts[] = $item['PRODUCT_ID']; } unset($item); return $giftedProducts; } private function deleteGiftedProducts(array $gifts, array $giftedProductIds) { foreach($gifts as $i => &$giftItem) { if($giftItem['Type'] === CSaleDiscountActionApply::GIFT_SELECT_TYPE_ONE) { if(array_intersect($giftedProductIds, (array)$giftItem['GiftValue'])) { unset($gifts[$i]); continue; } } elseif($giftItem['Type'] === CSaleDiscountActionApply::GIFT_SELECT_TYPE_ALL) { $giftItem['GiftValue'] = array_diff((array)$giftItem['GiftValue'], $giftedProductIds); if(!$giftItem['GiftValue']) { unset($gifts[$i]); continue; } } } unset($giftItem); return $gifts; } private function getPotentialGiftData(array $discounts, array $appliedDiscounts = array()) { if(!$discounts) { return array(); } $potentialGiftData = array(); foreach($appliedDiscounts as $discount) { $giftedProductIds = $this->getGiftedProductIdsByAppliedDiscount($discount); $potentialGiftData = array_merge( $potentialGiftData, $this->deleteGiftedProducts( \CSaleActionGiftCtrlGroup::ProvideGiftProductData($discount), \CSaleActionGiftCtrlGroup::ExtendProductIds($giftedProductIds) ) ); } unset($discount); foreach($discounts as $discount) { if(isset($appliedDiscounts[$discount['ID']])) { continue; } //todo Does the list use LAST_DISCOUNT configuration? $data = \CSaleActionGiftCtrlGroup::ProvideGiftProductData($discount); if(!$data) { continue; } $potentialGiftData = array_merge($potentialGiftData, $data); } unset($discount); return $potentialGiftData; } private function isValidProduct(array $product) { if(empty($product['ID'])) { $this->errorCollection[] = new Error('Product array has to have ID'); } if(empty($product['MODULE'])) { $this->errorCollection[] = new Error('Product array has to have MODULE'); } if(empty($product['PRODUCT_PROVIDER_CLASS'])) { $this->errorCollection[] = new Error('Product array has to have PRODUCT_PROVIDER_CLASS'); } if(empty($product['QUANTITY'])) { $this->errorCollection[] = new Error('Product array has to have QUANTITY'); } if($this->errorCollection->count()) { return false; } return true; } /** * Returns list of collections with gifts by product and some basket. * If basket does not contain product, then we calculate gifts without the product. * After we add the product to basket, calculate gifts and comparing with previous gifts. * If there is difference, then the method returns gifts with the product, else the method returns empty array. * * @param Basket $basket Target basket. * @param array $product Array which describes product (@see isValidProduct()). * @return array|null */ public function getCollectionsByProduct(Basket $basket, array $product) { $this->errorCollection->clear(); if(!$this->existsDiscountsWithGift()) { return array(); } if(!$this->isValidProduct($product)) { return null; } $pseudoBasket = $this->getBasketCopy($basket); $checkProductInBasket = $this->checkProductInBasket($product, $pseudoBasket); if($checkProductInBasket) { $this->deleteProductFromBasket($pseudoBasket, $product); } else { $this->addProductToBasket($pseudoBasket, $product); } $collectionsByPseudoBasket = $this->getCollectionsByBasket($pseudoBasket); $collectionsByBasket = $this->getCollectionsByBasket($basket); if(!$this->hasDifference($collectionsByBasket, $collectionsByPseudoBasket)) { return array(); } return $checkProductInBasket? $collectionsByBasket : $collectionsByPseudoBasket; } private function hasDifference(array $collectionsA, array $collectionsB) { foreach($collectionsA as $i => $collectionA) { $found = false; foreach($collectionsB as $j => $collectionB) { if($this->isEqual($collectionA, $collectionB)) { $found = true; unset($collectionsA[$i]); unset($collectionsB[$j]); break; } } unset($collectionB); if(!$found) { return true; } } unset($collectionA); return (bool)$collectionsB; } private function isEqual(Collection $collectionA, Collection $collectionB) { $productIdsFromCollectionA = $this->getProductIdsFromCollection($collectionA); $productIdsFromCollectionB = $this->getProductIdsFromCollection($collectionB); return !array_diff($productIdsFromCollectionA, $productIdsFromCollectionB) && !array_diff($productIdsFromCollectionB, $productIdsFromCollectionA) ; } private function getProductIdsFromCollection(Collection $collection) { $idsFrom = array(); foreach($collection as $gift) { /** @var Gift $gift */ $idsFrom[$gift->getProductId()] = $gift->getProductId(); } unset($gift); return $idsFrom; } /** * Returns basket items in specific format which we can use to insert in discount and build structure which * will look like old discounts in \CAllSaleDiscount::DoProcessOrder in DISCOUNT_LIST. * * @param Basket $basket * @param array $discountData * @param array $calcResults * @return array */ private function getAffectedReformattedBasketItemsInDiscount(Basket $basket, array $discountData, array $calcResults) { $items = array(); foreach($calcResults['PRICES']['BASKET'] as $basketCode => $priceData) { if(empty($priceData['DISCOUNT'])) { continue; } if(!empty($priceData['PRICE'])) { continue; } if(empty($calcResults['RESULT']['BASKET'][$basketCode])) { continue; } //we have gift and PRICE equals 0. $found = false; foreach($calcResults['RESULT']['BASKET'][$basketCode] as $data) { if($data['DISCOUNT_ID'] == $discountData['ID']) { $found = true; } } unset($data); if(!$found) { continue; } $basketItem = $basket->getItemByBasketCode($basketCode); if(!$basketItem || $basketItem->getField('MODULE') != 'catalog') { continue; } $items[] = array( 'PRODUCT_ID' => $basketItem->getProductId(), 'VALUE_PERCENT' => '100', 'MODULE' => 'catalog', ); } unset($priceData); return $items; } private function getDiscounts(Basket $basket) { if($basket->getOrder()) { throw new SystemException('Could not get discounts by basket which has order.'); } $order = Order::create($basket->getSiteId(), $this->userId); if(!$order->setBasket($basket)->isSuccess()) { return null; } $calcResults = $order->getDiscount()->getApplyResult(true); $appliedDiscounts = array(); foreach($calcResults['DISCOUNT_LIST'] as $discountData) { if(isset($calcResults['FULL_DISCOUNT_LIST'][$discountData['REAL_DISCOUNT_ID']])) { $appliedDiscounts[$discountData['REAL_DISCOUNT_ID']] = $calcResults['FULL_DISCOUNT_LIST'][$discountData['REAL_DISCOUNT_ID']]; if(empty($appliedDiscounts[$discountData['REAL_DISCOUNT_ID']]['RESULT']['BASKET'])) { $appliedDiscounts[$discountData['REAL_DISCOUNT_ID']]['RESULT']['BASKET'] = array(); } $appliedDiscounts[$discountData['REAL_DISCOUNT_ID']]['RESULT']['BASKET'] = array_merge( $appliedDiscounts[$discountData['REAL_DISCOUNT_ID']]['RESULT']['BASKET'], $this->getAffectedReformattedBasketItemsInDiscount($basket, $discountData, $calcResults) ); } } unset($discountData); return array( $calcResults['FULL_DISCOUNT_LIST'], $appliedDiscounts, ); } private function checkProductInBasket(array $product, Basket $basket) { return (bool)$this->getItemFromBasket($product, $basket); } private function getItemFromBasket(array $product, Basket $basket) { foreach($basket as $item) { /** @var BasketItem $item */ if( $item->getProductId() == $product['ID'] && $item->getField('MODULE') === $product['MODULE'] ) { return $item; } } return null; } private function addProductToBasket(Basket $basket, array $product) { $basketItem = $basket->createItem($product['MODULE'], $product['ID']); unset($product['MODULE'], $product['ID']); $result = $basketItem->setFields($product); if(!$result->isSuccess()) { return; } if(!$this->basketAddedProduct->contains($basket)) { $this->basketAddedProduct[$basket] = array($product); } else { $this->basketAddedProduct[$basket][] = $product; } } private function deleteProductFromBasket(Basket $basket, array $product) { $item = $this->getItemFromBasket($product, $basket); if($item && $item->getQuantity() == $product['QUANTITY']) { $item->delete(); } } private function existProductInAppliedDiscounts(array $product, array $appliedDiscounts) { foreach($appliedDiscounts as $discount) { if(array_search($product['ID'], $this->getGiftedProductIdsByAppliedDiscount($discount)) !== false) { return true; } } return false; } /** * Returns true if the product is gift for basket. * * @param Basket $basket Target basket. * @param array $product Array which describes product (@see isValidProduct()). * @return bool|null */ public function isGift(Basket $basket, array $product) { $this->errorCollection->clear(); if(!$this->existsDiscountsWithGift()) { return false; } if(!$this->isValidProduct($product)) { return null; } if(!$this->checkProductInBasket($product, $basket)) { $basket = $this->getBasketCopy($basket); $this->addProductToBasket($basket, $product); } list(, $appliedDiscounts) = $this->getDiscounts($basket); return $this->existProductInAppliedDiscounts($product, $appliedDiscounts); } /** * Returns true if the discount contains action with gift. * * @param array $discount Discount. * @return bool */ public function isContainGiftAction(array $discount) { return Analyzer::getInstance()->isContainGiftAction($discount); } /** * Returns true if exists discount with gift. * @return bool * @throws \Bitrix\Main\ArgumentNullException */ public function existsDiscountsWithGift() { return Option::get('sale', 'exists_discounts_with_gift', 'N') === 'Y'; } /** * Disables existence discount with gift. * @return void */ public function disableExistenceDiscountsWithGift() { Option::set('sale', 'exists_discounts_with_gift', 'N'); } /** * Enables existence discount with gift. * @return void */ public function enableExistenceDiscountsWithGift() { Option::set('sale', 'exists_discounts_with_gift', 'Y'); } }