1C-Bitrix 25.700.0
Загрузка...
Поиск...
Не найдено
querybuilder.php
См. документацию.
1<?php
2
8
10{
11 public const ENTITY_PRODUCT = 'PRODUCT';
12 public const ENTITY_PRODUCT_USER_FIELD = 'PRODUCT_USER_FIELD';
13 public const ENTITY_PRICE = 'PRICE';
14 public const ENTITY_WARENHOUSE = 'WARENHOUSE';
15 public const ENTITY_FLAT_PRICE = 'FLAT_PRICES';
16 public const ENTITY_FLAT_WAREHNOUSE = 'FLAT_WARENHOUSES';
17 public const ENTITY_FLAT_BARCODE = 'FLAT_BARCODE';
18 public const ENTITY_OLD_PRODUCT = 'OLD_PRODUCT';
19 public const ENTITY_OLD_PRICE = 'OLD_PRICE';
20 public const ENTITY_OLD_STORE = 'OLD_STORE';
21 private const ENTITY_CATALOG_IBLOCK = 'CATALOG_IBLOCK';
22 private const ENTITY_VAT = 'VAT';
23
24 public const FIELD_ALLOWED_SELECT = 0x0001;
25 public const FIELD_ALLOWED_FILTER = 0x0002;
26 public const FIELD_ALLOWED_ORDER = 0x0004;
27 public const FIELD_ALLOWED_ALL = self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER|self::FIELD_ALLOWED_ORDER;
28
29 private const FIELD_PATTERN_OLD_STORE = '/^CATALOG_STORE_AMOUNT_([0-9]+)$/';
30 private const FIELD_PATTERN_OLD_PRICE_ROW = '/^CATALOG_GROUP_([0-9]+)$/';
31 private const FIELD_PATTERN_OLD_PRICE = '/^CATALOG_([A-Z][A-Z_]+)+_([0-9]+)$/';
32 private const FIELD_PATTERN_OLD_PRODUCT = '/^CATALOG_([A-Z][A-Z_]+)$/';
33 private const FIELD_PATTERN_FLAT_ENTITY = '/^([A-Z][A-Z_]+)$/';
34 private const FIELD_PATTERN_SEPARATE_ENTITY = '/^([A-Z][A-Z_]+)_([1-9][0-9]*)$/';
35 private const FIELD_PATTERN_PRODUCT_USER_FIELD = '/^PRODUCT_(UF_[A-Z0-9_]+)$/';
36
37 private const ENTITY_TYPE_FLAT = 0x0001;
38 private const ENTITY_TYPE_SEPARATE = 0x0002;
39
40 private const FIELD_TYPE_INT = 'int';
41 private const FIELD_TYPE_FLOAT = 'float';
42 private const FIELD_TYPE_CHAR = 'char';
43 private const FIELD_TYPE_STRING = 'string';
44
45 private static array $entityDescription = [];
46
47 private static array $entityFields = [];
48
49 private static array $options = [];
50
56 public static function makeFilter(array $filter, array $options = []): ?array
57 {
58 $query = self::prepareQuery(['filter' => $filter], $options);
59 if ($query === null)
60 {
61 return null;
62 }
63 if (empty($query['filter']) && empty($query['join']))
64 {
65 return null;
66 }
67
68 return $query;
69 }
70
76 public static function makeQuery(array $parameters, array $options = []): ?array
77 {
78 $query = self::prepareQuery($parameters, $options);
79 if ($query === null)
80 {
81 return null;
82 }
83 if (empty($query['select']) && empty($query['filter']) && empty($query['order']))
84 {
85 return null;
86 }
87
88 return $query;
89 }
90
95 public static function isValidField(string $field): bool
96 {
97 $field = strtoupper($field);
98 if ($field === '')
99 {
100 return false;
101 }
102
103 self::initEntityDescription();
104 self::initEntityFields();
105
106 $prepared = [];
107
108 if (preg_match(self::FIELD_PATTERN_OLD_STORE, $field, $prepared))
109 {
110 return true;
111 }
112 if (preg_match(self::FIELD_PATTERN_OLD_PRICE_ROW, $field, $prepared))
113 {
114 return true;
115 }
116 if (preg_match(self::FIELD_PATTERN_OLD_PRICE, $field, $prepared))
117 {
118 return true;
119 }
120 if (preg_match(self::FIELD_PATTERN_OLD_PRODUCT, $field, $prepared))
121 {
122 return true;
123 }
124 if (preg_match(self::FIELD_PATTERN_SEPARATE_ENTITY, $field, $prepared))
125 {
126 if (self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_SEPARATE))
127 {
128 return true;
129 }
130 }
131 if (preg_match(self::FIELD_PATTERN_FLAT_ENTITY, $field, $prepared))
132 {
133 if (self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_FLAT))
134 {
135 return true;
136 }
137 }
138 if (preg_match(self::FIELD_PATTERN_PRODUCT_USER_FIELD, $field, $prepared))
139 {
140 return true;
141 }
142
143 return false;
144 }
145
150 public static function isRealFilterField(string $field): bool
151 {
152 self::initEntityDescription();
153 self::initEntityFields();
154
155 $prepareField = self::parseField($field);
156 if (!self::checkPreparedField($prepareField))
157 {
158 return false;
159 }
160 if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER))
161 {
162 return false;
163 }
164
165 $description = self::getFieldDescription($prepareField['ENTITY'], $prepareField['FIELD']);
166 if (empty($description))
167 {
168 return false;
169 }
170
171 if (isset($description['PHANTOM']))
172 {
173 return false;
174 }
175
176 return true;
177 }
178
183 public static function isCatalogFilterField(string $field): bool
184 {
185 return self::isEntityFilterField(
186 $field,
187 [
188 self::ENTITY_PRODUCT => true,
189 self::ENTITY_PRODUCT_USER_FIELD => true,
190 self::ENTITY_OLD_PRODUCT => true,
191 self::ENTITY_OLD_PRICE => true,
192 self::ENTITY_PRICE => true,
193 self::ENTITY_FLAT_PRICE => true,
194 self::ENTITY_WARENHOUSE => true,
195 self::ENTITY_FLAT_WAREHNOUSE => true,
196 self::ENTITY_FLAT_BARCODE => true,
197 self::ENTITY_OLD_STORE => true,
198 ]
199 );
200 }
201
206 public static function isProductFilterField(string $field): bool
207 {
208 return self::isEntityFilterField(
209 $field,
210 [
211 self::ENTITY_PRODUCT => true,
212 self::ENTITY_PRODUCT_USER_FIELD => true,
213 self::ENTITY_OLD_PRODUCT => true,
214 ]
215 );
216 }
217
222 public static function isPriceFilterField(string $field): bool
223 {
224 return self::isEntityFilterField(
225 $field,
226 [
227 self::ENTITY_OLD_PRICE => true,
228 self::ENTITY_PRICE => true,
229 self::ENTITY_FLAT_PRICE => true,
230 ]
231 );
232 }
233
238 public static function isWarenhouseFilterField(string $field): bool
239 {
240 return self::isEntityFilterField(
241 $field,
242 [
243 self::ENTITY_WARENHOUSE => true,
244 self::ENTITY_FLAT_WAREHNOUSE => true,
245 self::ENTITY_OLD_STORE => true,
246 ]
247 );
248 }
249
256 public static function modifyFilterFromOrder(array $filter, array $order, array $options): array
257 {
259
260 self::initEntityDescription();
261 self::initEntityFields();
262
263 if (empty($order) || empty($options['QUANTITY']))
264 {
265 return $result;
266 }
267
268 foreach (array_keys($order) as $field)
269 {
270 $prepareField = self::parseField($field);
271 if (!self::checkPreparedField($prepareField))
272 {
273 continue;
274 }
275 switch ($prepareField['ENTITY'])
276 {
277 case self::ENTITY_OLD_PRICE:
278 if (
279 $prepareField['FIELD'] === 'PRICE'
280 || $prepareField['FIELD'] === 'PRICE_SCALE'
281 || $prepareField['FIELD'] === 'CURRENCY'
282 )
283 {
284 $filterFieldDescription = [
285 'ENTITY' => $prepareField['ENTITY'],
286 'FIELD' => 'SHOP_QUANTITY',
287 'ENTITY_ID' => $prepareField['ENTITY_ID'],
288 ];
289 $filterField = self::getField($filterFieldDescription, []);
290 if (!empty($filterField))
291 {
292 $filterField = $filterField['ALIAS'];
293 if (!isset($result[$filterField]))
294 {
295 $result[$filterField] = $options['QUANTITY'];
296 }
297 }
298 unset($filterField, $filterFieldDescription);
299 }
300 break;
301 case self::ENTITY_PRICE:
302 case self::ENTITY_FLAT_PRICE:
303 if (
304 $prepareField['FIELD'] === 'PRICE'
305 || $prepareField['FIELD'] === 'SCALED_PRICE'
306 || $prepareField['FIELD'] === 'CURRENCY'
307 )
308 {
309 $filterFieldDescription = [
310 'ENTITY' => $prepareField['ENTITY'],
311 'FIELD' => 'QUANTITY_RANGE_FILTER',
312 'ENTITY_ID' => $prepareField['ENTITY_ID'],
313 ];
314 $filterField = self::getField($filterFieldDescription, []);
315 if (!empty($filterField))
316 {
317 $filterField = $filterField['ALIAS'];
318 if (!isset($result[$filterField]))
319 {
320 $result[$filterField] = $options['QUANTITY'];
321 }
322 }
323 unset($filterField, $filterFieldDescription);
324 }
325 break;
326 }
327 }
328
329 return $result;
330 }
331
337 public static function convertOldField(string $field, int $useMode): ?string
338 {
339 self::initEntityDescription();
340 self::initEntityFields();
341
342 $prepareField = self::parseField($field);
343 if (!self::checkPreparedField($prepareField))
344 {
345 return null;
346 }
347 if (!self::checkAllowedAction($prepareField['ALLOWED'], $useMode))
348 {
349 return null;
350 }
351
352 $newEntity = null;
353 switch ($prepareField['ENTITY'])
354 {
355 case self::ENTITY_OLD_PRODUCT:
356 $newEntity = self::ENTITY_PRODUCT;
357 break;
358 case self::ENTITY_OLD_PRICE:
359 $newEntity = self::ENTITY_PRICE;
360 break;
361 case self::ENTITY_OLD_STORE:
362 $newEntity = self::ENTITY_WARENHOUSE;
363 break;
364 }
365 if ($newEntity === null)
366 {
367 return null;
368 }
369
370 $description = self::getFieldDescription($prepareField['ENTITY'], $prepareField['FIELD']);
371 if (empty($description))
372 {
373 return null;
374 }
375
376 if ($useMode === self::FIELD_ALLOWED_ORDER)
377 {
378 if (isset($description['ORDER_TRANSFORM']))
379 {
380 $description = self::getFieldDescription($prepareField['ENTITY'], $description['ORDER_TRANSFORM']);
381 if (empty($description))
382 {
383 return null;
384 }
385 }
386 }
387
388 $newField = [
389 'ENTITY' => $newEntity,
390 'ENTITY_ID' => $prepareField['ENTITY_ID'],
391 'FIELD' => $description['NEW_ID'] ?? $prepareField['FIELD'],
392 ];
393 unset($newEntity, $prepareField);
394
395 $description = self::getFieldDescription($newField['ENTITY'], $newField['FIELD']);
396 if (empty($description))
397 {
398 return null;
399 }
400
401 return str_replace('#ENTITY_ID#', $newField['ENTITY_ID'], $description['ALIAS']);
402 }
403
408 public static function convertOldSelect(array $select): array
409 {
410 $result = [];
411
412 if (empty($select))
413 {
414 return $result;
415 }
416
417 foreach ($select as $index => $field)
418 {
419 $newField = self::convertOldField($field, self::FIELD_ALLOWED_SELECT);
420 $result[$index] = $newField === null ? $field : $newField;
421 }
422 unset($newField, $index, $field);
423
424 return $result;
425 }
426
431 public static function convertOldFilter(array $filter): array
432 {
433 if (empty($filter))
434 {
435 return [];
436 }
437
438 $result = [];
439
440 foreach ($filter as $field => $value)
441 {
442 if (is_object($value))
443 {
444 $result[$field] = $value;
445 }
446 elseif (is_numeric($field))
447 {
448 $result[$field] =
449 is_array($value)
450 ? self::convertOldFilter($value)
451 : $value
452 ;
453 }
454 else
455 {
456 $filterItem = \CIBlock::MkOperationFilter($field);
457 if ($filterItem['FIELD'] === 'SUBQUERY')
458 {
459 if (
460 is_array($value)
461 && isset($value['FIELD'])
462 && isset($value['FILTER'])
463 && is_array($value['FILTER'])
464 )
465 {
466 $value['FILTER'] = self::convertOldFilter($value['FILTER']);
467 }
468 $result[$field] = $value;
469 }
470 else
471 {
472 $newField = self::convertOldField($filterItem['FIELD'], self::FIELD_ALLOWED_FILTER);
473 if ($newField !== null)
474 {
475 $result[$filterItem['PREFIX'] . $newField] = $value;
476 }
477 else
478 {
479 $result[$field] = $value;
480 }
481 unset($newField);
482 }
483 unset($filterItem);
484 }
485 }
486 unset($filed, $value);
487
488 return $result;
489 }
490
495 public static function convertOldOrder(array $order): array
496 {
497 $result = [];
498
499 if (empty($order))
500 return $result;
501
502 foreach ($order as $field => $direction)
503 {
504 $newField = self::convertOldField($field, self::FIELD_ALLOWED_ORDER);
505 if ($newField === null)
506 $result[$field] = $direction;
507 else
508 $result[$newField] = $direction;
509 }
510 unset($newField, $field, $direction);
511
512 return $result;
513 }
514
520 public static function getEntityFieldAliases(string $entity, array $options = []): ?array
521 {
522 if ($entity === '')
523 {
524 return null;
525 }
526
527 $entity = strtoupper($entity);
528
529 self::initEntityDescription();
530 self::initEntityFields();
531
532 if (!isset(self::$entityFields[$entity]))
533 {
534 return null;
535 }
536
537 $entityId = '';
538 if (
539 isset($options['ENTITY_ID'])
540 && (is_string($options['ENTITY_ID']) || is_int($options['ENTITY_ID']))
541 )
542 {
543 $entityId = (string)$options['ENTITY_ID'];
544 }
545
546 $result = [];
547 foreach (self::$entityFields[$entity] as $field)
548 {
549 $result[] = str_replace('#ENTITY_ID#', $entityId, $field['ALIAS']);
550 }
551 unset($field);
552
553 return $result;
554 }
555
559 private static function initEntityDescription(): void
560 {
561 if (!empty(self::$entityDescription))
562 {
563 return;
564 }
565
566 self::$entityDescription = [
567 self::ENTITY_PRODUCT => [
568 'NAME' => 'b_catalog_product',
569 'ALIAS' => 'PRD',
570 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = #ELEMENT#.ID)'
571 ],
572 self::ENTITY_PRODUCT_USER_FIELD => [
573 'EXTERNAL' => true,
574 'HANDLER' => [__CLASS__, 'handleProductUserFields'],
575 ],
576 self::ENTITY_PRICE => [
577 'NAME' => 'b_catalog_price',
578 'ALIAS' => 'PRC_#ENTITY_ID#',
579 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.CATALOG_GROUP_ID = #ENTITY_ID##JOIN_MODIFY#)'
580 ],
581 self::ENTITY_WARENHOUSE => [
582 'NAME' => 'b_catalog_store_product',
583 'ALIAS' => 'WHS_#ENTITY_ID#',
584 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.STORE_ID = #ENTITY_ID#)'
585 ],
586 self::ENTITY_FLAT_PRICE => [
587 'NAME' => 'b_catalog_price',
588 'ALIAS' => 'PRC_FT',
589 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID#JOIN_MODIFY#)'
590 ],
591 self::ENTITY_FLAT_WAREHNOUSE => [
592 'NAME' => 'b_catalog_store_product',
593 'ALIAS' => 'WHS_FT',
594 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID)'
595 ],
596 self::ENTITY_FLAT_BARCODE => [
597 'NAME' => 'b_catalog_store_barcode',
598 'ALIAS' => 'BRC_FT',
599 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID)'
600 ],
601 self::ENTITY_OLD_PRODUCT => [
602 'NAME' => 'b_catalog_product',
603 'ALIAS' => 'CAT_PR',
604 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = #ELEMENT#.ID)'
605 ],
606 self::ENTITY_OLD_PRICE => [
607 'NAME' => 'b_catalog_price',
608 'ALIAS' => 'CAT_P#ENTITY_ID#',
609 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.CATALOG_GROUP_ID = #ENTITY_ID##JOIN_MODIFY#)'
610 ],
611 self::ENTITY_OLD_STORE => [
612 'NAME' => 'b_catalog_store_product',
613 'ALIAS' => 'CAT_SP#ENTITY_ID#',
614 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.PRODUCT_ID = #ELEMENT#.ID and #ALIAS#.STORE_ID = #ENTITY_ID#)'
615 ],
616 self::ENTITY_CATALOG_IBLOCK => [
617 'NAME' => 'b_catalog_iblock',
618 'ALIAS' => 'CAT_IB',
619 'JOIN' => 'left join #NAME# as #ALIAS# on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and #ALIAS#.IBLOCK_ID = #ELEMENT#.IBLOCK_ID)'
620 ],
621 self::ENTITY_VAT => [
622 'NAME' => 'b_catalog_vat',
623 'ALIAS' => 'CAT_VAT',
624 'JOIN' => 'left join #NAME# as #ALIAS# on (#ALIAS#.ID = CASE WHEN (CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0) THEN CAT_IB.VAT_ID ELSE CAT_PR.VAT_ID END)',
625 'RELATION' => [self::ENTITY_CATALOG_IBLOCK]
626 ],
627 ];
628 }
629
633 private static function initEntityFields(): void
634 {
635 if (!empty(self::$entityFields))
636 {
637 return;
638 }
639
640 self::$entityFields = [
641 self::ENTITY_PRODUCT => self::getProductFields(),
642 self::ENTITY_PRODUCT_USER_FIELD => self::getProductUserFields(),
643 self::ENTITY_PRICE => self::getPriceFields(),
644 self::ENTITY_WARENHOUSE => self::getWarenhouseFields(),
645 self::ENTITY_FLAT_PRICE => self::getFlatPriceFields(),
646 self::ENTITY_FLAT_WAREHNOUSE => self::getFlatWarenhouseFields(),
647 self::ENTITY_FLAT_BARCODE => self::getFlatBarcodeFields(),
648 self::ENTITY_OLD_PRODUCT => self::getOldProductFields(),
649 self::ENTITY_OLD_PRICE => self::getOldPriceFields(),
650 self::ENTITY_OLD_STORE => self::getOldStoreFields(),
651 self::ENTITY_VAT => self::getVatFields(),
652 ];
653 }
654
658 private static function getProductFields(): array
659 {
660 return [
661 'TYPE' => [
662 'NAME' => 'TYPE',
663 'ALIAS' => 'TYPE',
664 'TYPE' => self::FIELD_TYPE_INT,
665 'ALLOWED' => self::FIELD_ALLOWED_ALL,
666 ],
667 'AVAILABLE' => [
668 'NAME' => 'AVAILABLE',
669 'ALIAS' => 'AVAILABLE',
670 'TYPE' => self::FIELD_TYPE_CHAR,
671 'ALLOWED' => self::FIELD_ALLOWED_ALL,
672 'ORDER_DEFAULT' => 'DESC',
673 ],
674 'BUNDLE' => [
675 'NAME' => 'BUNDLE',
676 'ALIAS' => 'BUNDLE',
677 'TYPE' => self::FIELD_TYPE_CHAR,
678 'ALLOWED' => self::FIELD_ALLOWED_ALL,
679 ],
680 'QUANTITY' => [
681 'NAME' => 'QUANTITY',
682 'ALIAS' => 'QUANTITY',
683 'TYPE' => self::FIELD_TYPE_FLOAT,
684 'ALLOWED' => self::FIELD_ALLOWED_ALL,
685 ],
686 'QUANTITY_RESERVED' => [
687 'NAME' => 'QUANTITY_RESERVED',
688 'ALIAS' => 'QUANTITY_RESERVED',
689 'TYPE' => self::FIELD_TYPE_FLOAT,
690 'ALLOWED' => self::FIELD_ALLOWED_ALL,
691 ],
692 'QUANTITY_TRACE' => [
693 'NAME' => 'QUANTITY_TRACE',
694 'ALIAS' => 'QUANTITY_TRACE',
695 'TYPE' => self::FIELD_TYPE_CHAR,
696 'ALLOWED' => self::FIELD_ALLOWED_ALL,
697 'SELECT_EXPRESSION' => [__CLASS__, 'selectQuantityTrace'],
698 'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterQuantityTrace'],
699 ],
700 'QUANTITY_TRACE_RAW' => [
701 'NAME' => 'QUANTITY_TRACE',
702 'ALIAS' => 'QUANTITY_TRACE_RAW',
703 'TYPE' => self::FIELD_TYPE_CHAR,
704 'ALLOWED' => self::FIELD_ALLOWED_ALL,
705 ],
706 'CAN_BUY_ZERO' => [
707 'NAME' => 'CAN_BUY_ZERO',
708 'ALIAS' => 'CAN_BUY_ZERO',
709 'TYPE' => self::FIELD_TYPE_CHAR,
710 'ALLOWED' => self::FIELD_ALLOWED_ALL,
711 'SELECT_EXPRESSION' => [__CLASS__, 'selectCanBuyZero'],
712 'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterCanBuyZero'],
713 ],
714 'CAN_BUY_ZERO_RAW' => [
715 'NAME' => 'CAN_BUY_ZERO',
716 'ALIAS' => 'CAN_BUY_ZERO_RAW',
717 'TYPE' => self::FIELD_TYPE_CHAR,
718 'ALLOWED' => self::FIELD_ALLOWED_ALL,
719 ],
720 'SUBSCRIBE' => [
721 'NAME' => 'SUBSCRIBE',
722 'ALIAS' => 'SUBSCRIBE',
723 'TYPE' => self::FIELD_TYPE_CHAR,
724 'ALLOWED' => self::FIELD_ALLOWED_ALL,
725 'SELECT_EXPRESSION' => [__CLASS__, 'selectSubscribe'],
726 'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterSubscribe'],
727 ],
728 'SUBSCRIBE_RAW' => [
729 'NAME' => 'SUBSCRIBE',
730 'ALIAS' => 'SUBSCRIBE_RAW',
731 'TYPE' => self::FIELD_TYPE_CHAR,
732 'ALLOWED' => self::FIELD_ALLOWED_ALL,
733 ],
734 'VAT_ID' => [
735 'NAME' => 'VAT_ID',
736 'ALIAS' => 'VAT_ID',
737 'TYPE' => self::FIELD_TYPE_INT,
738 'ALLOWED' => self::FIELD_ALLOWED_ALL,
739 ],
740 'VAT_INCLUDED' => [
741 'NAME' => 'VAT_INCLUDED',
742 'ALIAS' => 'VAT_INCLUDED',
743 'TYPE' => self::FIELD_TYPE_CHAR,
744 'ALLOWED' => self::FIELD_ALLOWED_ALL,
745 ],
746 'PURCHASING_PRICE' => [
747 'NAME' => 'PURCHASING_PRICE',
748 'ALIAS' => 'PURCHASING_PRICE',
749 'TYPE' => self::FIELD_TYPE_FLOAT,
750 'ALLOWED' => self::FIELD_ALLOWED_ALL,
751 ],
752 'PURCHASING_CURRENCY' => [
753 'NAME' => 'PURCHASING_CURRENCY',
754 'ALIAS' => 'PURCHASING_CURRENCY',
755 'TYPE' => self::FIELD_TYPE_CHAR,
756 'ALLOWED' => self::FIELD_ALLOWED_ALL,
757 ],
758 'BARCODE_MULTI' => [
759 'NAME' => 'BARCODE_MULTI',
760 'ALIAS' => 'BARCODE_MULTI',
761 'TYPE' => self::FIELD_TYPE_CHAR,
762 'ALLOWED' => self::FIELD_ALLOWED_ALL,
763 ],
764 'WEIGHT' => [
765 'NAME' => 'WEIGHT',
766 'ALIAS' => 'WEIGHT',
767 'TYPE' => self::FIELD_TYPE_FLOAT,
768 'ALLOWED' => self::FIELD_ALLOWED_ALL,
769 ],
770 'WIDTH' => [
771 'NAME' => 'WIDTH',
772 'ALIAS' => 'WIDTH',
773 'TYPE' => self::FIELD_TYPE_FLOAT,
774 'ALLOWED' => self::FIELD_ALLOWED_ALL,
775 ],
776 'LENGTH' => [
777 'NAME' => 'LENGTH',
778 'ALIAS' => 'LENGTH',
779 'TYPE' => self::FIELD_TYPE_FLOAT,
780 'ALLOWED' => self::FIELD_ALLOWED_ALL,
781 ],
782 'HEIGHT' => [
783 'NAME' => 'HEIGHT',
784 'ALIAS' => 'HEIGHT',
785 'TYPE' => self::FIELD_TYPE_FLOAT,
786 'ALLOWED' => self::FIELD_ALLOWED_ALL,
787 ],
788 'MEASURE' => [
789 'NAME' => 'MEASURE',
790 'ALIAS' => 'MEASURE',
791 'TYPE' => self::FIELD_TYPE_INT,
792 'ALLOWED' => self::FIELD_ALLOWED_ALL,
793 ],
794 'PAYMENT_TYPE' => [
795 'NAME' => 'PRICE_TYPE',
796 'ALIAS' => 'PAYMENT_TYPE',
797 'TYPE' => self::FIELD_TYPE_CHAR,
798 'ALLOWED' => self::FIELD_ALLOWED_ALL,
799 ],
800 'RECUR_SCHEME_LENGTH' => [
801 'NAME' => 'RECUR_SCHEME_LENGTH',
802 'ALIAS' => 'RECUR_SCHEME_LENGTH',
803 'TYPE' => self::FIELD_TYPE_INT,
804 'ALLOWED' => self::FIELD_ALLOWED_ALL,
805 ],
806 'RECUR_SCHEME_TYPE' => [
807 'NAME' => 'RECUR_SCHEME_TYPE',
808 'ALIAS' => 'RECUR_SCHEME_TYPE',
809 'TYPE' => self::FIELD_TYPE_CHAR,
810 'ALLOWED' => self::FIELD_ALLOWED_ALL,
811 ],
812 'TRIAL_PRICE_ID' => [
813 'NAME' => 'TRIAL_PRICE_ID',
814 'ALIAS' => 'TRIAL_PRICE_ID',
815 'TYPE' => self::FIELD_TYPE_INT,
816 'ALLOWED' => self::FIELD_ALLOWED_ALL,
817 ],
818 'WITHOUT_ORDER' => [
819 'NAME' => 'WITHOUT_ORDER',
820 'ALIAS' => 'WITHOUT_ORDER',
821 'TYPE' => self::FIELD_TYPE_CHAR,
822 'ALLOWED' => self::FIELD_ALLOWED_ALL,
823 ],
824 ];
825 }
826
830 private static function getProductUserFields(): array
831 {
832 $result = [];
833
834 $iterator = Main\UserFieldTable::getList([
835 'select' => [
836 'ID',
837 'ENTITY_ID',
838 'FIELD_NAME',
839 'USER_TYPE_ID',
840 'XML_ID',
841 'SORT',
842 'MULTIPLE',
843 ],
844 'filter' => [
845 '=ENTITY_ID' => Bitrix\Catalog\ProductTable::getUfId(),
846 ],
847 'order' => [
848 'SORT' => 'ASC',
849 'ID' => 'ASC',
850 ],
851 'cache' => [
852 'ttl' => 86400,
853 ],
854 ]);
855 while ($row = $iterator->fetch())
856 {
857 if (!self::isValidProductUserField($row))
858 {
859 continue;
860 }
861 $item = [
862 'NAME' => $row['FIELD_NAME'],
863 'ALIAS' => 'PRODUCT_'.$row['FIELD_NAME'],
864 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
865 ];
866
867 $result[$row['FIELD_NAME']] = $item;
868 }
869 unset(
870 $row,
871 $iterator,
872 );
873
874 return $result;
875 }
876
880 private static function getPriceFields(): array
881 {
882 return [
883 'PRICE' => [
884 'NAME' => 'PRICE',
885 'ALIAS' => 'PRICE_#ENTITY_ID#',
886 'TYPE' => self::FIELD_TYPE_FLOAT,
887 'ALLOWED' => self::FIELD_ALLOWED_ALL,
888 'ORDER_NULLABLE' => true,
889 ],
890 'CURRENCY' => [
891 'NAME' => 'CURRENCY',
892 'ALIAS' => 'CURRENCY_#ENTITY_ID#',
893 'TYPE' => self::FIELD_TYPE_CHAR,
894 'ALLOWED' => self::FIELD_ALLOWED_ALL,
895 'ORDER_NULLABLE' => true,
896 ],
897 'QUANTITY_FROM' => [
898 'NAME' => 'QUANTITY_FROM',
899 'ALIAS' => 'QUANTITY_FROM_#ENTITY_ID#',
900 'TYPE' => self::FIELD_TYPE_INT,
901 'ALLOWED' => self::FIELD_ALLOWED_ALL,
902 'ORDER_NULLABLE' => true,
903 ],
904 'QUANTITY_TO' => [
905 'NAME' => 'QUANTITY_TO',
906 'ALIAS' => 'QUANTITY_TO_#ENTITY_ID#',
907 'TYPE' => self::FIELD_TYPE_INT,
908 'ALLOWED' => self::FIELD_ALLOWED_ALL,
909 'ORDER_NULLABLE' => true,
910 ],
911 'SCALED_PRICE' => [
912 'NAME' => 'PRICE_SCALE',
913 'ALIAS' => 'SCALED_PRICE_#ENTITY_ID#',
914 'TYPE' => self::FIELD_TYPE_FLOAT,
915 'ALLOWED' => self::FIELD_ALLOWED_ALL,
916 'ORDER_NULLABLE' => true,
917 ],
918 'EXTRA_ID' => [
919 'NAME' => 'EXTRA_ID',
920 'ALIAS' => 'EXTRA_ID_#ENTITY_ID#',
921 'TYPE' => self::FIELD_TYPE_INT,
922 'ALLOWED' => self::FIELD_ALLOWED_ALL,
923 ],
924 'DEFAULT_PRICE_FILTER' => [
925 'PHANTOM' => true,
926 'NAME' => null,
927 'ALIAS' => 'DEFAULT_PRICE_FILTER_#ENTITY_ID#',
928 'TYPE' => null,
929 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
930 'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
931 ],
932 'QUANTITY_RANGE_FILTER' => [
933 'PHANTOM' => true,
934 'NAME' => null,
935 'ALIAS' => 'QUANTITY_RANGE_FILTER_#ENTITY_ID#',
936 'TYPE' => null,
937 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
938 'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
939 ],
940 'CURRENCY_FOR_SCALE' => [
941 'PHANTOM' => true,
942 'NAME' => null,
943 'ALIAS' => 'CURRENCY_FOR_SCALE_#ENTITY_ID#',
944 'TYPE' => null,
945 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
946 'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale'],
947 ],
948 ];
949 }
950
954 private static function getFlatPriceFields(): array
955 {
956 return [
957 'PRICE_TYPE' => [
958 'NAME' => 'CATALOG_GROUP_ID',
959 'ALIAS' => 'PRICE_TYPE',
960 'TYPE' => self::FIELD_TYPE_INT,
961 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
962 ],
963 'PRICE' => [
964 'NAME' => 'PRICE',
965 'ALIAS' => 'PRICE',
966 'TYPE' => self::FIELD_TYPE_FLOAT,
967 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
968 'ORDER_NULLABLE' => true,
969 ],
970 'CURRENCY' => [
971 'NAME' => 'CURRENCY',
972 'ALIAS' => 'CURRENCY',
973 'TYPE' => self::FIELD_TYPE_CHAR,
974 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
975 'ORDER_NULLABLE' => true,
976 ],
977 'QUANTITY_FROM' => [
978 'NAME' => 'QUANTITY_FROM',
979 'ALIAS' => 'QUANTITY_FROM',
980 'TYPE' => self::FIELD_TYPE_INT,
981 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
982 'ORDER_NULLABLE' => true,
983 ],
984 'QUANTITY_TO' => [
985 'NAME' => 'QUANTITY_TO',
986 'ALIAS' => 'QUANTITY_TO',
987 'TYPE' => self::FIELD_TYPE_INT,
988 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
989 'ORDER_NULLABLE' => true,
990 ],
991 'SCALED_PRICE' => [
992 'NAME' => 'PRICE_SCALE',
993 'ALIAS' => 'SCALED_PRICE',
994 'TYPE' => self::FIELD_TYPE_FLOAT,
995 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
996 'ORDER_NULLABLE' => true,
997 ],
998 'EXTRA_ID' => [
999 'NAME' => 'EXTRA_ID',
1000 'ALIAS' => 'EXTRA_ID',
1001 'TYPE' => self::FIELD_TYPE_INT,
1002 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1003 ],
1004 'DEFAULT_PRICE_FILTER' => [
1005 'PHANTOM' => true,
1006 'NAME' => null,
1007 'ALIAS' => 'DEFAULT_PRICE_FILTER',
1008 'TYPE' => null,
1009 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1010 'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
1011 ],
1012 'QUANTITY_RANGE_FILTER' => [
1013 'PHANTOM' => true,
1014 'NAME' => null,
1015 'ALIAS' => 'QUANTITY_RANGE_FILTER',
1016 'TYPE' => null,
1017 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1018 'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
1019 ],
1020 'CURRENCY_FOR_SCALE' => [
1021 'PHANTOM' => true,
1022 'NAME' => null,
1023 'ALIAS' => 'CURRENCY_FOR_SCALE',
1024 'TYPE' => null,
1025 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1026 'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale'],
1027 ],
1028 ];
1029 }
1030
1034 private static function getWarenhouseFields(): array
1035 {
1036 return [
1037 'STORE_AMOUNT' => [
1038 'NAME' => 'AMOUNT',
1039 'ALIAS' => 'STORE_AMOUNT_#ENTITY_ID#',
1040 'TYPE' => self::FIELD_TYPE_FLOAT,
1041 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1042 'ORDER_NULLABLE' => true,
1043 ],
1044 ];
1045 }
1046
1050 private static function getFlatWarenhouseFields(): array
1051 {
1052 return [
1053 'STORE_NUMBER' => [
1054 'NAME' => 'STORE_ID',
1055 'ALIAS' => 'STORE_NUMBER',
1056 'TYPE' => self::FIELD_TYPE_INT,
1057 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1058 ],
1059 'STORE_AMOUNT' => [
1060 'NAME' => 'AMOUNT',
1061 'ALIAS' => 'STORE_AMOUNT',
1062 'TYPE' => self::FIELD_TYPE_FLOAT,
1063 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1064 'ORDER_NULLABLE' => true,
1065 ],
1066 ];
1067 }
1068
1072 private static function getFlatBarcodeFields(): array
1073 {
1074 return [
1075 'PRODUCT_BARCODE' => [
1076 'NAME' => 'BARCODE',
1077 'ALIAS' => 'PRODUCT_BARCODE',
1078 'TYPE' => self::FIELD_TYPE_STRING,
1079 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1080 ],
1081 'PRODUCT_BARCODE_STORE' => [
1082 'NAME' => 'STORE_ID',
1083 'ALIAS' => 'PRODUCT_BARCODE_STORE',
1084 'TYPE' => self::FIELD_TYPE_INT,
1085 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1086 ],
1087 'PRODUCT_BARCODE_ORDER' => [
1088 'NAME' => 'ORDER_ID',
1089 'ALIAS' => 'PRODUCT_BARCODE_ORDER',
1090 'TYPE' => self::FIELD_TYPE_INT,
1091 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1092 ],
1093 ];
1094 }
1095
1099 private static function getOldProductFields(): array
1100 {
1101 return [
1102 'QUANTITY' => [
1103 'NAME' => 'QUANTITY',
1104 'ALIAS' => 'CATALOG_QUANTITY',
1105 'TYPE' => self::FIELD_TYPE_FLOAT,
1106 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1107 ],
1108 'QUANTITY_TRACE' => [
1109 'NAME' => 'QUANTITY_TRACE',
1110 'ALIAS' => 'CATALOG_QUANTITY_TRACE',
1111 'TYPE' => self::FIELD_TYPE_CHAR,
1112 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1113 'SELECT_EXPRESSION' => [__CLASS__, 'selectQuantityTrace'],
1114 ],
1115 'QUANTITY_TRACE_ORIG' => [
1116 'NAME' => 'QUANTITY_TRACE',
1117 'ALIAS' => 'CATALOG_QUANTITY_TRACE_ORIG',
1118 'TYPE' => self::FIELD_TYPE_CHAR,
1119 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1120 'NEW_ID' => 'QUANTITY_TRACE_RAW',
1121 ],
1122 'WEIGHT' => [
1123 'NAME' => 'WEIGHT',
1124 'ALIAS' => 'CATALOG_WEIGHT',
1125 'TYPE' => self::FIELD_TYPE_FLOAT,
1126 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1127 ],
1128 'VAT_ID' => [
1129 'NAME' => 'VAT_ID',
1130 'ALIAS' => 'CATALOG_VAT_ID',
1131 'TYPE' => self::FIELD_TYPE_INT,
1132 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1133 ],
1134 'VAT_INCLUDED' => [
1135 'NAME' => 'VAT_INCLUDED',
1136 'ALIAS' => 'CATALOG_VAT_INCLUDED',
1137 'TYPE' => self::FIELD_TYPE_CHAR,
1138 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1139 ],
1140 'CAN_BUY_ZERO' => [
1141 'NAME' => 'CAN_BUY_ZERO',
1142 'ALIAS' => 'CATALOG_CAN_BUY_ZERO',
1143 'TYPE' => self::FIELD_TYPE_CHAR,
1144 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1145 'SELECT_EXPRESSION' => [__CLASS__, 'selectCanBuyZero'],
1146 ],
1147 'CAN_BUY_ZERO_ORIG' => [
1148 'NAME' => 'CAN_BUY_ZERO',
1149 'ALIAS' => 'CATALOG_CAN_BUY_ZERO_ORIG',
1150 'TYPE' => self::FIELD_TYPE_CHAR,
1151 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1152 'NEW_ID' => 'CAN_BUY_ZERO_RAW',
1153 ],
1154 'PURCHASING_PRICE' => [
1155 'NAME' => 'PURCHASING_PRICE',
1156 'ALIAS' => 'CATALOG_PURCHASING_PRICE',
1157 'TYPE' => self::FIELD_TYPE_FLOAT,
1158 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1159 ],
1160 'PURCHASING_CURRENCY' => [
1161 'NAME' => 'PURCHASING_CURRENCY',
1162 'ALIAS' => 'CATALOG_PURCHASING_CURRENCY',
1163 'TYPE' => self::FIELD_TYPE_CHAR,
1164 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1165 ],
1166 'QUANTITY_RESERVED' => [
1167 'NAME' => 'QUANTITY_RESERVED',
1168 'ALIAS' => 'CATALOG_QUANTITY_RESERVED',
1169 'TYPE' => self::FIELD_TYPE_FLOAT,
1170 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1171 ],
1172 'SUBSCRIBE' => [
1173 'NAME' => 'SUBSCRIBE',
1174 'ALIAS' => 'CATALOG_SUBSCRIBE',
1175 'TYPE' => self::FIELD_TYPE_CHAR,
1176 'ALLOWED' => self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER,
1177 'SELECT_EXPRESSION' => [__CLASS__, 'selectSubscribe'],
1178 'FILTER_PREPARE_VALUE_EXPRESSION' => [__CLASS__, 'prepareFilterSubscribe'],
1179 ],
1180 'SUBSCRIBE_ORIG' => [
1181 'NAME' => 'SUBSCRIBE',
1182 'ALIAS' => 'CATALOG_SUBSCRIBE_ORIG',
1183 'TYPE' => self::FIELD_TYPE_CHAR,
1184 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1185 'NEW_ID' => 'SUBSCRIBE_RAW',
1186 ],
1187 'WIDTH' => [
1188 'NAME' => 'WIDTH',
1189 'ALIAS' => 'CATALOG_WIDTH',
1190 'TYPE' => self::FIELD_TYPE_FLOAT,
1191 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1192 ],
1193 'LENGTH' => [
1194 'NAME' => 'LENGTH',
1195 'ALIAS' => 'CATALOG_LENGTH',
1196 'TYPE' => self::FIELD_TYPE_FLOAT,
1197 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1198 ],
1199 'HEIGHT' => [
1200 'NAME' => 'HEIGHT',
1201 'ALIAS' => 'CATALOG_HEIGHT',
1202 'TYPE' => self::FIELD_TYPE_FLOAT,
1203 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1204 ],
1205 'MEASURE' => [
1206 'NAME' => 'MEASURE',
1207 'ALIAS' => 'CATALOG_MEASURE',
1208 'TYPE' => self::FIELD_TYPE_INT,
1209 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1210 ],
1211 'TYPE' => [
1212 'NAME' => 'TYPE',
1213 'ALIAS' => 'CATALOG_TYPE',
1214 'TYPE' => self::FIELD_TYPE_INT,
1215 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1216 ],
1217 'AVAILABLE' => [
1218 'NAME' => 'AVAILABLE',
1219 'ALIAS' => 'CATALOG_AVAILABLE',
1220 'TYPE' => self::FIELD_TYPE_CHAR,
1221 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1222 'ORDER_DEFAULT' => 'DESC',
1223 ],
1224 'BUNDLE' => [
1225 'NAME' => 'BUNDLE',
1226 'ALIAS' => 'CATALOG_BUNDLE',
1227 'TYPE' => self::FIELD_TYPE_CHAR,
1228 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1229 ],
1230 'PRICE_TYPE' => [
1231 'NAME' => 'PRICE_TYPE',
1232 'ALIAS' => 'CATALOG_PRICE_TYPE',
1233 'TYPE' => self::FIELD_TYPE_CHAR,
1234 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1235 'NEW_ID' => 'PAYMENT_TYPE',
1236 ],
1237 'RECUR_SCHEME_LENGTH' => [
1238 'NAME' => 'RECUR_SCHEME_LENGTH',
1239 'ALIAS' => 'CATALOG_RECUR_SCHEME_LENGTH',
1240 'TYPE' => self::FIELD_TYPE_INT,
1241 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1242 ],
1243 'RECUR_SCHEME_TYPE' => [
1244 'NAME' => 'RECUR_SCHEME_TYPE',
1245 'ALIAS' => 'CATALOG_RECUR_SCHEME_TYPE',
1246 'TYPE' => self::FIELD_TYPE_CHAR,
1247 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1248 ],
1249 'TRIAL_PRICE_ID' => [
1250 'NAME' => 'TRIAL_PRICE_ID',
1251 'ALIAS' => 'CATALOG_TRIAL_PRICE_ID',
1252 'TYPE' => self::FIELD_TYPE_INT,
1253 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1254 ],
1255 'WITHOUT_ORDER' => [
1256 'NAME' => 'WITHOUT_ORDER',
1257 'ALIAS' => 'CATALOG_WITHOUT_ORDER',
1258 'TYPE' => self::FIELD_TYPE_CHAR,
1259 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1260 ],
1261 'SELECT_BEST_PRICE' => [
1262 'NAME' => 'SELECT_BEST_PRICE',
1263 'ALIAS' => 'CATALOG_SELECT_BEST_PRICE',
1264 'TYPE' => self::FIELD_TYPE_CHAR,
1265 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1266 ],
1267 'NEGATIVE_AMOUNT_TRACE' => [
1268 'NAME' => 'NEGATIVE_AMOUNT_TRACE',
1269 'ALIAS' => 'CATALOG_NEGATIVE_AMOUNT_TRACE',
1270 'TYPE' => self::FIELD_TYPE_CHAR,
1271 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1272 'SELECT_EXPRESSION' => [__CLASS__, 'selectNegativeAmountTrace'],
1273 ],
1274 'NEGATIVE_AMOUNT_TRACE_ORIG' => [
1275 'NAME' => 'NEGATIVE_AMOUNT_TRACE',
1276 'ALIAS' => 'CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG',
1277 'TYPE' => self::FIELD_TYPE_CHAR,
1278 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1279 ],
1280 ];
1281 }
1282
1286 private static function getOldPriceFields(): array
1287 {
1288 return [
1289 'ID' => [
1290 'NAME' => 'ID',
1291 'ALIAS' => 'CATALOG_PRICE_ID_#ENTITY_ID#',
1292 'TYPE' => self::FIELD_TYPE_INT,
1293 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1294 ],
1295 'PRODUCT_ID' => [
1296 'NAME' => 'PRODUCT_ID',
1297 'ALIAS' => null,
1298 'TYPE' => self::FIELD_TYPE_INT,
1299 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1300 ],
1301 'CATALOG_GROUP_ID' => [
1302 'NAME' => 'CATALOG_GROUP_ID',
1303 'ALIAS' => 'CATALOG_GROUP_ID_#ENTITY_ID#',
1304 'TYPE' => self::FIELD_TYPE_INT,
1305 'ALLOWED' => self::FIELD_ALLOWED_SELECT|self::FIELD_ALLOWED_FILTER,
1306 ],
1307 'PRICE' => [
1308 'NAME' => 'PRICE',
1309 'ALIAS' => 'CATALOG_PRICE_#ENTITY_ID#',
1310 'TYPE' => self::FIELD_TYPE_FLOAT,
1311 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1312 'ORDER_TRANSFORM' => 'PRICE_SCALE',
1313 ],
1314 'CURRENCY' => [
1315 'NAME' => 'CURRENCY',
1316 'ALIAS' => 'CATALOG_CURRENCY_#ENTITY_ID#',
1317 'TYPE' => self::FIELD_TYPE_CHAR,
1318 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1319 'ORDER_NULLABLE' => true,
1320 ],
1321 'PRICE_SCALE' => [
1322 'NAME' => 'PRICE_SCALE',
1323 'ALIAS' => null,
1324 'TYPE' => self::FIELD_TYPE_FLOAT,
1325 'ALLOWED' => self::FIELD_ALLOWED_ORDER|self::FIELD_ALLOWED_FILTER,
1326 'ORDER_NULLABLE' => true,
1327 'NEW_ID' => 'SCALED_PRICE',
1328 ],
1329 'QUANTITY_FROM' => [
1330 'NAME' => 'QUANTITY_FROM',
1331 'ALIAS' => 'CATALOG_QUANTITY_FROM_#ENTITY_ID#',
1332 'TYPE' => self::FIELD_TYPE_INT,
1333 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1334 ],
1335 'QUANTITY_TO' => [
1336 'NAME' => 'QUANTITY_TO',
1337 'ALIAS' => 'CATALOG_QUANTITY_TO_#ENTITY_ID#',
1338 'TYPE' => self::FIELD_TYPE_INT,
1339 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1340 ],
1341 'EXTRA_ID' => [
1342 'NAME' => 'EXTRA_ID',
1343 'ALIAS' => 'CATALOG_EXTRA_ID_#ENTITY_ID#',
1344 'TYPE' => self::FIELD_TYPE_INT,
1345 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1346 ],
1347 'CATALOG_GROUP_NAME' => [
1348 'NAME' => null,
1349 'ALIAS' => 'CATALOG_GROUP_NAME_#ENTITY_ID#',
1350 'TYPE' => self::FIELD_TYPE_STRING,
1351 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1352 'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeName'],
1353 ],
1354 'CATALOG_CAN_ACCESS' => [
1355 'NAME' => null,
1356 'ALIAS' => 'CATALOG_CAN_ACCESS_#ENTITY_ID#',
1357 'TYPE' => self::FIELD_TYPE_CHAR,
1358 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1359 'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeAllowedView'],
1360 ],
1361 'CATALOG_CAN_BUY' => [
1362 'NAME' => null,
1363 'ALIAS' => 'CATALOG_CAN_BUY_#ENTITY_ID#',
1364 'TYPE' => self::FIELD_TYPE_CHAR,
1365 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1366 'SELECT_EXPRESSION' => [__CLASS__, 'selectPriceTypeAllowedBuy'],
1367 ],
1368 'CURRENCY_SCALE' => [
1369 'PHANTOM' => true,
1370 'NAME' => null,
1371 'ALIAS' => 'CATALOG_CURRENCY_SCALE_#ENTITY_ID#',
1372 'TYPE' => null,
1373 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1374 'FILTER_MODIFY_EXPRESSION' => [__CLASS__, 'filterModifierCurrencyScale'],
1375 'NEW_ID' => 'CURRENCY_FOR_SCALE',
1376 ],
1377 'SHOP_QUANTITY' => [
1378 'PHANTOM' => true,
1379 'NAME' => null,
1380 'ALIAS' => 'CATALOG_SHOP_QUANTITY_#ENTITY_ID#',
1381 'TYPE' => null,
1382 'ALLOWED' => self::FIELD_ALLOWED_FILTER,
1383 'JOIN_MODIFY_EXPRESSION' => [__CLASS__, 'priceParametersFilter'],
1384 'NEW_ID' => 'DEFAULT_PRICE_FILTER',
1385 ],
1386 ];
1387 }
1388
1392 private static function getOldStoreFields(): array
1393 {
1394 return [
1395 'STORE_AMOUNT' => [
1396 'NAME' => 'AMOUNT',
1397 'ALIAS' => 'CATALOG_STORE_AMOUNT_#ENTITY_ID#',
1398 'TYPE' => self::FIELD_TYPE_FLOAT,
1399 'ALLOWED' => self::FIELD_ALLOWED_ALL,
1400 'ORDER_NULLABLE' => true,
1401 ],
1402 ];
1403 }
1404
1408 private static function getVatFields(): array
1409 {
1410 return [
1411 'RATE' => [
1412 'NAME' => 'RATE',
1413 'ALIAS' => 'CATALOG_VAT',
1414 'TYPE' => self::FIELD_TYPE_FLOAT,
1415 'ALLOWED' => self::FIELD_ALLOWED_SELECT,
1416 ],
1417 ];
1418 }
1419
1424 public static function isValidProductUserField(array $userField): bool
1425 {
1426 if ($userField['USER_TYPE_ID'] === Main\UserField\Types\FileType::USER_TYPE_ID)
1427 {
1428 return false;
1429 }
1430
1431 return true;
1432 }
1433
1439 private static function getFieldAllowed(string $entity, string $field): int
1440 {
1441 if (!isset(self::$entityFields[$entity][$field]))
1442 return 0;
1443 if (!isset(self::$entityFields[$entity][$field]['ALLOWED']))
1444 return 0;
1445 return (self::$entityFields[$entity][$field]['ALLOWED']);
1446 }
1447
1452 private static function getFieldsAllowedToSelect(string $entity): ?array
1453 {
1454 $filter = function($field)
1455 {
1456 return (isset($field['ALLOWED']) && ($field['ALLOWED'] & self::FIELD_ALLOWED_SELECT > 0));
1457 };
1458
1459 $result = array_filter(self::$entityFields[$entity], $filter);
1460 unset($filter);
1461 return (!empty($result) ? array_keys($result) : null);
1462 }
1463
1468 private static function parseField(string $field): ?array
1469 {
1470 if ($field === '')
1471 {
1472 return null;
1473 }
1474
1475 $field = strtoupper($field);
1476
1477 $entity = '';
1478 $entityId = 0;
1479 $prepared = [];
1480 $allowed = 0;
1481 $compatible = false;
1482 $checked = false;
1483
1484 if (preg_match(self::FIELD_PATTERN_OLD_STORE, $field, $prepared))
1485 {
1486 $compatible = true;
1487 $entity = self::ENTITY_OLD_STORE;
1488 $field = 'STORE_AMOUNT';
1489 $entityId = (int)$prepared[1];
1490 $checked = ($entityId > 0);
1491 }
1492 elseif (preg_match(self::FIELD_PATTERN_OLD_PRICE_ROW, $field, $prepared))
1493 {
1494 $compatible = true;
1495 $entity = self::ENTITY_OLD_PRICE;
1496 $field = 'ID';
1497 $entityId = (int)$prepared[1];
1498 $checked = ($entityId > 0);
1499 }
1500 elseif (preg_match(self::FIELD_PATTERN_OLD_PRICE, $field, $prepared))
1501 {
1502 $compatible = true;
1503 $entity = self::ENTITY_OLD_PRICE;
1504 $field = $prepared[1];
1505 $entityId = (int)$prepared[2];
1506 $checked = ($entityId > 0);
1507 }
1508 elseif (preg_match(self::FIELD_PATTERN_OLD_PRODUCT, $field, $prepared))
1509 {
1510 $compatible = true;
1511 $entity = self::ENTITY_OLD_PRODUCT;
1512 $field = $prepared[1];
1513 $checked = true;
1514 }
1515 elseif (preg_match(self::FIELD_PATTERN_PRODUCT_USER_FIELD, $field, $prepared))
1516 {
1517 $entity = self::ENTITY_PRODUCT_USER_FIELD;
1518 $field = $prepared[1];
1519 $checked = true;
1520 }
1521 elseif (preg_match(self::FIELD_PATTERN_SEPARATE_ENTITY, $field, $prepared))
1522 {
1523 $entity = self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_SEPARATE);
1524 if (!empty($entity))
1525 {
1526 $field = $prepared[1];
1527 $entityId = (int)$prepared[2];
1528 $checked = ($entityId > 0);
1529 }
1530 }
1531 elseif (preg_match(self::FIELD_PATTERN_FLAT_ENTITY, $field, $prepared))
1532 {
1533 $entity = self::searchFieldEntity($prepared[1], self::ENTITY_TYPE_FLAT);
1534 if (!empty($entity))
1535 {
1536 $field = $prepared[1];
1537 $checked = true;
1538 }
1539 }
1540
1541 if ($checked)
1542 {
1543 $allowed = self::getFieldAllowed($entity, $field);
1544 if (empty($allowed))
1545 {
1546 $checked = false;
1547 }
1548 }
1549
1550 if (!$checked)
1551 {
1552 return null;
1553 }
1554
1555 return [
1556 'ENTITY' => $entity,
1557 'FIELD' => $field,
1558 'ENTITY_ID' => $entityId,
1559 'ALLOWED' => $allowed,
1560 'COMPATIBLE' => $compatible
1561 ];
1562 }
1563
1569 private static function searchFieldEntity(string $field, int $type): ?string
1570 {
1571 $result = null;
1572
1573 switch ($type)
1574 {
1575 case self::ENTITY_TYPE_FLAT:
1576 if (isset(self::$entityFields[self::ENTITY_PRODUCT][$field]))
1577 $result = self::ENTITY_PRODUCT;
1578 elseif (isset(self::$entityFields[self::ENTITY_FLAT_PRICE][$field]))
1579 $result = self::ENTITY_FLAT_PRICE;
1580 elseif (isset(self::$entityFields[self::ENTITY_FLAT_WAREHNOUSE][$field]))
1581 $result = self::ENTITY_FLAT_WAREHNOUSE;
1582 elseif (isset(self::$entityFields[self::ENTITY_FLAT_BARCODE][$field]))
1583 $result = self::ENTITY_FLAT_BARCODE;
1584 break;
1585 case self::ENTITY_TYPE_SEPARATE:
1586 if (isset(self::$entityFields[self::ENTITY_WARENHOUSE][$field]))
1587 $result = self::ENTITY_WARENHOUSE;
1588 elseif (isset(self::$entityFields[self::ENTITY_PRICE][$field]))
1589 $result = self::ENTITY_PRICE;
1590 break;
1591 }
1592
1593 return $result;
1594 }
1595
1601 private static function checkAllowedAction(int $allowed, int $action): bool
1602 {
1603 return ($allowed & $action) > 0;
1604 }
1605
1611 private static function isEntityFilterField(string $field, array $entityList): bool
1612 {
1613 if (is_numeric($field))
1614 {
1615 return false;
1616 }
1617
1618 $result = false;
1619
1620 self::initEntityDescription();
1621 self::initEntityFields();
1622
1623 $filterItem = \CIBlock::MkOperationFilter($field);
1624 $prepareField = self::parseField($filterItem['FIELD']);
1625 if (
1626 self::checkPreparedField($prepareField)
1627 && self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER)
1628 )
1629 {
1630 if (isset($entityList[$prepareField['ENTITY']]))
1631 $result = true;
1632 }
1633 unset($prepareField, $filterItem);
1634
1635 return $result;
1636 }
1637
1642 private static function getEntityIndex(array $field): string
1643 {
1644 return $field['ENTITY'].':'.$field['ENTITY_ID'];
1645 }
1646
1651 private static function getEntityRow(string $entity): ?array
1652 {
1653 if (!isset(self::$entityDescription[$entity]))
1654 {
1655 return null;
1656 }
1657
1658 return self::$entityDescription[$entity];
1659 }
1660
1665 private static function isExternalEntity(string $entity): bool
1666 {
1667 return isset(self::$entityDescription[$entity]['EXTERNAL']);
1668 }
1669
1676 private static function getEntityDescription(array $entity): ?array
1677 {
1678 $row = self::getEntityRow($entity['ENTITY']);
1679 if ($row === null)
1680 {
1681 return null;
1682 }
1683 if (self::isExternalEntity($entity['ENTITY']))
1684 {
1685 return $row;
1686 }
1687
1688 $row['ALIAS'] = str_replace('#ENTITY_ID#', $entity['ENTITY_ID'], $row['ALIAS']);
1689
1690 $joinTemplates = [
1691 '#NAME#' => $row['NAME'],
1692 '#ALIAS#' => $row['ALIAS'],
1693 '#ENTITY_ID#' => $entity['ENTITY_ID']
1694 ];
1695 $additionalAliases = self::getOption('ALIASES');
1696 if (!empty($additionalAliases))
1697 $joinTemplates = $joinTemplates + $additionalAliases;
1698 unset($additionalAliases);
1699 $row['JOIN'] = str_replace(
1700 array_keys($joinTemplates),
1701 array_values($joinTemplates),
1702 $row['JOIN']
1703 );
1704 unset($joinTemplates);
1705
1706 return $row;
1707 }
1708
1713 private static function getFieldIndex(array $field): string
1714 {
1715 return $field['ENTITY'].':'.$field['ENTITY_ID'].':'.$field['FIELD'];
1716 }
1717
1722 private static function isPhantomField(array $field): bool
1723 {
1724 return isset($field['PHANTOM']);
1725 }
1726
1732 private static function getFieldDescription(string $entity, string $field): ?array
1733 {
1734 if (empty(self::$entityFields[$entity][$field]))
1735 return null;
1736 return self::$entityFields[$entity][$field];
1737 }
1738
1744 private static function getField(array $queryItem, array $options): ?array
1745 {
1746 $whiteList = [
1747 'ALIAS' => true,
1748 'SELECT' => true,
1749 'FILTER' => true,
1750 'ORDER' => true,
1751 'ENTITY_DESCRIPTION' => true
1752 ];
1753
1754 $field = self::getFieldDescription($queryItem['ENTITY'], $queryItem['FIELD']);
1755 if (empty($field))
1756 {
1757 return null;
1758 }
1759
1760 $entity = self::getEntityDescription($queryItem);
1761 if (empty($entity))
1762 {
1763 return null;
1764 }
1765
1766 $fantomField = self::isPhantomField($field);
1767
1768 if ($field['ALIAS'] !== null)
1769 {
1770 $field['ALIAS'] = str_replace('#ENTITY_ID#', $queryItem['ENTITY_ID'], $field['ALIAS']);
1771 }
1772 if (!$fantomField)
1773 {
1774 $field['FULL_NAME'] = $entity['ALIAS'].'.'.$field['NAME'];
1775 }
1776
1777 if (
1778 self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_SELECT)
1779 && isset($options['select'])
1780 )
1781 {
1782 if (!$fantomField)
1783 {
1784 $field['SELECT'] = '#FULL_NAME#';
1785 if (isset($field['SELECT_EXPRESSION']) && is_callable($field['SELECT_EXPRESSION']))
1786 {
1787 call_user_func_array(
1788 $field['SELECT_EXPRESSION'],
1789 [&$queryItem, &$entity, &$field]
1790 );
1791 }
1792 $field['SELECT'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['SELECT']);
1793 }
1794 }
1795
1796 if (
1797 self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_FILTER)
1798 && isset($options['filter'])
1799 )
1800 {
1801 if (isset($field['FILTER_PREPARE_VALUE_EXPRESSION']) && is_callable($field['FILTER_PREPARE_VALUE_EXPRESSION']))
1802 {
1803 call_user_func_array(
1804 $field['FILTER_PREPARE_VALUE_EXPRESSION'],
1805 [&$queryItem, &$entity, &$field]
1806 );
1807 }
1808
1809 if (!$fantomField)
1810 {
1811 $field['FILTER'] = \CIBlock::FilterCreate(
1812 '#FULL_NAME#',
1813 $queryItem['VALUES'],
1814 self::getFilterType($field['TYPE']),
1815 $queryItem['OPERATION']
1816 );
1817 $field['FILTER'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['FILTER']);
1818 }
1819 }
1820
1821 if (
1822 self::checkAllowedAction($field['ALLOWED'], self::FIELD_ALLOWED_ORDER)
1823 && isset($options['order'])
1824 )
1825 {
1826 if (!$fantomField)
1827 {
1828 $field['ORDER'] = \CIBlock::_Order(
1829 '#FULL_NAME#',
1830 $queryItem['ORDER'],
1831 $field['ORDER_DEFAULT'] ?? 'ASC',
1832 isset($field['ORDER_NULLABLE'])
1833 );
1834 $field['ORDER'] = str_replace('#FULL_NAME#', $field['FULL_NAME'], $field['ORDER']);
1835 }
1836 }
1837
1838 if (isset($field['JOIN_MODIFY_EXPRESSION']) && is_callable($field['JOIN_MODIFY_EXPRESSION']))
1839 {
1840 call_user_func_array(
1841 $field['JOIN_MODIFY_EXPRESSION'],
1842 [&$queryItem, &$entity, &$field]
1843 );
1844 }
1845 if (isset($field['JOIN_MODIFY']))
1846 {
1847 $field['JOIN_MODIFY'] = str_replace('#TABLE#', $entity['ALIAS'], $field['JOIN_MODIFY']);
1848 $entity['JOIN_MODIFY'] = $field['JOIN_MODIFY'];
1849 }
1850
1851 unset($fantomField);
1852
1853 $field['ENTITY_DESCRIPTION'] = $entity;
1854 unset($entity);
1855
1856 $result = array_intersect_key($field, $whiteList);
1857 unset($field, $whiteList);
1858
1859 return $result;
1860 }
1861
1866 private static function getFilterType(string $fieldType): string
1867 {
1868 return match ($fieldType)
1869 {
1870 self::FIELD_TYPE_INT, self::FIELD_TYPE_FLOAT => 'number',
1871 self::FIELD_TYPE_CHAR => 'string_equal',
1872 default => 'string',
1873 };
1874 }
1875
1880 private static function checkPreparedField(?array $field): bool
1881 {
1882 if (empty($field))
1883 {
1884 return false;
1885 }
1886 if ($field['ENTITY_ID'] === 0)
1887 {
1888 if (
1889 $field['ENTITY'] != self::ENTITY_PRODUCT
1890 && $field['ENTITY'] != self::ENTITY_PRODUCT_USER_FIELD
1891 && $field['ENTITY'] != self::ENTITY_FLAT_PRICE
1892 && $field['ENTITY'] != self::ENTITY_FLAT_WAREHNOUSE
1893 && $field['ENTITY'] != self::ENTITY_FLAT_BARCODE
1894 && $field['ENTITY'] != self::ENTITY_OLD_PRODUCT
1895 )
1896 {
1897 return false;
1898 }
1899 }
1900
1901 return true;
1902 }
1903
1908 private static function getFieldSignature(array $field): array
1909 {
1910 return [
1911 'ENTITY' => $field['ENTITY'],
1912 'FIELD' => $field['FIELD'],
1913 'ENTITY_ID' => $field['ENTITY_ID']
1914 ];
1915 }
1916
1921 private static function prepareSelectedCompatibleFields(array &$parameters): void
1922 {
1923 if ($parameters['compatible_mode'] && !empty($parameters['compatible_entities']))
1924 {
1925 foreach ($parameters['compatible_entities'] as $entity)
1926 {
1927 $list = self::getFieldsAllowedToSelect($entity['ENTITY']);
1928 if ($list === null)
1929 continue;
1930 foreach ($list as $fieldId)
1931 {
1932 $field = [
1933 'ENTITY' => $entity['ENTITY'],
1934 'FIELD' => $fieldId,
1935 'ENTITY_ID' => $entity['ENTITY_ID']
1936 ];
1937 $parameters['select'][self::getFieldIndex($field)] = $field;
1938 }
1939 unset($field, $fieldId, $list);
1940 }
1941 unset($entity);
1942 }
1943 unset($parameters['compatible_mode'], $parameters['compatible_entities']);
1944 }
1945
1951 private static function fillCompatibleEntities(array &$result, array $field): void
1952 {
1953 if (!$field['COMPATIBLE'])
1954 return;
1955 $result['compatible_mode'] = true;
1956 $result['compatible_entities'][self::getEntityIndex($field)] = [
1957 'ENTITY' => $field['ENTITY'],
1958 'ENTITY_ID' => $field['ENTITY_ID']
1959 ];
1960 // if compatible mode enabled - product always exists.
1961 $entity = [
1962 'ENTITY' => self::ENTITY_OLD_PRODUCT,
1963 'ENTITY_ID' => 0
1964 ];
1965 $result['compatible_entities'][self::getEntityIndex($entity)] = $entity;
1966 $entity = [
1967 'ENTITY' => self::ENTITY_VAT,
1968 'ENTITY_ID' => 0
1969 ];
1970 $result['compatible_entities'][self::getEntityIndex($entity)] = $entity;
1971 unset($entity);
1972
1973 }
1974
1980 private static function prepareQuery(array $parameters, array $options): ?array
1981 {
1982 self::initEntityDescription();
1983 self::initEntityFields();
1984
1985 self::setOptions($options);
1986
1987 $result = [
1988 'compatible_mode' => false,
1989 'compatible_entities' => [],
1990 'select' => [],
1991 'filter' => [],
1992 'order' => [],
1993 ];
1994
1995 if (!empty($parameters['select']) && is_array($parameters['select']))
1996 {
1997 foreach ($parameters['select'] as $field)
1998 {
1999 $prepareField = self::parseField($field);
2000 if (!self::checkPreparedField($prepareField))
2001 {
2002 continue;
2003 }
2004 if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_SELECT))
2005 {
2006 continue;
2007 }
2008
2009 self::fillCompatibleEntities($result, $prepareField);
2010 $result['select'][self::getFieldIndex($prepareField)] = self::getFieldSignature($prepareField);
2011 }
2012 unset($prepareField, $field);
2013 }
2014
2015 if (!empty($parameters['filter']) && is_array($parameters['filter']))
2016 {
2017 foreach (array_keys($parameters['filter']) as $key)
2018 {
2019 $filter = \CIBlock::MkOperationFilter($key);
2020 $prepareField = self::parseField($filter['FIELD']);
2021
2022 if (!self::checkPreparedField($prepareField))
2023 {
2024 continue;
2025 }
2026 if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_FILTER))
2027 {
2028 continue;
2029 }
2030
2031 self::fillCompatibleEntities($result, $prepareField);
2032 $prepareField = self::getFieldSignature($prepareField);
2033 $prepareField['OPERATION'] = $filter['OPERATION'];
2034 $prepareField['PREFIX'] = $filter['PREFIX'];
2035 $prepareField['VALUES'] = $parameters['filter'][$key];
2036 $result['filter'][] = $prepareField;
2037 }
2038 unset($prepareField, $filter, $key);
2039 }
2040
2041 if (!empty($parameters['order']) && is_array($parameters['order']))
2042 {
2043 foreach ($parameters['order'] as $index => $value)
2044 {
2045 if (empty($value) || !is_array($value))
2046 {
2047 continue;
2048 }
2049
2050 $order = reset($value);
2051 $field = key($value);
2052
2053 $prepareField = self::parseField($field);
2054 if (!self::checkPreparedField($prepareField))
2055 {
2056 continue;
2057 }
2058 if (!self::checkAllowedAction($prepareField['ALLOWED'], self::FIELD_ALLOWED_ORDER))
2059 {
2060 continue;
2061 }
2062
2063 self::orderTransformField($prepareField);
2064
2065 self::fillCompatibleEntities($result, $prepareField);
2066 $fieldIndex = self::getFieldIndex($prepareField);
2067
2068 $prepareField = self::getFieldSignature($prepareField);
2069 $result['select'][$fieldIndex] = $prepareField;
2070
2071 $prepareField['INDEX'] = $index;
2072 $prepareField['ORDER'] = $order;
2073 $result['order'][$fieldIndex] = $prepareField;
2074 }
2075 unset($order, $field, $index, $value);
2076 }
2077
2078 self::prepareSelectedCompatibleFields($result);
2079
2080 $result = self::build($result);
2081 self::clearOptions();
2082 return $result;
2083 }
2084
2088 private static function clearOptions(): void
2089 {
2090 self::$options = [];
2091 }
2092
2097 private static function setOptions(array $options): void
2098 {
2099 global $USER;
2100
2101 if (!isset($options['ALIASES']))
2102 {
2103 $options['ALIASES'] = [];
2104 }
2105 if (!isset($options['ALIASES']['#ELEMENT#']))
2106 {
2107 $options['ALIASES']['#ELEMENT#'] = 'BE';
2108 }
2109 if (!isset($options['ALIASES']['#ELEMENT_JOIN#']))
2110 {
2111 $options['ALIASES']['#ELEMENT_JOIN#'] = 'BE.ID';
2112 }
2113
2114 if (!isset($options['USER']))
2115 {
2116 $options['USER'] = [];
2117 }
2118 if (!isset($options['USER']['ID']))
2119 {
2120 $options['USER']['ID'] = (\CCatalog::IsUserExists() ? $USER->GetID() : 0);
2121 }
2122 $options['USER']['ID'] = (int)$options['USER']['ID'];
2123
2124 self::$options = $options;
2125 }
2126
2131 private static function getOption(string $index): mixed
2132 {
2133 if (!isset(self::$options[$index]))
2134 return null;
2135 return self::$options[$index];
2136 }
2137
2142 private static function build(array $parameters): ?array
2143 {
2144 $founded = false;
2145 $result = [
2146 'select' => [],
2147 'filter' => [],
2148 'order' => [],
2149 'join' => [],
2150 'join_modify' => [],
2151 ];
2152
2153 if (!empty($parameters['select']))
2154 {
2155 self::buildSelect($result, $parameters['select']);
2156 if (!empty($result['select']))
2157 {
2158 $founded = true;
2159 }
2160 }
2161 if (!empty($parameters['filter']))
2162 {
2163 self::buildFilter($result, $parameters['filter']);
2164 if (!empty($result['filter']) || !empty($result['join']))
2165 {
2166 $founded = true;
2167 }
2168 }
2169 if (!empty($parameters['order']))
2170 {
2171 self::buildOrder($result, $parameters['order']);
2172 if (!empty($result['order']))
2173 {
2174 $founded = true;
2175 }
2176 }
2177
2178 if (!$founded)
2179 {
2180 return null;
2181 }
2182
2183 self::buildJoin($result);
2184
2185 $result['join'] = array_values($result['join']);
2186 unset($result['join_modify']);
2187
2188 return $result;
2189 }
2190
2196 private static function buildSelect(array &$result, array $list): void
2197 {
2198 foreach ($list as $item)
2199 {
2200 $field = self::getField($item, ['select' => true]);
2201 if (empty($field))
2202 continue;
2203
2204 if (isset($field['SELECT']))
2205 $result['select'][] = $field['SELECT'].' as '.$field['ALIAS'];
2206
2207 $item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
2208 self::addJoin($result, $item);
2209 }
2210 unset($item);
2211 }
2212
2218 private static function buildFilter(array &$result, array $list): void
2219 {
2220 self::filterModify($list);
2221
2222 $packedFilter = self::getPackedFilter($list);
2223
2224 self::buildInternalFilter($result, $packedFilter['INTERNAL']);
2225 self::buildExternalFilter($result, $packedFilter['EXTERNAL']);
2226 }
2227
2233 private static function buildOrder(array &$result, array $list): void
2234 {
2235 foreach ($list as $item)
2236 {
2237 $field = self::getField($item, ['order' => true]);
2238 if (empty($field))
2239 continue;
2240
2241 if (isset($field['ORDER']))
2242 $result['order'][$item['INDEX']] = $field['ORDER'];
2243
2244 $item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
2245 self::addJoin($result, $item);
2246 }
2247 unset($field, $item);
2248 }
2249
2254 private static function buildJoin(array &$result): void
2255 {
2256 foreach (array_keys($result['join']) as $index)
2257 {
2258 $modifier = (isset($result['join_modify'][$index])
2259 ? ' '.implode(' ', $result['join_modify'][$index])
2260 : ''
2261 );
2262 $result['join'][$index] = str_replace('#JOIN_MODIFY#', $modifier, $result['join'][$index]);
2263 }
2264 unset($modifier, $index);
2265 }
2266
2271 private static function filterModify(array &$list): void
2272 {
2273 foreach (array_keys($list) as $index)
2274 {
2275 $item = $list[$index];
2276 $field = self::getFieldDescription($item['ENTITY'], $item['FIELD']);
2277 if (empty($field))
2278 {
2279 continue;
2280 }
2281
2282 $entity = self::getEntityDescription($item);
2283 if (empty($entity))
2284 {
2285 continue;
2286 }
2287
2288 if (isset($field['FILTER_MODIFY_EXPRESSION']) && is_callable($field['FILTER_MODIFY_EXPRESSION']))
2289 {
2290 call_user_func_array(
2291 $field['FILTER_MODIFY_EXPRESSION'],
2292 [&$list, $index, $entity, $field]
2293 );
2294 }
2295 }
2296 unset($field, $entity, $item, $index);
2297 }
2298
2299 private static function getPackedFilter(array $filter): array
2300 {
2301 $result = [
2302 'INTERNAL' => [],
2303 'EXTERNAL' => [],
2304 ];
2305
2306 foreach ($filter as $item)
2307 {
2308 $entity = $item['ENTITY'];
2309 if (self::isExternalEntity($entity))
2310 {
2311 if (!isset($result['EXTERNAL'][$entity]))
2312 {
2313 $result['EXTERNAL'][$entity] = [];
2314 }
2315 $index = self::getEntityIndex($item);
2316 if (!isset($result['EXTERNAL'][$entity][$index]))
2317 {
2318 $result['EXTERNAL'][$entity][$index] = [];
2319 }
2320 $result['EXTERNAL'][$entity][$index][] = $item;
2321 }
2322 else
2323 {
2324 $result['INTERNAL'][] = $item;
2325 }
2326 }
2327
2328 return $result;
2329 }
2330
2335 private static function buildInternalFilter(array &$result, array $list): void
2336 {
2337 foreach ($list as $item)
2338 {
2339 $field = self::getField($item, ['filter' => true]);
2340 if (empty($field))
2341 {
2342 continue;
2343 }
2344
2345 if (isset($field['FILTER']))
2346 {
2347 $result['filter'][] = $field['FILTER'];
2348 }
2349
2350 $item['ENTITY_DESCRIPTION'] = $field['ENTITY_DESCRIPTION'];
2351 self::addJoin($result, $item);
2352 }
2353 unset($item);
2354 }
2355
2361 private static function addJoin(array &$result, array $entity): void
2362 {
2363 $index = self::getEntityIndex($entity);
2364 $description = $entity['ENTITY_DESCRIPTION'];
2365
2366 if (!isset($result['join'][$index]))
2367 {
2368 if (!empty($description['RELATION']))
2369 {
2370 foreach ($description['RELATION'] as $item)
2371 {
2372 if (!is_array($item))
2373 {
2374 $item = [
2375 'ENTITY' => $item,
2376 'ENTITY_ID' => 0
2377 ];
2378 }
2379 $ownerIndex = self::getEntityIndex($item);
2380 if (isset($result['join'][$ownerIndex]))
2381 continue;
2382 $owner = self::getEntityDescription($item);
2383 if (empty($owner))
2384 continue;
2385 $result['join'][$ownerIndex] = $owner['JOIN'];
2386 }
2387 unset($owner, $ownerIndex, $parent, $item);
2388 }
2389 $result['join'][$index] = $description['JOIN'];
2390 }
2391 if (!empty($description['JOIN_MODIFY']))
2392 {
2393 if (!isset($result['join_modify'][$index]))
2394 $result['join_modify'][$index] = [];
2395 $result['join_modify'][$index][] = $description['JOIN_MODIFY'];
2396 }
2397 unset($description, $index);
2398 }
2399
2404 private static function buildExternalFilter(array &$result, array $filter): void
2405 {
2406 foreach (array_keys($filter) as $entity)
2407 {
2408 $row = self::getEntityRow($entity);
2409 if (isset($row['HANDLER']) && is_callable($row['HANDLER']))
2410 {
2411 call_user_func_array(
2412 $row['HANDLER'],
2413 [
2414 &$result,
2415 $row,
2416 [
2417 'filter' => $filter[$entity],
2418 ]
2419 ]
2420 );
2421 }
2422 }
2423 }
2424
2429 private static function orderTransformField(array &$item): void
2430 {
2431 $field = self::getFieldDescription($item['ENTITY'], $item['FIELD']);
2432 if (empty($field))
2433 return;
2434 if (isset($field['ORDER_TRANSFORM']))
2435 $item['FIELD'] = $field['ORDER_TRANSFORM'];
2436 unset($field);
2437 }
2438
2447 private static function selectQuantityTrace(array &$parameters, array &$entity, array &$field): void
2448 {
2449 $field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_quantity_trace'));
2450 }
2451
2460 private static function selectCanBuyZero(array &$parameters, array &$entity, array &$field): void
2461 {
2462 $field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_can_buy_zero'));
2463 }
2464
2473 private static function selectNegativeAmountTrace(array &$parameters, array &$entity, array &$field): void
2474 {
2475 $field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'allow_negative_amount'));
2476 }
2477
2486 private static function selectSubscribe(array &$parameters, array &$entity, array &$field): void
2487 {
2488 $field['SELECT'] = self::getReplaceSqlFunction(Main\Config\Option::get('catalog', 'default_subscribe'));
2489 }
2490
2497 private static function getReplaceSqlFunction(string $defaultValue): string
2498 {
2499 return 'CASE WHEN #FULL_NAME# = \'' . ProductTable::STATUS_DEFAULT . '\' THEN \'' . $defaultValue . '\' ELSE #FULL_NAME# END';
2500 }
2501
2508 private static function selectPriceTypeName(array &$parameters, array &$entity, array &$field): void
2509 {
2510 $result = '';
2511 $id = $parameters['ENTITY_ID'];
2512 $fullPriceTypeList = GroupTable::getTypeList();
2513 if (!empty($fullPriceTypeList[$id]))
2514 {
2515 $result = $fullPriceTypeList[$id]['NAME_LANG'] ?? $fullPriceTypeList[$id]['NAME'];
2516 $connection = Main\Application::getInstance()->getConnection();
2517 $sqlHelper = $connection->getSqlHelper();
2518 $result = $sqlHelper->forSql($result);
2519 unset($sqlHelper, $connection);
2520 }
2521 unset($fullPriceTypeList, $id);
2522 $field['SELECT'] = '\''.$result.'\'';
2523 unset($result);
2524 }
2525
2532 private static function selectPriceTypeAllowedView(array &$parameters, array &$entity, array &$field): void
2533 {
2534 $parameters['ACCESS'] = GroupAccessTable::ACCESS_VIEW;
2535 $field['SELECT'] = self::getPriceTypeAccess($parameters);
2536 }
2537
2544 private static function selectPriceTypeAllowedBuy(array &$parameters, array &$entity, array &$field): void
2545 {
2546 $parameters['ACCESS'] = GroupAccessTable::ACCESS_BUY;
2547 $field['SELECT'] = self::getPriceTypeAccess($parameters);
2548 }
2549
2554 private static function getPriceTypeAccess(array $parameters): string
2555 {
2556 $result = 'N';
2557
2558 $user = self::getOption('USER');
2559 if (!empty($user))
2560 {
2561 if (empty($user['GROUPS']) || !is_array($user['GROUPS']))
2562 $user['GROUPS'] = self::getUserGroups($user['ID']);
2563 $iterator = GroupAccessTable::getList([
2564 'select' => ['ID'],
2565 'filter' => [
2566 '=CATALOG_GROUP_ID' => $parameters['ENTITY_ID'],
2567 '@GROUP_ID' => $user['GROUPS'],
2568 '=ACCESS' => $parameters['ACCESS']
2569 ],
2570 'limit' => 1
2571 ]);
2572 $row = $iterator->fetch();
2573 if (!empty($row))
2574 $result = 'Y';
2575 unset($row, $iterator);
2576 }
2577 unset($user);
2578
2579 return '\''.$result.'\'';
2580 }
2581
2588 private static function prepareFilterQuantityTrace(array &$parameters, array &$entity, array &$field): void
2589 {
2590 $parameters['VALUES'] = self::addDefaultValue(
2591 $parameters['VALUES'],
2592 Main\Config\Option::get('catalog', 'default_quantity_trace')
2593 );
2594 }
2595
2602 private static function prepareFilterCanBuyZero(array &$parameters, array &$entity, array &$field): void
2603 {
2604 $parameters['VALUES'] = self::addDefaultValue(
2605 $parameters['VALUES'],
2606 Main\Config\Option::get('catalog', 'default_can_buy_zero')
2607 );
2608 }
2609
2616 private static function prepareFilterSubscribe(array &$parameters, array &$entity, array &$field): void
2617 {
2618 $parameters['VALUES'] = self::addDefaultValue(
2619 $parameters['VALUES'],
2620 Main\Config\Option::get('catalog', 'default_subscribe')
2621 );
2622 }
2623
2631 private static function addDefaultValue(mixed $values, string $defaultValue): mixed
2632 {
2633 if (!is_array($values))
2634 {
2635 if ($values === $defaultValue)
2636 {
2637 $values = [$values, ProductTable::STATUS_DEFAULT];
2638 }
2639 }
2640 else
2641 {
2642 if (in_array($defaultValue, $values))
2643 {
2644 $values[] = ProductTable::STATUS_DEFAULT;
2645 $values = array_unique($values);
2646 }
2647 }
2648 return $values;
2649 }
2650
2657 private static function priceParametersFilter(array &$parameters, array &$entity, array &$field): void
2658 {
2659 if (empty($parameters['VALUES']))
2660 return;
2661 if (!is_string($parameters['VALUES']) && !is_int($parameters['VALUES']))
2662 return;
2663 $value = (int)$parameters['VALUES'];
2664 if ($value <= 0)
2665 return;
2666
2667 $field['JOIN_MODIFY'] = ' and '.($parameters['OPERATION'] == 'N' ? 'not' : '').
2668 ' ((#TABLE#.QUANTITY_FROM <= '.$value.' or #TABLE#.QUANTITY_FROM IS NULL)'.
2669 ' and (#TABLE#.QUANTITY_TO >= '.$value.' or #TABLE#.QUANTITY_TO IS NULL))';
2670 }
2671
2679 private static function filterModifierCurrencyScale(array &$filter, int|string $filterKey, array $entity, array $field): void
2680 {
2681 $activeItem = $filter[$filterKey];
2682
2683 if ($activeItem['FIELD'] !== 'CURRENCY_FOR_SCALE' && $activeItem['FIELD'] !== 'CURRENCY_SCALE')
2684 {
2685 return;
2686 }
2687 if ($activeItem['OPERATION'] !== 'E' && $activeItem['OPERATION'] !== 'I')
2688 {
2689 return;
2690 }
2691
2692 $value = $activeItem['VALUES'];
2693 if (!is_string($value))
2694 {
2695 return;
2696 }
2697
2698 $currencyId = Currency\CurrencyManager::checkCurrencyID($value);
2699 if ($currencyId === false)
2700 {
2701 return;
2702 }
2703
2704 $currency = \CCurrency::GetByID($currencyId);
2705 if (empty($currency))
2706 {
2707 return;
2708 }
2709 $currency['CURRENT_BASE_RATE'] = (float)$currency['CURRENT_BASE_RATE'];
2710 if ($currency['CURRENT_BASE_RATE'] <= 0)
2711 {
2712 return;
2713 }
2714
2715 foreach (array_keys($filter) as $index)
2716 {
2717 if ($index == $filterKey)
2718 {
2719 continue;
2720 }
2721 $filterItem = $filter[$index];
2722 if (
2723 $filterItem['ENTITY'] != $activeItem['ENTITY']
2724 || $filterItem['ENTITY_ID'] != $activeItem['ENTITY_ID']
2725 )
2726 {
2727 continue;
2728 }
2729 if ($filterItem['FIELD'] != 'PRICE')
2730 {
2731 continue;
2732 }
2733 if (is_array($filter[$index]['VALUES']))
2734 {
2735 $newPrices = [];
2736 foreach ($filter[$index]['VALUES'] as $oldPrice)
2737 {
2738 $newPrices[] = (float)$oldPrice * $currency['CURRENT_BASE_RATE'];
2739 }
2740 $filter[$index]['VALUES'] = $newPrices;
2741 unset($oldPrice, $newPrices);
2742 }
2743 else
2744 {
2745 $filter[$index]['VALUES'] = (float)$filter[$index]['VALUES']*$currency['CURRENT_BASE_RATE'];
2746 }
2747 $filter[$index]['FIELD'] = $activeItem['FIELD'] === 'CURRENCY_FOR_SCALE'
2748 ? 'SCALED_PRICE'
2749 : 'PRICE_SCALE'
2750 ;
2751 }
2752 unset($index);
2753 }
2754
2759 private static function getUserGroups(int $userId): array
2760 {
2761 return Main\UserTable::getUserGroupIds($userId);
2762 }
2763
2769 private static function handleProductUserFields(array &$result, array $entity, array $data): void
2770 {
2771 if (empty($data['filter']))
2772 {
2773 return;
2774 }
2775 $aliases = self::getOption('ALIASES');
2776 if (empty($aliases['#ELEMENT_JOIN#']))
2777 {
2778 return;
2779 }
2780
2781 $userFieldManager = new CUserTypeSQL;
2782
2783 foreach ($data['filter'] as $entityIndex => $rows)
2784 {
2785 $userFieldManager->SetEntity(ProductTable::getUfId(), $aliases['#ELEMENT_JOIN#']);
2786 $userFieldManager->SetSelect([]);
2787 $rawFilter = [];
2788 foreach ($rows as $row)
2789 {
2790 $rawFilter[$row['PREFIX'].$row['FIELD']] = $row['VALUES'];
2791 }
2792 $userFieldManager->SetFilter($rawFilter);
2793 $userFieldManager->SetOrder([]);
2794
2795 $filter = $userFieldManager->GetFilter();
2796 if (!empty($filter))
2797 {
2798 $result['filter'][] = $filter;
2799 $join = $userFieldManager->GetJoin($aliases['#ELEMENT_JOIN#']);
2800 if (!empty($join))
2801 {
2802 $result['join'][$entityIndex] = $join;
2803 }
2804 }
2805 }
2806
2807 unset($userFieldManager);
2808 }
2809}
$type
Определения options.php:106
if($_SERVER $defaultValue['REQUEST_METHOD']==="GET" &&!empty($RestoreDefaults) && $bizprocPerms==="W" &&check_bitrix_sessid())
Определения options.php:32
if(!is_object($USER)||! $USER->IsAuthorized()) $userId
Определения check_mail.php:18
static makeQuery(array $parameters, array $options=[])
Определения querybuilder.php:76
const ENTITY_PRICE
Определения querybuilder.php:13
static isPriceFilterField(string $field)
Определения querybuilder.php:222
static makeFilter(array $filter, array $options=[])
Определения querybuilder.php:56
const FIELD_ALLOWED_ORDER
Определения querybuilder.php:26
static isValidField(string $field)
Определения querybuilder.php:95
const FIELD_ALLOWED_FILTER
Определения querybuilder.php:25
const ENTITY_OLD_PRICE
Определения querybuilder.php:19
static convertOldSelect(array $select)
Определения querybuilder.php:408
static isRealFilterField(string $field)
Определения querybuilder.php:150
const ENTITY_FLAT_BARCODE
Определения querybuilder.php:17
static isProductFilterField(string $field)
Определения querybuilder.php:206
static isValidProductUserField(array $userField)
Определения querybuilder.php:1424
static convertOldOrder(array $order)
Определения querybuilder.php:495
static convertOldField(string $field, int $useMode)
Определения querybuilder.php:337
const FIELD_ALLOWED_SELECT
Определения querybuilder.php:24
static isWarenhouseFilterField(string $field)
Определения querybuilder.php:238
const ENTITY_OLD_PRODUCT
Определения querybuilder.php:18
const ENTITY_WARENHOUSE
Определения querybuilder.php:14
static getEntityFieldAliases(string $entity, array $options=[])
Определения querybuilder.php:520
static modifyFilterFromOrder(array $filter, array $order, array $options)
Определения querybuilder.php:256
static convertOldFilter(array $filter)
Определения querybuilder.php:431
const ENTITY_OLD_STORE
Определения querybuilder.php:20
const FIELD_ALLOWED_ALL
Определения querybuilder.php:27
const ENTITY_PRODUCT_USER_FIELD
Определения querybuilder.php:12
const ENTITY_FLAT_PRICE
Определения querybuilder.php:15
const ENTITY_PRODUCT
Определения querybuilder.php:11
static isCatalogFilterField(string $field)
Определения querybuilder.php:183
const ENTITY_FLAT_WAREHNOUSE
Определения querybuilder.php:16
$data['IS_AVAILABLE']
Определения .description.php:13
if(errorBox) return true
Определения file_new.php:1035
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
Определения file_new.php:804
$result
Определения get_property_values.php:14
$query
Определения get_search.php:11
$entity
if(Loader::includeModule( 'bitrix24')) elseif(Loader::includeModule('intranet') &&CIntranetUtils::getPortalZone() !=='ru') $description
Определения .description.php:24
$select
Определения iblock_catalog_list.php:194
$filter
Определения iblock_catalog_list.php:54
global $USER
Определения csv_new_run.php:40
$value
Определения Param.php:39
$user
Определения mysql_to_pgsql.php:33
$order
Определения payment.php:8
$entityId
Определения payment.php:4
$direction
Определения prolog_auth_admin.php:25
if( $daysToExpire >=0 &&$daysToExpire< 60 elseif)( $daysToExpire< 0)
Определения prolog_main_admin.php:393
if(empty($signedUserToken)) $key
Определения quickway.php:257
$currency
Определения template.php:266
$rows
Определения options.php:264
$action
Определения file_dialog.php:21
$iterator
Определения yandex_run.php:610