26 private static $instance;
30 private function __construct()
49 $this->userId = $userId;
54 private function __clone()
63 if (!isset(self::$instance))
65 self::$instance =
new self;
68 return self::$instance;
81 $this->errorCollection->clear();
83 if(!$this->isValidProduct($product))
88 $basketCopied = $basket->copy();
89 $discounts = $this->getDiscounts($basketCopied);
90 Discount\Preset\Manager::getInstance()->registerAutoLoader();
91 $predictionDiscount = $this->findFirstPredictionDiscount($discounts, OrderAmount::className());
93 if($predictionDiscount)
95 $text = $this->buildTextByPredictionDiscount($basketCopied, $predictionDiscount);
102 $templates = (!empty($options[
'PAGE_TEMPLATES']) && is_array($options[
'PAGE_TEMPLATES']) ? $options[
'PAGE_TEMPLATES'] : []);
104 return $this->tryToFindPredictionConnectedProducts($basket->copy(), $product, $templates);
107 private function tryToFindPredictionConnectedProducts(
Basket $basket, array $product, array $templates = []): ?string
109 if(!$this->checkProductInBasket($product, $basket))
111 $this->addProductToBasket($basket, $product);
114 $discounts = $this->getDiscounts($basket);
115 $predictionDiscount = $this->findFirstPredictionDiscount($discounts, ConnectedProduct::className());
116 if ($predictionDiscount ===
null)
121 $manager = Discount\Preset\Manager::getInstance();
122 $preset = $manager->getPresetById($predictionDiscount[
'PRESET_ID']);
123 if(!$preset instanceof ConnectedProduct)
128 $currentProductIds = $this->extendProductIds(array($product[
'ID']));
129 $currentSectionIds = $this->getSectionIdsByProduct($product);
131 $state = $preset->generateState($predictionDiscount);
132 list($typeConditionProduct, $dataCondition) = $preset->getDescribedDataProductCondition($state);
133 list($typeActionProduct, $dataAction) = $preset->getDescribedDataProductAction($state);
135 $isAct = $isCond =
false;
137 if($typeConditionProduct === $preset::TYPE_PRODUCT)
139 $condProductIds = $this->extendProductIds($dataCondition);
140 if(array_intersect($condProductIds, $currentProductIds))
145 elseif($typeConditionProduct === $preset::TYPE_SECTION)
147 if(array_intersect($dataCondition, $currentSectionIds))
153 if($typeActionProduct === $preset::TYPE_PRODUCT)
155 $actProductIds = $this->extendProductIds($dataAction);
156 if(array_intersect($actProductIds, $currentProductIds))
161 elseif($typeActionProduct === $preset::TYPE_SECTION)
163 if(array_intersect($dataAction, $currentSectionIds))
169 if(!$isAct && !$isCond)
174 $predictionText = $preset->getPredictionText(
176 $isAct? $preset::PREDICTION_TEXT_TYPE_ACTION : $preset::PREDICTION_TEXT_TYPE_CONDITION
179 $currencyFormat =
'# ' . $predictionDiscount[
'CURRENCY'];
180 if(Loader::includeModule(
'currency'))
182 $currencyFormat = \CCurrencyLang::getCurrencyFormat($predictionDiscount[
'CURRENCY']);
183 $currencyFormat = $currencyFormat[
'FORMAT_STRING'];
186 $discountValue = str_replace(
'#', $state[
'discount_value'], $currencyFormat);
187 if($state[
'discount_type'] ===
'Perc')
189 $discountValue = $state[
'discount_value'] .
' %';
192 $placeholders = array(
193 '#DISCOUNT_VALUE#' => $discountValue,
198 if ($typeActionProduct === $preset::TYPE_SECTION)
200 $itemData = $this->getSectionData($dataAction, $templates);
201 if (!empty($itemData))
203 $placeholders = $placeholders + $itemData;
207 if ($typeActionProduct === $preset::TYPE_PRODUCT)
209 $itemData = $this->getProductData($dataAction, $templates);
210 if (!empty($itemData))
212 $placeholders = $placeholders + $itemData;
219 if ($typeConditionProduct === $preset::TYPE_SECTION)
221 $itemData = $this->getSectionData($dataCondition, $templates);
222 if (!empty($itemData))
224 $placeholders = $placeholders + $itemData;
228 if($typeConditionProduct === $preset::TYPE_PRODUCT)
230 $itemData = $this->getProductData($dataCondition, $templates);
231 if (!empty($itemData))
233 $placeholders = $placeholders + $itemData;
239 if (empty($placeholders[
'#NAME#']) || empty($placeholders[
'#LINK#']))
245 array_keys($placeholders),
246 array_values($placeholders),
251 private function getProductData($productId, array $templates = []): ?array
253 if (is_array($productId))
263 $iterator = \CIBlockElement::GetList(
265 [
'=ID' => $productId,
'CHECK_PERMISSIONS' =>
'N'],
268 [
'ID',
'IBLOCK_ID',
'NAME',
'DETAIL_PAGE_URL',
'CODE',
'IBLOCK_SECTION_ID']
270 if (!empty($templates[
'PRODUCT_URL']))
272 $iterator->SetUrlTemplates($templates[
'PRODUCT_URL']);
274 $row = $iterator->GetNext();
281 '#NAME#' => $row[
'~NAME'],
282 '#LINK#' => $row[
'~DETAIL_PAGE_URL']
286 private function getSectionData($sectionId, array $templates = []): ?array
288 if (is_array($sectionId))
298 $iterator = \CIBlockSection::GetList(
300 [
'=ID' => $sectionId,
'CHECK_PERMISSIONS' =>
'N'],
303 [
'ID',
'IBLOCK_ID',
'NAME',
'SECTION_PAGE_URL',
'CODE',
'IBLOCK_SECTION_ID']
305 if (!empty($templates[
'SECTION_URL']))
307 $iterator->SetUrlTemplates(
'', $templates[
'SECTION_URL']);
309 $row = $iterator->GetNext();
316 '#NAME#' => $row[
'~NAME'],
317 '#LINK#' => $row[
'~SECTION_PAGE_URL']
321 private function getSectionIdsByProduct(array $product)
323 $sectionIds = array();
324 foreach($this->extendProductIds(array($product[
'ID'])) as $productId)
326 $sectionIds = array_merge($sectionIds, $this->getSectionIdsByElement($productId));
329 return $this->extendSectionIds(array_unique($sectionIds));
332 private function getSectionIdsByElement($elementId)
334 $sectionIds = array();
335 $query = \CIBlockElement::getElementGroups($elementId,
true, array(
340 while($section = $query->fetch())
342 $sectionIds[] = $section[
'ID'];
348 private function extendSectionIds(array $sectionIds)
350 if(empty($sectionIds))
355 $extendedSectionIds = array();
357 $query = \CIBlockSection::GetList(array(), array(
359 ),
false, array(
'IBLOCK_ID',
'ID'));
361 while($row = $query->fetch())
363 $rsParents = \CIBlockSection::getNavChain($row[
'IBLOCK_ID'], $row[
'ID'], array(
'ID'));
364 while($arParent = $rsParents->fetch())
366 $extendedSectionIds[] = $arParent[
'ID'];
371 return $extendedSectionIds;
379 private function extendProductIds(array $productIds)
382 $products = \CCatalogSku::getProductList($productIds);
383 if (empty($products))
388 foreach($products as $product)
390 $productIds[] = $product[
'ID'];
396 private function checkProductInBasket(array $product, Basket $basket)
398 foreach($basket as $item)
402 $item->getProductId() == $product[
'ID'] &&
403 $item->getField(
'MODULE') == $product[
'MODULE']
413 private function buildTextByPredictionDiscount(Basket $basket, array $discount): ?string
415 if (empty($discount[
'PREDICTION_TEXT']))
419 $manager = Discount\Preset\Manager::getInstance();
420 $preset = $manager->getPresetById($discount[
'PRESET_ID']);
421 $state = $preset->generateState($discount);
423 $currencyFormat =
'# ' . $discount[
'CURRENCY'];
424 if(Loader::includeModule(
'currency'))
426 $currencyFormat = \CCurrencyLang::getCurrencyFormat($discount[
'CURRENCY']);
427 $currencyFormat = $currencyFormat[
'FORMAT_STRING'];
430 $placeholders = array();
431 if($preset instanceof OrderAmount)
433 $discountValue = str_replace(
'#', $state[
'discount_value'], $currencyFormat);
434 if($state[
'discount_type'] ===
'Perc')
436 $discountValue = $state[
'discount_value'] .
' %';
439 $shortage = $state[
'discount_order_amount'] - $basket->getPrice();
447 $placeholders = array(
448 '#SHORTAGE#' => str_replace(
'#', $shortage, $currencyFormat),
449 '#DISCOUNT_VALUE#' => $discountValue,
454 array_keys($placeholders),
455 array_values($placeholders),
456 (
string)$discount[
'PREDICTION_TEXT']
460 private function findFirstPredictionDiscount(array $discounts, $typePrediction)
462 if(empty($discounts))
467 $manager = Discount\Preset\Manager::getInstance();
468 foreach($discounts as $discount)
470 if(empty($discount[
'PRESET_ID']) || empty($discount[
'PREDICTION_TEXT']))
475 $preset = $manager->getPresetById($discount[
'PRESET_ID']);
476 if($preset instanceof $typePrediction)
485 private function addProductToBasket(Basket $basket, array $product)
487 $basketItem = $basket->createItem($product[
'MODULE'], $product[
'ID']);
488 unset($product[
'MODULE'], $product[
'ID']);
490 $basketItem->setFields($product);
493 private function getDiscounts(Basket $basket)
495 if($basket->getOrder())
497 throw new SystemException(
'Could not get discounts by basket which has order.');
503 $orderClass = $registry->getOrderClassName();
506 $order = $orderClass::create($basket->getSiteId(), $this->userId);
507 $discount = $order->getDiscount();
508 $discount->enableCheckingPrediction();
509 if(!$order->setBasket($basket)->isSuccess())
513 $calcResults = $discount->getApplyResult(
true);
515 return $calcResults[
'FULL_DISCOUNT_LIST']?: array();
518 private function isValidProduct(array $product)
520 if(empty($product[
'ID']))
522 $this->errorCollection[] =
new Error(
'Product array has to have ID');
524 if(empty($product[
'MODULE']))
526 $this->errorCollection[] =
new Error(
'Product array has to have MODULE');
528 if(empty($product[
'PRODUCT_PROVIDER_CLASS']))
530 $this->errorCollection[] =
new Error(
'Product array has to have PRODUCT_PROVIDER_CLASS');
532 if(empty($product[
'QUANTITY']))
534 $this->errorCollection[] =
new Error(
'Product array has to have QUANTITY');
536 if($this->errorCollection->count())