Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
discountmanager.php
1<?php
3
11
12Loc::loadMessages(__FILE__);
13
15{
16 protected static $discountCache = [];
17 protected static $typeCache = [];
18 protected static $editUrlTemplate = [];
19 protected static $saleIncluded = null;
20 protected static $preloadedPriceData = [];
21 protected static $preloadedProductsData = [];
22 protected static $productProperties = [];
23
31 public static function catalogDiscountManager(Main\Event $event): Main\EventResult
32 {
33 return new Main\EventResult(
34 Main\EventResult::SUCCESS,
35 [
36 'prepareData' => [__CLASS__, 'prepareData'],
37 'getEditUrl' => [__CLASS__, 'getEditUrl'],
38 'calculateApplyCoupons' => [__CLASS__, 'calculateApplyCoupons'],
39 'roundBasket' => [__CLASS__, 'roundBasket']
40 ],
41 'catalog'
42 );
43 }
44
52 public static function prepareData(array $discount, $params = [])
53 {
54 if (empty($discount) || empty($discount['ID']))
55 return false;
56
57 $discountId = (int)$discount['ID'];
58 if ($discountId <= 0)
59 return false;
60 if (!isset(self::$discountCache[$discountId]))
61 {
62 self::$discountCache[$discountId] = false;
63
64 $loadData = self::loadFromDatabase($discountId, $discount);
65 if (!empty($loadData))
66 {
67 $loadData['LAST_LEVEL_DISCOUNT'] = 'N';
68 if ($loadData['CURRENCY'] != $params['CURRENCY'])
69 Catalog\DiscountTable::convertCurrency($loadData, $params['CURRENCY']);
70 self::createSaleAction($loadData, $params);
71 $loadData['EDIT_PAGE_URL'] = self::getEditUrl([
72 'ID' => $discountId, 'TYPE' => $loadData['TYPE']
73 ]);
74 self::$discountCache[$discountId] = $loadData;
75 }
76 }
77 $result = self::$discountCache[$discountId];
78 if (empty($result))
79 return $result;
80 if ($result['USE_COUPONS'] == 'Y')
81 {
82 if (isset($discount['COUPON']))
83 $result['COUPON'] = $discount['COUPON'];
84 }
85
86 return $result;
87 }
88
95 public static function getEditUrl(array $discount): string
96 {
97 if (empty(self::$editUrlTemplate))
98 {
99 self::$editUrlTemplate = [
100 Catalog\DiscountTable::TYPE_DISCOUNT => '/bitrix/admin/cat_discount_edit.php?lang='.LANGUAGE_ID.'&ID=',
101 Catalog\DiscountTable::TYPE_DISCOUNT_SAVE => '/bitrix/admin/cat_discsave_edit.php?lang='.LANGUAGE_ID.'&ID='
102 ];
103 }
104 $result = '';
105 if (empty($discount['ID']) || (int)$discount['ID'] <= 0)
106 return $result;
107
108 $id = (int)$discount['ID'];
109 $type = -1;
110 if (isset($discount['TYPE']))
111 $type = (int)$discount['TYPE'];
112
113 if ($type != Catalog\DiscountTable::TYPE_DISCOUNT && $type != Catalog\DiscountTable::TYPE_DISCOUNT_SAVE)
114 {
115 if (isset(self::$typeCache[$id]))
116 {
117 $type = self::$typeCache[$id];
118 }
119 else
120 {
121 $discountIterator = Catalog\DiscountTable::getList([
122 'select' => ['ID', 'TYPE'],
123 'filter' => ['=ID' => $id]
124 ]);
125 $data = $discountIterator->fetch();
126 if (!empty($data))
127 {
128 $type = (int)$data['TYPE'];
129 self::$typeCache[$id] = $type;
130 }
131 unset($data, $discountIterator);
132 }
133 }
134 if (isset(self::$editUrlTemplate[$type]))
135 $result = self::$editUrlTemplate[$type].$id;
136 unset($type, $id);
137 return $result;
138 }
139
149 public static function calculateApplyCoupons(array $couponsList, array $basket, array $params): array
150 {
151 $result = [];
152
153 if (empty($couponsList))
154 return $result;
155 if (empty($basket))
156 return $result;
157 $filteredBasket = array_filter($basket, '\Bitrix\Catalog\Discount\DiscountManager::basketFilter');
158 if (empty($filteredBasket))
159 return $result;
160 $filteredBasket = array_filter($filteredBasket, '\Bitrix\Catalog\Discount\DiscountManager::lastDiscountFilter');
161 if (empty($filteredBasket))
162 return $result;
163
164 $filteredCoupons = [];
165 foreach ($couponsList as $coupon)
166 {
167 if (!isset($coupon['COUPON']) || $coupon['COUPON'] == '')
168 continue;
169 if (!isset($coupon['DISCOUNT_ID']) || (int)$coupon['DISCOUNT_ID'] <= 0)
170 continue;
171 $filteredCoupons[] = $coupon['COUPON'];
172 }
173 unset($coupon);
174 if (empty($filteredCoupons))
175 return $result;
176
177 $discountIds = [];
178 $discountCoupons = [];
179 $oneRowCoupons = [];
180 $couponsIterator = Catalog\DiscountCouponTable::getList([
181 'select' => ['ID', 'COUPON', 'DISCOUNT_ID', 'TYPE'],
182 'filter' => ['@COUPON' => $filteredCoupons, 'ACTIVE' => 'Y']
183 ]);
184 while ($coupon = $couponsIterator->fetch())
185 {
186 $discountIds[$coupon['DISCOUNT_ID']] = true;
187 $discountCoupons[$coupon['COUPON']] = $coupon['COUPON'];
188 if ($coupon['TYPE'] == Catalog\DiscountCouponTable::TYPE_ONE_ROW)
189 $oneRowCoupons[$coupon['COUPON']] = true;
190 }
191 unset($coupon, $couponsIterator);
192 if (empty($discountCoupons))
193 return $result;
194
195 $userId = (isset($params['USER_ID']) ? (int)$params['USER_ID'] : 0);
196 if ($userId <= 0)
197 return $result;
198 $userGroups = Main\UserTable::getUserGroupIds($userId);
199 $userGroups[] = -1;
200
201 $iblockList = [];
202 $product2Iblock = [];
203 $itemIds = [];
204 foreach ($filteredBasket as $basketItem)
205 {
206 $productId = (int)$basketItem['PRODUCT_ID'];
207 $itemIds[$productId] = $productId;
208 }
209 unset($basketItem);
210
211 $itemIterator = Iblock\ElementTable::getList([
212 'select' => ['ID', 'IBLOCK_ID'],
213 'filter' => ['@ID' => $itemIds, '=ACTIVE' => 'Y']
214 ]);
215 while ($item = $itemIterator->fetch())
216 {
217 $id = (int)$item['ID'];
218 $iblockId = (int)$item['IBLOCK_ID'];
219 if (!isset($iblockList[$iblockId]))
220 $iblockList[$iblockId] = [];
221 $iblockList[$iblockId][$id] = $id;
222 $product2Iblock[$id] = $iblockId;
223 unset($iblockId, $id);
224 }
225 unset($item, $itemIterator);
226 unset($itemIds);
227
228 if (empty($iblockList))
229 return $result;
230
231 foreach($iblockList as $iblockId => $elements)
232 {
234 \CCatalogDiscount::setProductSectionsCache($elements);
236 \CCatalogDiscount::setDiscountProductCache($elements, ['IBLOCK_ID' => $iblockId, 'GET_BY_ID' => 'Y']);
237 }
238 unset($iblockId, $elements);
239
240 $discountPercentMode = \CCatalogDiscount::getUseBasePrice();
241 if (isset($params['USE_BASE_PRICE']))
242 \CCatalogDiscount::setUseBasePrice($params['USE_BASE_PRICE'] == 'Y');
243
244 Main\Type\Collection::sortByColumn($filteredBasket, ['PRICE' => SORT_DESC], '', null, true);
245 foreach ($filteredBasket as $basketCode => $basketItem)
246 {
247 $productId = (int)$basketItem['PRODUCT_ID'];
248 if (!isset($product2Iblock[$productId]))
249 continue;
250 if (empty($discountCoupons))
251 break;
252
254 $discountList = \CCatalogDiscount::getDiscount(
255 $productId,
256 $product2Iblock[$productId],
257 [-1],
258 $userGroups,
259 'N',
260 $params['SITE_ID'],
261 $discountCoupons
262 );
263
264 if (empty($discountList))
265 continue;
266
267 $itemDiscounts = [];
268 foreach ($discountList as $discount)
269 {
270 if (!isset($discountIds[$discount['ID']]))
271 continue;
272 $itemDiscounts[] = $discount;
273 }
274 unset($discount, $discountList);
275 if (empty($itemDiscounts))
276 continue;
277
278 $itemsDiscountResult = \CCatalogDiscount::applyDiscountList($basketItem['PRICE'], $basketItem['CURRENCY'], $itemDiscounts);
279 unset($itemDiscounts);
280 if (!empty($itemsDiscountResult['DISCOUNT_LIST']))
281 {
282 $result[$basketCode] = [];
283 foreach ($itemsDiscountResult['DISCOUNT_LIST'] as $discount)
284 {
285 $result[$basketCode][] = \CCatalogDiscount::getDiscountDescription($discount);
286 if (!empty($discount['COUPON']) && isset($oneRowCoupons[$discount['COUPON']]))
287 unset($discountCoupons[$discount['COUPON']]);
288 }
289 unset($discount);
290 }
291 unset($itemsDiscountResult);
292 }
293 unset($basketCode, $basketItem, $basketItem);
294
295 \CCatalogDiscount::setUseBasePrice($discountPercentMode);
296 unset($discountPercentMode);
297
298 return $result;
299 }
300
310 public static function roundPrice(array $basketItem, array $roundData = []): array
311 {
312 if (empty($basketItem))
313 return [];
314
315 $result = self::roundBasket([0 => $basketItem], [0 => $roundData], []);
316 return (!empty($result[0]) ? $result[0] : []);
317 }
318
328 public static function roundBasket(array $basket, array $basketRoundData = [], array $order = []): array
329 {
330 if (empty($basket))
331 return [];
332
333 $result = [];
334 $basket = array_filter($basket, '\Bitrix\Catalog\Discount\DiscountManager::basketFilter');
335 if (!empty($basket))
336 {
337 $priceTypes = [];
338 $loadPriceId = [];
339 $loadBasketCodes = [];
340 foreach ($basket as $basketCode => $basketItem)
341 {
342 if (!empty($basketRoundData[$basketCode]))
343 continue;
344 $priceTypeId = 0;
345 if (isset($basketItem['PRICE_TYPE_ID']))
346 $priceTypeId = (int)$basketItem['PRICE_TYPE_ID'];
347 if ($priceTypeId <= 0 && isset($basketItem['CATALOG_GROUP_ID']))
348 $priceTypeId = (int)$basketItem['CATALOG_GROUP_ID'];
349 if ($priceTypeId <= 0 && isset($basketItem['PRODUCT_PRICE_ID']))
350 {
351 $priceId = (int)$basketItem['PRODUCT_PRICE_ID'];
352 if ($priceId > 0)
353 {
354 $cachedPrice = self::getPriceDataByPriceId($priceId);
355 if (!empty($cachedPrice))
356 $priceTypeId = (int)$cachedPrice['CATALOG_GROUP_ID'];
357 if ($priceTypeId <= 0)
358 {
359 $loadPriceId[] = $priceId;
360 $loadBasketCodes[$priceId] = $basketCode;
361 }
362 }
363 }
364
365 $basket[$basketCode]['PRICE_TYPE_ID'] = $priceTypeId;
366 if ($priceTypeId > 0)
367 $priceTypes[$priceTypeId] = $priceTypeId;
368
369 }
370 unset($priceId, $priceTypeId, $basketCode, $basketItem);
371
372 if (!empty($loadPriceId))
373 {
374 sort($loadPriceId);
375 foreach (array_chunk($loadPriceId, 500) as $pageIds)
376 {
377 $iterator = Catalog\PriceTable::getList([
378 'select' => ['ID', 'CATALOG_GROUP_ID'],
379 'filter' => ['@ID' => $pageIds]
380 ]);
381 while ($row = $iterator->fetch())
382 {
383 $id = (int)$row['ID'];
384 $priceTypeId = (int)$row['CATALOG_GROUP_ID'];
385 if (!isset($loadBasketCodes[$id]))
386 continue;
387 $basket[$loadBasketCodes[$id]]['PRICE_TYPE_ID'] = $priceTypeId;
388 $priceTypes[$priceTypeId] = $priceTypeId;
389 }
390 unset($priceTypeId, $id, $row, $iterator);
391 }
392 }
393 unset($loadBasketCodes, $loadPriceId);
394
395 if (!empty($priceTypes))
396 Catalog\Product\Price::loadRoundRules($priceTypes);
397 unset($priceTypes);
398
399 foreach ($basket as $basketCode => $basketItem)
400 {
401 if (!empty($basketRoundData[$basketCode]))
402 {
403 $roundData = $basketRoundData[$basketCode];
404 }
405 else
406 {
407 $roundData = Catalog\Product\Price::searchRoundRule(
408 $basketItem['PRICE_TYPE_ID'],
409 $basketItem['PRICE'],
410 $basketItem['CURRENCY']
411 );
412 }
413
414 if (empty($roundData))
415 {
416 continue;
417 }
418
419 $result[$basketCode] = self::getRoundResult($basketItem, $roundData);
420 }
421 unset($roundData, $basketCode, $basketItem, $basketRoundData);
422 }
423 unset($basket);
424
425 return $result;
426 }
427
436 public static function applyDiscount(&$product, $discount): void
437 {
438 if (empty($product) || !is_array($product))
439 return;
440 if (empty($discount) || empty($discount['TYPE']))
441 return;
442 if (isset($discount['CURRENCY']) && $discount['CURRENCY'] != $product['CURRENCY'])
443 return;
444 if (!isset($product['DISCOUNT_PRICE']))
445 $product['DISCOUNT_PRICE'] = 0;
446 $getPercentFromBasePrice = (isset($discount['USE_BASE_PRICE']) && $discount['USE_BASE_PRICE'] == 'Y');
447 $basePrice = (float)(
448 isset($product['BASE_PRICE'])
449 ? $product['BASE_PRICE']
450 : $product['PRICE'] + $product['DISCOUNT_PRICE']
451 );
452
453 switch ($discount['TYPE'])
454 {
455 case Catalog\DiscountTable::VALUE_TYPE_PERCENT:
456 $discount['VALUE'] = -$discount['VALUE'];
457 $discountValue = self::roundValue(
458 ((
459 $getPercentFromBasePrice
460 ? $basePrice
461 : $product['PRICE']
462 )*$discount['VALUE'])/100,
463 $product['CURRENCY']
464 );
465 if (isset($discount['MAX_VALUE']) && $discount['MAX_VALUE'] > 0)
466 {
467 if ($discountValue + $discount['MAX_VALUE'] <= 0)
468 $discountValue = -$discount['MAX_VALUE'];
469 }
470 $product['PRICE'] += $discountValue;
471 $product['DISCOUNT_PRICE'] -= $discountValue;
472 if (!empty($product['DISCOUNT_RESULT']))
473 {
474 $product['DISCOUNT_RESULT']['BASKET'][0]['RESULT_VALUE'] = (string)abs($discountValue);
475 $product['DISCOUNT_RESULT']['BASKET'][0]['RESULT_UNIT'] = $product['CURRENCY'];
476 }
477 unset($discountValue);
478 break;
479 case Catalog\DiscountTable::VALUE_TYPE_FIX:
480 $discount['VALUE'] = self::roundValue($discount['VALUE'], $product['CURRENCY']);
481 $product['PRICE'] -= $discount['VALUE'];
482 $product['DISCOUNT_PRICE'] += $discount['VALUE'];
483 break;
484 case Catalog\DiscountTable::VALUE_TYPE_SALE:
485 $discount['VALUE'] = self::roundValue($discount['VALUE'], $product['CURRENCY']);
486 $product['DISCOUNT_PRICE'] += ($product['PRICE'] - $discount['VALUE']);
487 $product['PRICE'] = $discount['VALUE'];
488 break;
489 }
490 }
491
499 public static function getPriceDataByProductId(int $productId, int $catalogGroupId): ?array
500 {
501 if (!isset(self::$preloadedPriceData[$productId.'-'.$catalogGroupId]))
502 {
503 self::$preloadedPriceData[$productId.'-'.$catalogGroupId] = null;
504 self::preloadPriceData([$productId], [$catalogGroupId]);
505 }
506 return self::$preloadedPriceData[$productId.'-'.$catalogGroupId];
507 }
508
517 public static function setProductPropertiesCache($productId, $props)
518 {
519 if (!is_array($props))
520 return;
521
522 self::$productProperties[$productId] = $props;
523 }
524
530 public static function clearProductPropertiesCache()
531 {
532 self::$productProperties = [];
533 }
534
540 public static function clearProductsCache()
541 {
542 self::$preloadedProductsData = [];
543 }
544
550 public static function clearProductPricesCache()
551 {
552 self::$preloadedPriceData = [];
553 }
554
562 public static function preloadPriceData(array $productIds, array $catalogGroups)
563 {
564 if (empty($productIds) || empty($catalogGroups))
565 return;
566 Collection::normalizeArrayValuesByInt($productIds);
567 if (empty($productIds))
568 return;
569 Collection::normalizeArrayValuesByInt($catalogGroups);
570 if (empty($catalogGroups))
571 return;
572
573 $productIds = self::extendProductIdsToOffer($productIds);
574
575 foreach($productIds as $i => $productId)
576 {
577 if(isset(self::$preloadedPriceData[$productId]))
578 {
579 unset($productIds[$i]);
580 }
581 }
582
583 if(empty($productIds))
584 {
585 return;
586 }
587
588 $dbPrice = Catalog\PriceTable::getList([
589 'select' => ['*'],
590 'filter' => ['@PRODUCT_ID' => $productIds, '@CATALOG_GROUP_ID' => $catalogGroups]
591 ]);
592 while($priceRow = $dbPrice->fetch())
593 {
594 self::$preloadedPriceData[$priceRow['PRODUCT_ID'].'-'.$priceRow['CATALOG_GROUP_ID']] = $priceRow;
595 }
596 }
597
598 private static function fillByPreloadedPrices(array &$productData, array $priceList)
599 {
600 foreach ($productData as $productId => $product)
601 {
602 foreach (self::$preloadedPriceData as $priceData)
603 {
604 if ($priceData['PRODUCT_ID'] != $productId)
605 {
606 continue;
607 }
608
609 if(!in_array($priceData['ID'], $priceList))
610 {
611 continue;
612 }
613
614 $productData[$productId]['CATALOG_GROUP_ID'] = $priceData['CATALOG_GROUP_ID'];
615 }
616 }
617 }
618
626 public static function preloadProductDataToExtendOrder(array $productIds, array $userGroups)
627 {
628 if (empty($productIds) || empty($userGroups))
629 return;
630 Collection::normalizeArrayValuesByInt($productIds, true);
631 if (empty($productIds))
632 return;
633 Collection::normalizeArrayValuesByInt($userGroups, true);
634 if (empty($userGroups))
635 return;
636
637 if(self::$saleIncluded === null)
638 self::$saleIncluded = Loader::includeModule('sale');
639
640 if(!self::$saleIncluded)
641 return;
642
643 $discountCache = Sale\Discount\RuntimeCache\DiscountCache::getInstance();
644
645 $discountIds = $discountCache->getDiscountIds($userGroups);
646 if(!$discountIds)
647 {
648 return;
649 }
650
651 Collection::normalizeArrayValuesByInt($discountIds, true);
652
653 $entityList = $discountCache->getDiscountEntities($discountIds);
654 if(!$entityList || empty($entityList['catalog']))
655 {
656 return;
657 }
658
659 $entityData = self::prepareEntity($entityList);
660 if(!$entityData)
661 {
662 return;
663 }
664
665 $productIds = self::extendProductIdsToOffer($productIds);
666
667 $iblockData = self::getProductIblocks($productIds);
668 self::fillProductPropertyList($entityData, $iblockData);
669
670 $productData = array_fill_keys($productIds, []);
671 if(empty($iblockData['iblockElement']))
672 {
673 return;
674 }
675
676 self::getProductData($productData, $entityData, $iblockData);
677
678 $cacheKeyForEntityList = self::getCacheKeyForEntityList($entityList);
679 if(!isset(self::$preloadedProductsData[$cacheKeyForEntityList]))
680 {
681 self::$preloadedProductsData[$cacheKeyForEntityList] = [];
682 }
683
684 foreach($productData as $productId => $data)
685 {
686 self::$preloadedProductsData[$cacheKeyForEntityList][$productId] = $data;
687 }
688 }
689
696 public static function extendOrderData(Main\Event $event): Main\EventResult
697 {
698 $process = true;
699 $resultData = [];
700 $orderData = $event->getParameter('ORDER');
701 $entityList = $event->getParameter('ENTITY');
702 $cacheKeyForEntityList = self::getCacheKeyForEntityList($entityList);
703
704 if (empty($orderData) || !is_array($orderData))
705 {
706 $process = false;
707 }
708 else
709 {
710 if (!isset($orderData['BASKET_ITEMS']) || !is_array($orderData['BASKET_ITEMS']))
711 $process = false;
712 }
713
714 $entityData = false;
715 $iblockData = false;
716 if (
717 $process
718 && !empty($orderData['BASKET_ITEMS'])
719 )
720 {
721 $entityData = self::prepareEntity($entityList);
722 if (empty($entityData))
723 $process = false;
724 }
725 if ($process)
726 {
727 $productMap = [];
728 $productList = [];
729 $productData = [];
730 $priceList = [];
731
732 $basket = array_filter($orderData['BASKET_ITEMS'], '\Bitrix\Catalog\Discount\DiscountManager::basketFilter');
733 if (!empty($basket))
734 {
735 foreach ($basket as $basketCode => $basketItem)
736 {
737 $basketItem['PRODUCT_ID'] = (int)$basketItem['PRODUCT_ID'];
738 $productList[] = $basketItem['PRODUCT_ID'];
739 if (!isset($productMap[$basketItem['PRODUCT_ID']]))
740 {
741 $productMap[$basketItem['PRODUCT_ID']] = [];
742 }
743 $productMap[$basketItem['PRODUCT_ID']][] = &$basket[$basketCode];
744
745 if (isset($basketItem['PRODUCT_PRICE_ID']))
746 {
747 $priceList[] = $basketItem['PRODUCT_PRICE_ID'];
748 }
749 }
750 unset($basketItem, $basketCode);
751
752 if(isset(self::$preloadedProductsData[$cacheKeyForEntityList]))
753 {
754 $preloadedProductIds = array_keys(self::$preloadedProductsData[$cacheKeyForEntityList]);
755 $loadedProductIds = array_intersect($productList, $preloadedProductIds);
756
757 $productList = array_diff($productList, $preloadedProductIds);
758 }
759
760 $productData = array_fill_keys($productList, []);
761
762 if($productData)
763 {
764 $iblockData = self::getProductIblocks($productList);
765 self::fillProductPropertyList($entityData, $iblockData);
766 self::fillProductPriceList($entityData, $priceList);
767 }
768 }
769
770 if (!empty($iblockData['iblockElement']))
771 {
772 self::getProductData($productData, $entityData, $iblockData);
773 }
774
775 if(!empty($loadedProductIds))
776 {
777 foreach($loadedProductIds as $loadedProductId)
778 {
779 $productData[$loadedProductId] = self::$preloadedProductsData[$cacheKeyForEntityList][$loadedProductId];
780 }
781
782 if(!empty($entityData['priceFields']))
783 {
784 self::fillByPreloadedPrices($productData, $priceList);
785 }
786 }
787
788 if($productData)
789 {
790 foreach ($productData as $product => $data)
791 {
792 if (empty($productMap[$product]))
793 continue;
794 foreach ($productMap[$product] as &$basketItem)
795 $basketItem['CATALOG'] = $data;
796 unset($basketItem);
797 }
798 unset($product, $data);
799
800 $resultData['BASKET_ITEMS'] = $basket;
801 }
802 unset($basket, $productData, $productMap, $productList);
803 }
804
805 if ($process)
806 $result = new Main\EventResult(Main\EventResult::SUCCESS, $resultData, 'catalog');
807 else
808 $result = new Main\EventResult(Main\EventResult::ERROR, null, 'catalog');
809 unset($process, $resultData);
810
811 return $result;
812 }
813
814 protected static function getCacheKeyForEntityList(array $entityList): string
815 {
816 return md5(serialize($entityList));
817 }
818
819 protected static function extendProductIdsToOffer(array $productIds): array
820 {
821 static $cache = [];
822
823 Collection::normalizeArrayValuesByInt($productIds);
824 if (empty($productIds))
825 return [];
826 $key = md5(implode('|', $productIds));
827
828 if(!isset($cache[$key]))
829 {
830 $extendedList = array_combine($productIds, $productIds);
831 foreach(\CCatalogSku::getOffersList($productIds) as $mainProduct)
832 {
833 foreach(array_keys($mainProduct) as $offerId)
834 {
835 if(!isset($extendedList[$offerId]))
836 {
837 $extendedList[$offerId] = $offerId;
838 }
839 }
840 }
841
842 $cache[$key] = $extendedList;
843 }
844
845 return $cache[$key];
846 }
847
854 protected static function basketFilter(array $basketItem): bool
855 {
856 return (
857 (
858 (isset($basketItem['MODULE']) && $basketItem['MODULE'] == 'catalog')
859 || (isset($basketItem['MODULE_ID']) && $basketItem['MODULE_ID'] == 'catalog')
860 )
861 && (isset($basketItem['PRODUCT_ID']) && (int)$basketItem['PRODUCT_ID'] > 0)
862 );
863 }
864
871 protected static function lastDiscountFilter(array $basketItem): bool
872 {
873 return (
874 !isset($basketItem['LAST_DISCOUNT'])
875 || $basketItem['LAST_DISCOUNT'] != 'Y'
876 );
877 }
878
885 protected static function loadFromDatabase(int $id, array $discount)
886 {
887 $select = [];
888 if (!isset($discount['NAME']))
889 $select['NAME'] = true;
890 if (empty($discount['CONDITIONS']))
891 $select['CONDITIONS_LIST'] = true;
892 if (empty($discount['UNPACK']))
893 $select['UNPACK'] = true;
894 if (empty($discount['USE_COUPONS']))
895 $discount['USE_COUPONS'] = (!empty($discount['COUPON']) ? 'Y' : 'N');
896 if (!isset($discount['SORT']))
897 $select['SORT'] = true;
898 if (!isset($discount['PRIORITY']))
899 $select['PRIORITY'] = true;
900 if (!isset($discount['LAST_DISCOUNT']))
901 $select['LAST_DISCOUNT'] = true;
902
903 if (
904 !isset($discount['TYPE'])
905 || ($discount['TYPE'] != Catalog\DiscountTable::TYPE_DISCOUNT && $discount['TYPE'] != Catalog\DiscountTable::TYPE_DISCOUNT_SAVE)
906 )
907 $select['TYPE'] = true;
908 if (!isset($discount['VALUE_TYPE']))
909 {
910 $select['VALUE_TYPE'] = true;
911 $select['VALUE'] = true;
912 $select['MAX_DISCOUNT'] = true;
913 $select['CURRENCY'] = true;
914 }
915 else
916 {
917 if (!isset($discount['VALUE']))
918 $select['VALUE'] = true;
919 if (!isset($discount['CURRENCY']))
920 $select['CURRENCY'] = true;
921 if ($discount['VALUE_TYPE'] == Catalog\DiscountTable::VALUE_TYPE_PERCENT && !isset($discount['MAX_VALUE']))
922 $select['MAX_DISCOUNT'] = true;
923 }
924 $selectKeys = array_keys($select);
925
926 if (!empty($select))
927 {
928 $discountIterator = Catalog\DiscountTable::getList([
929 'select' => $selectKeys,
930 'filter' => ['=ID' => $id]
931 ]);
932 $loadData = $discountIterator->fetch();
933 if (empty($loadData))
934 return false;
935 $discount = array_merge($loadData, $discount);
936 if (isset($discount['CONDITIONS_LIST']))
937 {
938 $discount['CONDITIONS'] = $discount['CONDITIONS_LIST'];
939 unset($discount['CONDITIONS_LIST']);
940 }
941 if (isset($discount['MAX_DISCOUNT']))
942 {
943 $discount['MAX_VALUE'] = $discount['MAX_DISCOUNT'];
944 unset($discount['MAX_DISCOUNT']);
945 }
946 unset($loadData, $discountIterator);
947 }
948 $discount['DISCOUNT_ID'] = $id;
949 if (empty($discount['MODULE_ID']))
950 $discount['MODULE_ID'] = 'catalog';
951 if (array_key_exists('HANDLERS', $discount))
952 {
953 if (!empty($discount['HANDLERS']['MODULES']) && empty($discount['MODULES']))
954 $discount['MODULES'] = $discount['HANDLERS']['MODULES'];
955 unset($discount['HANDLERS']);
956 }
957 if (empty($discount['MODULES']))
958 {
959 $discount['MODULES'] = [];
960
961 $conn = Main\Application::getConnection();
962 $helper = $conn->getSqlHelper();
964 $moduleIterator = $conn->query(
965 'select MODULE_ID from '.$helper->quote('b_catalog_discount_module').' where '.$helper->quote('DISCOUNT_ID').' = '.$id
966 );
967 while ($module = $moduleIterator->fetch())
968 $discount['MODULES'][] = $module['MODULE_ID'];
969 unset($module, $moduleIterator, $helper, $conn);
970 if (!in_array('catalog', $discount['MODULES']))
971 $discount['MODULES'][] = 'catalog';
972 }
973 self::$typeCache[$id] = $discount['TYPE'];
974
975 return $discount;
976 }
977
985 protected static function prepareEntity($entityList)
986 {
987 $result = [
988 'iblockFields' => [],
989 'sections' => false,
990 'iblockProperties' => [],
991 'iblockPropertiesMap' => [],
992 'catalogFields' => [],
993 'priceFields' => []
994 ];
995
996 if (!is_array($entityList))
997 return false;
998
999 if (empty($entityList['catalog']))
1000 return $result;
1001
1002 if (!empty($entityList['catalog']))
1003 {
1004 if (!empty($entityList['catalog']['ELEMENT']) && is_array($entityList['catalog']['ELEMENT']))
1005 {
1006 foreach ($entityList['catalog']['ELEMENT'] as $entity)
1007 {
1008 if ($entity['FIELD_ENTITY'] == 'SECTION_ID')
1009 {
1010 $result['sections'] = true;
1011 continue;
1012 }
1013 $result['iblockFields'][$entity['FIELD_TABLE']] = $entity['FIELD_ENTITY'];
1014 }
1015 unset($entity);
1016 }
1017 if (!empty($entityList['catalog']['ELEMENT_PROPERTY']) && is_array($entityList['catalog']['ELEMENT_PROPERTY']))
1018 {
1019 foreach ($entityList['catalog']['ELEMENT_PROPERTY'] as $entity)
1020 {
1021 $propertyData = explode(':', $entity['FIELD_TABLE']);
1022 if (!is_array($propertyData) || count($propertyData) != 2)
1023 continue;
1024 $iblock = (int)$propertyData[0];
1025 $property = (int)$propertyData[1];
1026 unset($propertyData);
1027 if (!isset($result['iblockProperties'][$iblock]))
1028 $result['iblockProperties'][$iblock] = [];
1029 $result['iblockProperties'][$iblock][] = $property;
1030 if (!isset($result['iblockPropertiesMap'][$iblock]))
1031 $result['iblockPropertiesMap'][$iblock] = [];
1032 $result['iblockPropertiesMap'][$iblock][$property] = $entity['FIELD_ENTITY'];
1033 }
1034 unset($iblock, $property, $entity);
1035 }
1036
1037 if (!empty($entityList['catalog']['PRODUCT']) && is_array($entityList['catalog']['PRODUCT']))
1038 {
1039 foreach ($entityList['catalog']['PRODUCT'] as $entity)
1040 $result['catalogFields'][$entity['FIELD_TABLE']] = $entity['FIELD_ENTITY'];
1041 unset($entity);
1042 }
1043
1044 if (!empty($entityList['catalog']['PRICE']) && is_array($entityList['catalog']['PRICE']))
1045 {
1046 foreach ($entityList['catalog']['PRICE'] as $entity)
1047 $result['priceFields'][$entity['FIELD_TABLE']] = $entity['FIELD_ENTITY'];
1048 unset($entity);
1049 }
1050 }
1051
1052 return $result;
1053 }
1054
1061 protected static function getProductIblocks(array $productList): array
1062 {
1063 $result = [
1064 'iblockElement' => [],
1065 'iblockList' => [],
1066 'skuIblockList' => []
1067 ];
1068
1069 if (empty($productList))
1070 return $result;
1071
1072 $elementIterator = Iblock\ElementTable::getList([
1073 'select' => ['ID', 'IBLOCK_ID'],
1074 'filter' => ['@ID' => $productList]
1075 ]);
1076 while ($element = $elementIterator->fetch())
1077 {
1078 $element['ID'] = (int)$element['ID'];
1079 $element['IBLOCK_ID'] = (int)$element['IBLOCK_ID'];
1080 if (!isset($result['iblockElement'][$element['IBLOCK_ID']]))
1081 $result['iblockElement'][$element['IBLOCK_ID']] = [];
1082 $result['iblockElement'][$element['IBLOCK_ID']][] = $element['ID'];
1083 }
1084 unset($element, $elementIterator);
1085 if (!empty($result['iblockElement']))
1086 {
1087 $result['iblockList'] = array_keys($result['iblockElement']);
1088
1089 $skuIterator = Catalog\CatalogIblockTable::getList([
1090 'select' => ['IBLOCK_ID', 'PRODUCT_IBLOCK_ID', 'SKU_PROPERTY_ID'],
1091 'filter' => ['@IBLOCK_ID' => $result['iblockList'], '!=PRODUCT_IBLOCK_ID' => 0]
1092 ]);
1093 while ($sku = $skuIterator->fetch())
1094 {
1095 $sku['IBLOCK_ID'] = (int)$sku['IBLOCK_ID'];
1096 $sku['PRODUCT_IBLOCK_ID'] = (int)$sku['PRODUCT_IBLOCK_ID'];
1097 $sku['SKU_PROPERTY_ID'] = (int)$sku['SKU_PROPERTY_ID'];
1098 $result['skuIblockList'][$sku['IBLOCK_ID']] = $sku;
1099 }
1100 unset($sku, $skuIterator);
1101 }
1102
1103 return $result;
1104 }
1105
1113 protected static function fillProductPropertyList(array &$entityData, array $iblockData): void
1114 {
1115 $entityData['needProperties'] = [];
1116 if (!empty($entityData['iblockProperties']) && !empty($iblockData['iblockList']))
1117 {
1118 foreach ($iblockData['iblockList'] as $iblock)
1119 {
1120 if (!empty($entityData['iblockProperties'][$iblock]))
1121 $entityData['needProperties'][$iblock] = $entityData['iblockProperties'][$iblock];
1122 }
1123 unset($iblock);
1124 }
1125 if (!empty($iblockData['skuIblockList']))
1126 {
1127 foreach ($iblockData['skuIblockList'] as $skuData)
1128 {
1129 if (!isset($entityData['needProperties'][$skuData['IBLOCK_ID']]))
1130 $entityData['needProperties'][$skuData['IBLOCK_ID']] = [];
1131 $entityData['needProperties'][$skuData['IBLOCK_ID']][] = $skuData['SKU_PROPERTY_ID'];
1132 $entityData['iblockPropertiesMap'][$skuData['IBLOCK_ID']][$skuData['SKU_PROPERTY_ID']] = 'PARENT_ID';
1133 if (!empty($entityData['iblockProperties'][$skuData['PRODUCT_IBLOCK_ID']]))
1134 $entityData['needProperties'][$skuData['PRODUCT_IBLOCK_ID']] = $entityData['iblockProperties'][$skuData['PRODUCT_IBLOCK_ID']];
1135 }
1136 unset($skuData);
1137 }
1138 }
1139
1150 protected static function convertProperties(&$productData, $propertyValues, $entityData, $iblockData): void
1151 {
1152 if (empty($productData) || !is_array($productData))
1153 return;
1154 if (empty($propertyValues) || !is_array($propertyValues))
1155 return;
1156 if (empty($entityData) || !is_array($entityData))
1157 return;
1158 if (empty($iblockData) || !is_array($iblockData))
1159 return;
1160
1161 if (empty($entityData['needProperties']) || !is_array($entityData['needProperties']))
1162 return;
1163 $propertyIblocks = array_keys($entityData['needProperties']);
1164 foreach ($propertyIblocks as $iblock)
1165 {
1166 if (empty($iblockData['iblockElement'][$iblock]))
1167 continue;
1168 $propertyMap = $entityData['iblockPropertiesMap'][$iblock];
1169 foreach ($iblockData['iblockElement'][$iblock] as $element)
1170 {
1171 if (empty($propertyValues[$element]))
1172 continue;
1173 foreach ($propertyValues[$element] as $property)
1174 {
1175 if (empty($property) || empty($property['ID']))
1176 continue;
1177 if ($property['PROPERTY_TYPE'] == Iblock\PropertyTable::TYPE_FILE)
1178 continue;
1179 $property['ID'] = (int)$property['ID'];
1180 if (empty($propertyMap[$property['ID']]))
1181 continue;
1182 $propertyKey = $propertyMap[$property['ID']];
1183 $value = '';
1184
1185 $check = false;
1186 if ($property['MULTIPLE'] == 'N')
1187 {
1188 if (!empty($property['USER_TYPE']))
1189 {
1190 switch($property['USER_TYPE'])
1191 {
1192 case 'DateTime':
1193 case 'Date':
1194 $property['VALUE'] = (string)$property['VALUE'];
1195 if ($property['VALUE'] != '')
1196 {
1197 $propertyFormat = false;
1198 if ($property['USER_TYPE'] == 'DateTime')
1199 {
1200 if (defined('FORMAT_DATETIME'))
1201 $propertyFormat = FORMAT_DATETIME;
1202 }
1203 else
1204 {
1205 if (defined('FORMAT_DATE'))
1206 $propertyFormat = FORMAT_DATE;
1207 }
1208 $intStackTimestamp = (int)$property['VALUE'];
1209 $property['VALUE'] = (
1210 $intStackTimestamp.'!' != $property['VALUE'].'!'
1211 ? (int)MakeTimeStamp($property['VALUE'], $propertyFormat)
1212 : $intStackTimestamp
1213 );
1214 }
1215 $value = $property['VALUE'];
1216 $check = true;
1217 break;
1218 }
1219 }
1220 if (!$check)
1221 {
1222 switch ($property['PROPERTY_TYPE'])
1223 {
1224 case Iblock\PropertyTable::TYPE_LIST:
1225 $property['VALUE_ENUM_ID'] = (int)$property['VALUE_ENUM_ID'];
1226 $value = ($property['VALUE_ENUM_ID'] > 0 ? $property['VALUE_ENUM_ID'] : -1);
1227 break;
1228 case Iblock\PropertyTable::TYPE_ELEMENT:
1229 case Iblock\PropertyTable::TYPE_SECTION:
1230 $property['VALUE'] = (int)$property['VALUE'];
1231 $value = ($property['VALUE'] > 0 ? $property['VALUE'] : -1);
1232 break;
1233 default:
1234 $value = $property['VALUE'];
1235 break;
1236 }
1237 }
1238 }
1239 else
1240 {
1241 $value = [];
1242 if (!empty($property['USER_TYPE']))
1243 {
1244 switch($property['USER_TYPE'])
1245 {
1246 case 'DateTime':
1247 case 'Date':
1248 if (!empty($property['VALUE']) && is_array($property['VALUE']))
1249 {
1250 $propertyFormat = false;
1251 if ($property['USER_TYPE'] == 'DateTime')
1252 {
1253 if (defined('FORMAT_DATETIME'))
1254 $propertyFormat = FORMAT_DATETIME;
1255 }
1256 else
1257 {
1258 if (defined('FORMAT_DATE'))
1259 $propertyFormat = FORMAT_DATE;
1260 }
1261 foreach ($property['VALUE'] as &$oneValue)
1262 {
1263 $oneValue = (string)$oneValue;
1264 if ('' != $oneValue)
1265 {
1266 $intStackTimestamp = (int)$oneValue;
1267 if ($intStackTimestamp.'!' != $oneValue.'!')
1268 $oneValue = (int)MakeTimeStamp($oneValue, $propertyFormat);
1269 else
1270 $oneValue = $intStackTimestamp;
1271 }
1272 $value[] = $oneValue;
1273 }
1274 unset($oneValue, $propertyFormat);
1275 }
1276 $check = true;
1277 break;
1278 }
1279 }
1280 if (!$check)
1281 {
1282 switch ($property['PROPERTY_TYPE'])
1283 {
1284 case Iblock\PropertyTable::TYPE_LIST:
1285 if (!empty($property['VALUE_ENUM_ID']) && is_array($property['VALUE_ENUM_ID']))
1286 {
1287 foreach ($property['VALUE_ENUM_ID'] as &$oneValue)
1288 {
1289 $oneValue = (int)$oneValue;
1290 if ($oneValue > 0)
1291 $value[] = $oneValue;
1292 }
1293 unset($oneValue);
1294 }
1295 if (empty($value))
1296 $value = [-1];
1297 break;
1298 case Iblock\PropertyTable::TYPE_ELEMENT:
1299 case Iblock\PropertyTable::TYPE_SECTION:
1300 if (!empty($property['VALUE']) && is_array($property['VALUE']))
1301 {
1302 foreach ($property['VALUE'] as &$oneValue)
1303 {
1304 $oneValue = (int)$oneValue;
1305 if ($oneValue > 0)
1306 $value[] = $oneValue;
1307 }
1308 unset($oneValue);
1309 }
1310 if (empty($value))
1311 $value = [-1];
1312 break;
1313 default:
1314 $value = $property['VALUE'];
1315 break;
1316 }
1317 }
1318 }
1319 $productData[$element][$propertyKey] = (is_array($value) ? $value : [$value]);
1320 }
1321 }
1322 unset($element);
1323 }
1324 unset($iblock);
1325 }
1326
1336 protected static function fillEmptyProperties(array &$propertyValues, int $iblockId, array $itemIds, array $propertyIds): void
1337 {
1338 if ($iblockId <= 0 || empty($itemIds) || empty($propertyIds))
1339 {
1340 return;
1341 }
1342 $propertyList = [];
1343 $iterator = Iblock\PropertyTable::getList([
1344 'select' => ['ID', 'PROPERTY_TYPE', 'MULTIPLE', 'USER_TYPE'],
1345 'filter' => ['=IBLOCK_ID' => $iblockId, '@ID' => $propertyIds]
1346 ]);
1347 while ($row = $iterator->fetch())
1348 {
1349 $id = (int)$row['ID'];
1350 $multiple = ($row['MULTIPLE'] == 'Y');
1351 if ($multiple)
1352 {
1353 $row = $row
1354 + [
1355 'VALUE_ENUM' => null,
1356 'VALUE_XML_ID' => null,
1357 'VALUE_SORT' => null,
1358 'VALUE' => false,
1359 'PROPERTY_VALUE_ID' => false,
1360 'DESCRIPTION' => false,
1361 '~DESCRIPTION' => false,
1362 '~VALUE' => false
1363 ];
1364 }
1365 else
1366 {
1367 $row = $row
1368 + [
1369 'VALUE_ENUM' => null,
1370 'VALUE_XML_ID' => null,
1371 'VALUE_SORT' => null,
1372 'VALUE' => '',
1373 'PROPERTY_VALUE_ID' => null,
1374 'DESCRIPTION' => '',
1375 '~DESCRIPTION' => '',
1376 '~VALUE' => '',
1377 ];
1378 }
1379 if ($row['PROPERTY_TYPE'] == Iblock\PropertyTable::TYPE_LIST)
1380 {
1381 $row['VALUE_ENUM_ID'] = ($multiple ? false : null);
1382 }
1383
1384 $propertyList[$id] = $row;
1385 }
1386 unset($row, $iterator);
1387
1388 foreach ($itemIds as $id)
1389 {
1390 if (!empty($propertyValues[$id]))
1391 {
1392 continue;
1393 }
1394 $propertyValues[$id] = $propertyList;
1395 }
1396 unset($propertyList);
1397 }
1398
1408 protected static function getParentProducts(&$productData, $entityData, $iblockData): void
1409 {
1410 if (empty($iblockData['skuIblockList']))
1411 return;
1412 if (empty($productData) || !is_array($productData))
1413 return;
1414 $parentMap = [];
1415 $parentData = [];
1416 $parentIblockData = [
1417 'iblockElement' => [],
1418 'iblockList' => []
1419 ];
1420 if (!empty($entityData['iblockFields']))
1421 {
1422 foreach ($entityData['iblockFields'] as &$value)
1423 $value = 'PARENT_'.$value;
1424 }
1425 if (array_key_exists('catalogFields', $entityData))
1426 unset($entityData['catalogFields']);
1427 foreach ($iblockData['skuIblockList'] as $skuData)
1428 {
1429 if (empty($iblockData['iblockElement'][$skuData['IBLOCK_ID']]))
1430 continue;
1431 foreach ($iblockData['iblockElement'][$skuData['IBLOCK_ID']] as $element)
1432 {
1433 if (empty($productData[$element]['PARENT_ID']))
1434 continue;
1435 $parentId = (int)(
1436 is_array($productData[$element]['PARENT_ID'])
1437 ? current($productData[$element]['PARENT_ID'])
1438 : $productData[$element]['PARENT_ID']
1439 );
1440 if ($parentId <= 0)
1441 continue;
1442 if (!isset($parentMap[$parentId]))
1443 $parentMap[$parentId] = [];
1444 $parentMap[$parentId][] = $element;
1445 $parentData[$parentId] = [];
1446 if (!isset($parentIblockData['iblockElement'][$skuData['PRODUCT_IBLOCK_ID']]))
1447 $parentIblockData['iblockElement'][$skuData['PRODUCT_IBLOCK_ID']] = [];
1448 $parentIblockData['iblockElement'][$skuData['PRODUCT_IBLOCK_ID']][] = $parentId;
1449 }
1450 unset($parentId, $element);
1451 }
1452 unset($skuData);
1453 if (empty($parentIblockData['iblockElement']))
1454 return;
1455 $parentIblockData['iblockList'] = array_keys($parentIblockData['iblockElement']);
1456
1457 self::getProductData($parentData, $entityData, $parentIblockData);
1458
1459 foreach ($parentData as $parentId => $data)
1460 {
1461 $parentSections = [];
1462 if ($entityData['sections'])
1463 {
1464 $parentSections = $data['SECTION_ID'];
1465 unset($data['SECTION_ID']);
1466 }
1467 if(!isset($parentMap[$parentId]))
1468 {
1469 continue;
1470 }
1471 foreach ($parentMap[$parentId] as $element)
1472 {
1473 $productData[$element] = array_merge($productData[$element], $data);
1474 if ($entityData['sections'])
1475 {
1476 $productData[$element]['SECTION_ID'] = (
1477 empty($productData['SECTION_ID'])
1478 ? $parentSections
1479 : array_merge($productData[$element]['SECTION_ID'], $parentSections)
1480 );
1481 }
1482 }
1483 unset($element, $parentSections);
1484 }
1485 unset($parentId, $data);
1486 }
1487
1488 protected static function loadIblockFields(array $productIds, array $fields): array
1489 {
1490 if (isset($fields['DATE_ACTIVE_FROM']))
1491 {
1492 $fields['ACTIVE_FROM'] = $fields['DATE_ACTIVE_FROM'];
1493 unset($fields['DATE_ACTIVE_FROM']);
1494 }
1495 if (isset($fields['DATE_ACTIVE_TO']))
1496 {
1497 $fields['ACTIVE_TO'] = $fields['DATE_ACTIVE_TO'];
1498 unset($fields['DATE_ACTIVE_TO']);
1499 }
1500
1501 $productData = [];
1502
1503 \CTimeZone::Disable();
1504 $elementIterator = Iblock\ElementTable::getList([
1505 'select' => array_merge(['ID'], array_keys($fields)),
1506 'filter' => ['@ID' => $productIds]
1507 ]);
1508 while ($element = $elementIterator->fetch())
1509 {
1510 $element['ID'] = (int)$element['ID'];
1511 foreach ($fields as $key => $alias)
1512 {
1513 if ($element[$key] instanceof Main\Type\DateTime)
1514 $productData[$element['ID']][$alias] = $element[$key]->getTimestamp();
1515 else
1516 $productData[$element['ID']][$alias] = $element[$key];
1517 }
1518 }
1519 \CTimeZone::Enable();
1520
1521 return $productData;
1522 }
1523
1524 protected static function loadSections(array $productIds): array
1525 {
1526 $productSection = array_fill_keys($productIds, []);
1527 $elementSectionIterator = Iblock\SectionElementTable::getList([
1528 'select' => ['*'],
1529 'filter' => ['@IBLOCK_ELEMENT_ID' => $productIds]
1530 ]);
1531 while ($elementSection = $elementSectionIterator->fetch())
1532 {
1533 $elementSection['IBLOCK_ELEMENT_ID'] = (int)$elementSection['IBLOCK_ELEMENT_ID'];
1534 $elementSection['IBLOCK_SECTION_ID'] = (int)$elementSection['IBLOCK_SECTION_ID'];
1535 $elementSection['ADDITIONAL_PROPERTY_ID'] = (int)$elementSection['ADDITIONAL_PROPERTY_ID'];
1536 if ($elementSection['ADDITIONAL_PROPERTY_ID'] > 0)
1537 continue;
1538 $productSection[$elementSection['IBLOCK_ELEMENT_ID']][$elementSection['IBLOCK_SECTION_ID']] = true;
1539 $parentSectionIterator = \CIBlockSection::GetNavChain(0, $elementSection['IBLOCK_SECTION_ID'], ['ID']);
1540 while ($parentSection = $parentSectionIterator->fetch())
1541 {
1542 $parentSection['ID'] = (int)$parentSection['ID'];
1543 $productSection[$elementSection['IBLOCK_ELEMENT_ID']][$parentSection['ID']] = true;
1544 }
1545 unset($parentSection, $parentSectionIterator);
1546 }
1547 unset($elementSection, $elementSectionIterator);
1548
1549 return $productSection;
1550 }
1551
1552 protected static function loadCatalogFields(array $productIds, array $fields): array
1553 {
1554 $productData = [];
1555
1556 $productIterator = Catalog\ProductTable::getList([
1557 'select' => array_merge(['ID'], array_keys($fields)),
1558 'filter' => ['@ID' => $productIds]
1559 ]);
1560 while ($product = $productIterator->fetch())
1561 {
1562 $product['ID'] = (int)$product['ID'];
1563 foreach ($fields as $key => $alias)
1564 {
1565 $productData[$product['ID']][$alias] = $product[$key];
1566 }
1567 }
1568
1569 return $productData;
1570 }
1571
1572 protected static function fillProperties(array &$productData, array $productIds, array $iblockData, array $entityData)
1573 {
1574 $propertyValues = array_fill_keys($productIds, []);
1575 foreach ($entityData['needProperties'] as $iblock => $propertyList)
1576 {
1577 if (empty($iblockData['iblockElement'][$iblock]))
1578 {
1579 continue;
1580 }
1581
1582 $needToLoad = array_fill_keys($iblockData['iblockElement'][$iblock], true);
1583 if(self::$productProperties)
1584 {
1585 foreach ($iblockData['iblockElement'][$iblock] as $productId)
1586 {
1587 $allExist = true;
1588 foreach ($propertyList as $prop)
1589 {
1590 $propData = self::getCachedProductProperty($productId, $prop);
1591 if (!empty($propData))
1592 {
1593 $propertyValues[$productId][$propData['ID']] = $propData;
1594 }
1595 else
1596 {
1597 $allExist = false;
1598 break;
1599 }
1600 }
1601 unset($prop);
1602 if (!$allExist)
1603 {
1604 // if property value is not exist
1605 $propertyValues[$productId] = [];
1606 }
1607 else
1608 {
1609 unset($needToLoad[$productId]);
1610 }
1611 unset($allExist);
1612 }
1613 }
1614
1615 if(!empty($needToLoad))
1616 {
1617 $needProductIds = array_keys($needToLoad);
1618 sort($needProductIds);
1619 $iblockPropertyValues = array_fill_keys($needProductIds, []);
1620
1621 \CTimeZone::Disable();
1622 foreach (array_chunk($needProductIds, 500) as $pageIds)
1623 {
1624 $filter = [
1625 'ID' => $pageIds,
1626 'IBLOCK_ID' => $iblock
1627 ];
1628
1629 \CIBlockElement::GetPropertyValuesArray(
1630 $iblockPropertyValues,
1631 $iblock,
1632 $filter,
1633 ['ID' => $propertyList],
1634 [
1635 'USE_PROPERTY_ID' => 'Y',
1636 'PROPERTY_FIELDS' => ['ID', 'PROPERTY_TYPE', 'MULTIPLE', 'USER_TYPE']
1637 ]
1638 );
1639 foreach ($iblockPropertyValues as $productId => $data)
1640 {
1641 if (!empty($data))
1642 {
1643 $propertyValues[$productId] = $data;
1644 unset($needToLoad[$productId]);
1645 }
1646 }
1647 unset($productId, $data);
1648 }
1649 unset($pageIds);
1650 \CTimeZone::Enable();
1651 unset($iblockPropertyValues, $needProductIds);
1652
1653 if (!empty($needToLoad))
1654 {
1655 self::fillEmptyProperties($propertyValues, $iblock, array_keys($needToLoad), $propertyList);
1656 }
1657 }
1658 }
1659
1660 self::convertProperties($productData, $propertyValues, $entityData, $iblockData);
1661 }
1662
1672 protected static function getProductData(&$productData, $entityData, $iblockData): void
1673 {
1674 if (!empty($iblockData['iblockElement']))
1675 {
1676 $productList = array_keys($productData);
1677 if (!empty($entityData['iblockFields']))
1678 {
1679 foreach(self::loadIblockFields($productList, $entityData['iblockFields']) as $productId => $fields)
1680 {
1681 $productData[$productId] = (
1682 empty($productData[$productId])
1683 ? $fields
1684 : array_merge($productData[$productId], $fields)
1685 );
1686 }
1687 unset($fields);
1688 }
1689 if ($entityData['sections'])
1690 {
1691 foreach(self::loadSections($productList) as $element => $sections)
1692 {
1693 $productData[$element]['SECTION_ID'] = array_keys($sections);
1694 }
1695 }
1696 if (!empty($entityData['needProperties']))
1697 {
1698 self::fillProperties($productData, $productList, $iblockData, $entityData);
1699 }
1700 if (!empty($entityData['catalogFields']))
1701 {
1702 foreach(self::loadCatalogFields($productList, $entityData['catalogFields']) as $productId => $fields)
1703 {
1704 $productData[$productId] = (
1705 empty($productData[$productId])
1706 ? $fields
1707 : array_merge($productData[$productId], $fields)
1708 );
1709 }
1710 unset($fields);
1711 }
1712 if (!empty($entityData['priceFields']) && !empty($entityData['priceData']))
1713 {
1714 foreach($entityData['priceData'] as $productId => $priceId)
1715 {
1716 $productData[$productId]['CATALOG_GROUP_ID'] = $priceId;
1717 }
1718 unset($product, $productIterator);
1719 }
1720
1721 if (!empty($iblockData['skuIblockList']))
1722 self::getParentProducts($productData, $entityData, $iblockData);
1723 }
1724 }
1725
1734 protected static function createSaleAction(&$discount, $params): void
1735 {
1736 $data = [
1737 'TYPE' => $discount['VALUE_TYPE'],
1738 'VALUE' => $discount['VALUE'],
1739 'CURRENCY' => $discount['CURRENCY'],
1740 'USE_BASE_PRICE' => $params['USE_BASE_PRICE']
1741 ];
1742 if ($discount['TYPE'] == Catalog\DiscountTable::VALUE_TYPE_PERCENT)
1743 $data['MAX_VALUE'] = $discount['MAX_VALUE'];
1744
1745 $action = '\Bitrix\Catalog\Discount\DiscountManager::applyDiscount('.$params['BASKET_ITEM'].', '.var_export($data, true).');';
1746 $discount['APPLICATION'] = 'function (&'.$params['BASKET_ITEM'].'){'.$action.'};';
1747 $discount['ACTIONS'] = $data;
1748 unset($action, $data);
1749
1750 if (self::$saleIncluded === null)
1751 self::$saleIncluded = Loader::includeModule('sale');
1752 if (!self::$saleIncluded)
1753 return;
1754
1755 $type = '';
1756 $descr = [
1757 'VALUE_ACTION' => (
1758 $discount['TYPE'] == Catalog\DiscountTable::TYPE_DISCOUNT_SAVE
1759 ? Sale\Discount\Formatter::VALUE_ACTION_CUMULATIVE
1760 : Sale\Discount\Formatter::VALUE_ACTION_DISCOUNT
1761 ),
1762 'VALUE' => $discount['VALUE']
1763 ];
1764 switch ($discount['VALUE_TYPE'])
1765 {
1766 case Catalog\DiscountTable::VALUE_TYPE_PERCENT:
1767 $type = (
1768 $discount['MAX_VALUE'] > 0
1769 ? Sale\Discount\Formatter::TYPE_LIMIT_VALUE
1770 : Sale\Discount\Formatter::TYPE_VALUE
1771 );
1772 $descr['VALUE_TYPE'] = Sale\Discount\Formatter::VALUE_TYPE_PERCENT;
1773 if ($discount['MAX_VALUE'] > 0)
1774 {
1775 $descr['LIMIT_TYPE'] = Sale\Discount\Formatter::LIMIT_MAX;
1776 $descr['LIMIT_UNIT'] = $discount['CURRENCY'];
1777 $descr['LIMIT_VALUE'] = $discount['MAX_VALUE'];
1778 }
1779 break;
1780 case Catalog\DiscountTable::VALUE_TYPE_FIX:
1781 $type = Sale\Discount\Formatter::TYPE_VALUE;
1782 $descr['VALUE_TYPE'] = Sale\Discount\Formatter::VALUE_TYPE_CURRENCY;
1783 $descr['VALUE_UNIT'] = $discount['CURRENCY'];
1784 break;
1785 case Catalog\DiscountTable::VALUE_TYPE_SALE:
1786 $type = Sale\Discount\Formatter::TYPE_FIXED;
1787 $descr['VALUE_UNIT'] = $discount['CURRENCY'];
1788 break;
1789 }
1790 $descrResult = Sale\Discount\Formatter::prepareRow($type, $descr);
1791 if ($descrResult !== null)
1792 {
1793 $discount['ACTIONS_DESCR'] = [
1794 'BASKET' => [
1795 0 => $descrResult
1796 ]
1797 ];
1798 }
1799 unset($descrResult, $descr, $type);
1800 }
1801
1802 protected static function fillProductPriceList(&$entityData, $priceIds)
1803 {
1804 $entityData['priceData'] = [];
1805 if(empty($entityData['priceFields']) || empty($priceIds))
1806 {
1807 return;
1808 }
1809
1810 $priceData = [];
1811 $priceList = Catalog\PriceTable::getList([
1812 'select' => ['PRODUCT_ID', 'CATALOG_GROUP_ID'],
1813 'filter' => ['@ID' => $priceIds],
1814 ]);
1815 while($price = $priceList->fetch())
1816 {
1817 if(!isset($priceData[$price['PRODUCT_ID']]))
1818 {
1819 $priceData[$price['PRODUCT_ID']] = [];
1820 }
1821 $priceData[$price['PRODUCT_ID']] = $price['CATALOG_GROUP_ID'];
1822 }
1823
1824 $entityData['priceData'] = $priceData;
1825 }
1826
1834 protected static function roundValue($value, string $currency): float
1835 {
1836 if (self::$saleIncluded === null)
1837 self::$saleIncluded = Loader::includeModule('sale');
1838 if (self::$saleIncluded)
1839 return Sale\Discount\Actions::roundValue($value, $currency);
1840 else
1841 return roundEx($value, CATALOG_VALUE_PRECISION);
1842 }
1843
1852 private static function getRoundResult(array $basketItem, array $roundData): array
1853 {
1854 $result = [
1855 'ROUND_RULE' => $roundData
1856 ];
1857 $result['PRICE'] = Catalog\Product\Price::roundValue(
1858 $basketItem['PRICE'],
1859 $roundData['ROUND_PRECISION'],
1860 $roundData['ROUND_TYPE']
1861 );
1862
1863 if (isset($basketItem['BASE_PRICE']))
1864 {
1865 $result['DISCOUNT_PRICE'] = $basketItem['BASE_PRICE'] - $result['PRICE'];
1866 }
1867 else
1868 {
1869 if (!isset($result['DISCOUNT_PRICE']))
1870 $result['DISCOUNT_PRICE'] = 0;
1871 $result['DISCOUNT_PRICE'] += ($basketItem['PRICE'] - $result['PRICE']);
1872 }
1873
1874 return $result;
1875 }
1876
1877 private static function getPriceDataByPriceId($priceId)
1878 {
1879 foreach(self::$preloadedPriceData as $priceData)
1880 {
1881 if($priceData['ID'] == $priceId)
1882 {
1883 return $priceData;
1884 }
1885 }
1886
1887 return null;
1888 }
1889
1890 private static function getCachedProductProperty($productId, $propertyId)
1891 {
1892 if(!isset(self::$productProperties[$productId]))
1893 {
1894 return null;
1895 }
1896
1897 foreach(self::$productProperties[$productId] as $props)
1898 {
1899 if($props['ID'] == $propertyId)
1900 {
1901 return $props;
1902 }
1903 }
1904
1905 return null;
1906 }
1907}
static catalogDiscountManager(Main\Event $event)
static prepareData(array $discount, $params=[])
static extendOrderData(Main\Event $event)
static fillProperties(array &$productData, array $productIds, array $iblockData, array $entityData)
static extendProductIdsToOffer(array $productIds)
static loadFromDatabase(int $id, array $discount)
static lastDiscountFilter(array $basketItem)
static getProductIblocks(array $productList)
static createSaleAction(&$discount, $params)
static getCacheKeyForEntityList(array $entityList)
static fillProductPropertyList(array &$entityData, array $iblockData)
static roundPrice(array $basketItem, array $roundData=[])
static roundBasket(array $basket, array $basketRoundData=[], array $order=[])
static loadCatalogFields(array $productIds, array $fields)
static calculateApplyCoupons(array $couponsList, array $basket, array $params)
static getParentProducts(&$productData, $entityData, $iblockData)
static getProductData(&$productData, $entityData, $iblockData)
static loadIblockFields(array $productIds, array $fields)
static getPriceDataByProductId(int $productId, int $catalogGroupId)
static fillProductPriceList(&$entityData, $priceIds)
static convertProperties(&$productData, $propertyValues, $entityData, $iblockData)
static applyDiscount(&$product, $discount)
static preloadPriceData(array $productIds, array $catalogGroups)
static preloadProductDataToExtendOrder(array $productIds, array $userGroups)
static roundValue($value, string $currency)
static fillEmptyProperties(array &$propertyValues, int $iblockId, array $itemIds, array $propertyIds)
static setProductPropertiesCache($productId, $props)
static loadMessages($file)
Definition loc.php:64