Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
facet.php
1<?php
9
10class Facet
11{
12 protected $iblockId = 0;
13 protected $valid = false;
14 protected $skuIblockId = 0;
15 protected $skuPropertyId = 0;
16 protected $sectionFilter = array();
17 protected $priceFilter = null;
18 protected $toCurrencyId = 0;
19 protected $convertCurrencyId = array();
20
22 protected $dictionary = null;
24 protected $storage = null;
25 protected static $catalog = null;
26
27 protected $sectionId = 0;
28 protected $where = array();
29
33 public function __construct($iblockId)
34 {
35 $this->iblockId = intval($iblockId);
36 $this->valid = \CIBlock::getArrayByID($this->iblockId, "PROPERTY_INDEX") === "Y";
37
38 if (self::$catalog === null)
39 {
40 self::$catalog = \Bitrix\Main\Loader::includeModule("catalog");
41 }
42
43 if (self::$catalog)
44 {
45 $catalogInfo = \CCatalogSKU::getInfoByProductIBlock($this->iblockId);
46 if (!empty($catalogInfo) && is_array($catalogInfo))
47 {
48 $this->skuIblockId = $catalogInfo["IBLOCK_ID"];
49 $this->skuPropertyId = $catalogInfo["SKU_PROPERTY_ID"];
50 $this->valid = $this->valid && \CIBlock::getArrayByID($this->skuIblockId, "PROPERTY_INDEX") === "Y";
51 }
52 }
53
54 $this->dictionary = new Dictionary($this->iblockId);
55 $this->storage = new Storage($this->iblockId);
56
57 $this->valid = $this->valid && $this->dictionary->isExists();
58 }
59
65 public function isValid()
66 {
67 return $this->valid;
68 }
69
75 public function getIblockId()
76 {
77 return $this->iblockId;
78 }
79
85 public function getSkuIblockId()
86 {
87 return $this->skuIblockId;
88 }
89
95 public function getSkuPropertyId()
96 {
98 }
99
105 public function getStorage()
106 {
107 return $this->storage;
108 }
109
115 public function getDictionary()
116 {
117 return $this->dictionary;
118 }
119
127 public function lookupDictionaryValue($valueId)
128 {
129 return $this->dictionary->getStringById($valueId);
130 }
131
145 public function query(array $filter, array $facetTypes = array(), $facetId = 0)
146 {
147 $connection = \Bitrix\Main\Application::getConnection();
148 $sqlHelper = $connection->getSqlHelper();
149
150 $facetFilter = $this->getFacetFilter($facetTypes);
151 if (!$facetFilter)
152 {
153 return false;
154 }
155
156 if ($filter)
157 {
158 $filter["IBLOCK_ID"] = $this->iblockId;
159
160 $element = new \CIBlockElement;
161 $element->strField = "ID";
162 $element->prepareSql(array("ID"), $filter, false, false);
163 $elementFrom = $element->sFrom;
164 $elementWhere = $element->sWhere;
165 }
166 else
167 {
168 $elementFrom = "";
169 $elementWhere = "";
170 }
171
172 $facets = array();
173 if ($facetId)
174 {
175 $facets[] = array(
176 "where" => $this->getWhere($facetId),
177 "facet" => array($facetId),
178 );
179 }
180 else
181 {
182 foreach ($facetFilter as $facetId)
183 {
184 $where = $this->getWhere($facetId);
185
186 $found = false;
187 foreach ($facets as $i => $facetWhereAndFacets)
188 {
189 if ($facetWhereAndFacets["where"] == $where)
190 {
191 $facets[$i]["facet"][] = $facetId;
192 $found = true;
193 break;
194 }
195 }
196
197 if (!$found)
198 {
199 $facets[] = array(
200 "where" => $where,
201 "facet" => array($facetId),
202 );
203 }
204 }
205 }
206
207 $sqlUnion = array();
208 foreach ($facets as $facetWhereAndFacets)
209 {
210 $where = $facetWhereAndFacets["where"];
211 $facetFilter = $facetWhereAndFacets["facet"];
212
213 $sqlSearch = array("1=1");
214
215 if (empty($where))
216 {
217 $sqlUnion[] = "
218 SELECT
219 F.FACET_ID
220 ,F.VALUE
221 ,MIN(F.VALUE_NUM) MIN_VALUE_NUM
222 ,MAX(F.VALUE_NUM) MAX_VALUE_NUM
223 ".($connection instanceof \Bitrix\Main\DB\MysqlCommonConnection
224 ?",MAX(case when LOCATE('.', F.VALUE_NUM) > 0 then LENGTH(SUBSTRING_INDEX(F.VALUE_NUM, '.', -1)) else 0 end)"
225 :",MAX(".$sqlHelper->getLengthFunction("ABS(F.VALUE_NUM) - FLOOR(ABS(F.VALUE_NUM))")."+1-".$sqlHelper->getLengthFunction("0.1").")"
226 )." VALUE_FRAC_LEN
227 ,COUNT(DISTINCT F.ELEMENT_ID) ELEMENT_COUNT
228 FROM
229 ".($elementFrom
230 ?$elementFrom."
231 INNER JOIN ".$this->storage->getTableName()." F ON BE.ID = F.ELEMENT_ID"
232 :$this->storage->getTableName()." F"
233 )."
234 WHERE
235 F.SECTION_ID = ".$this->sectionId."
236 and F.FACET_ID in (".implode(",", $facetFilter).")
237 ".$elementWhere."
238 GROUP BY
239 F.FACET_ID, F.VALUE
240 ";
241 continue;
242 }
243 elseif (count($where) == 1)
244 {
245 $fcJoin = "INNER JOIN ".$this->storage->getTableName()." FC on FC.ELEMENT_ID = BE.ID";
246 foreach ($where as $facetWhere)
247 {
248 $sqlWhere = $this->whereToSql($facetWhere, "FC");
249 if ($sqlWhere)
250 $sqlSearch[] = $sqlWhere;
251 }
252 }
253 elseif (count($where) <= 5)
254 {
255 $subJoin = "";
256 $subWhere = "";
257 $i = 0;
258 foreach ($where as $facetWhere)
259 {
260 if ($i == 0)
261 $subJoin .= "FROM ".$this->storage->getTableName()." FC$i\n";
262 else
263 $subJoin .= "INNER JOIN ".$this->storage->getTableName()." FC$i ON FC$i.ELEMENT_ID = FC0.ELEMENT_ID\n";
264
265 $sqlWhere = $this->whereToSql($facetWhere, "FC$i");
266 if ($sqlWhere)
267 {
268 if ($subWhere)
269 $subWhere .= "\nAND ".$sqlWhere;
270 else
271 $subWhere .= $sqlWhere;
272 }
273
274 $i++;
275 }
276 $fcJoin = "
277 INNER JOIN (
278 SELECT FC0.ELEMENT_ID
279 $subJoin
280 WHERE
281 $subWhere
282 ) FC on FC.ELEMENT_ID = BE.ID
283 ";
284 }
285 else
286 {
287 $condition = array();
288 foreach ($where as $facetWhere)
289 {
290 $sqlWhere = $this->whereToSql($facetWhere, "FC0");
291 if ($sqlWhere)
292 $condition[] = $sqlWhere;
293 }
294 $fcJoin = "
295 INNER JOIN (
296 SELECT FC0.ELEMENT_ID
297 FROM ".$this->storage->getTableName()." FC0
298 WHERE FC0.SECTION_ID = ".$this->sectionId."
299 AND (
300 (".implode(")OR(", $condition).")
301 )
302 GROUP BY FC0.ELEMENT_ID
303 HAVING count(DISTINCT FC0.FACET_ID) = ".count($condition)."
304 ) FC on FC.ELEMENT_ID = BE.ID
305 ";
306 }
307
308 $sqlUnion[] = "
309 SELECT
310 F.FACET_ID
311 ,F.VALUE
312 ,MIN(F.VALUE_NUM) MIN_VALUE_NUM
313 ,MAX(F.VALUE_NUM) MAX_VALUE_NUM
314 ".($connection instanceof \Bitrix\Main\DB\MysqlCommonConnection
315 ?",MAX(case when LOCATE('.', F.VALUE_NUM) > 0 then LENGTH(SUBSTRING_INDEX(F.VALUE_NUM, '.', -1)) else 0 end)"
316 :",MAX(".$sqlHelper->getLengthFunction("ABS(F.VALUE_NUM) - FLOOR(ABS(F.VALUE_NUM))")."+1-".$sqlHelper->getLengthFunction("0.1").")"
317 )." VALUE_FRAC_LEN
318 ,COUNT(DISTINCT F.ELEMENT_ID) ELEMENT_COUNT
319 FROM
320 ".$this->storage->getTableName()." F
321 INNER JOIN (
322 SELECT BE.ID
323 FROM
324 ".($elementFrom
325 ?$elementFrom
326 :"b_iblock_element BE"
327 )."
328 ".$fcJoin."
329 WHERE ".implode(" AND ", $sqlSearch)."
330 ".$elementWhere."
331 ) E ON E.ID = F.ELEMENT_ID
332 WHERE
333 F.SECTION_ID = ".$this->sectionId."
334 and F.FACET_ID in (".implode(",", $facetFilter).")
335 GROUP BY
336 F.FACET_ID, F.VALUE
337 ";
338 }
339
340 $result = $connection->query(implode("\nUNION ALL\n", $sqlUnion));
341
342 return $result;
343 }
344
352 protected function getFacetFilter(array $facetTypes)
353 {
354 $facetFilter = array();
355 foreach ($this->getSectionFilterProperty($this->sectionId) as $propertyId => $propertyType)
356 {
357 if (!$facetTypes || in_array($propertyType, $facetTypes))
358 {
359 $facetFilter[] = $propertyId * 2;
360 }
361 }
362 if (!$facetTypes || in_array(Storage::PRICE, $facetTypes))
363 {
364 foreach ($this->getFilterPrices() as $priceId)
365 {
366 $facetFilter[] = 1 + $priceId * 2;
367 }
368 }
369 return $facetFilter;
370 }
371
379 protected function getWhere($facetToExclude)
380 {
381 $where = array();
382 foreach ($this->where as $facetWhere)
383 {
384 if ($facetWhere["FACET_ID"] != $facetToExclude)
385 $where[] = $facetWhere;
386 }
387 return $where;
388 }
389
399 public function whereToSql(array $where, $tableAlias, $subsectionsCondition = "")
400 {
401 $sqlWhere = "";
402 $sectionCondition = "$tableAlias.SECTION_ID = ".$this->sectionId;
403 $facetCondition = "$tableAlias.FACET_ID = ".$where["FACET_ID"];
404 switch ($where["TYPE"])
405 {
407 case Storage::STRING:
408 if ($where["OP"] == "=")
409 {
410 $sqlWhere = $sectionCondition
411 ." AND ".$facetCondition
412 ." AND $tableAlias.VALUE_NUM = 0"
413 ." AND $tableAlias.VALUE in (".implode(", ", $where["VALUES"]).")"
414 ;
415 }
416 break;
417 case Storage::NUMERIC:
418 if ($where["OP"] == ">=" || $where["OP"] == "<=")
419 {
420 $sqlWhere = $sectionCondition
421 ." AND ".$facetCondition
422 ." AND $tableAlias.VALUE_NUM ".$where["OP"]." ".$where["VALUES"][0]
423 ." AND $tableAlias.VALUE = 0"
424 ;
425 }
426 elseif ($where["OP"] == "><")
427 {
428 $sqlWhere = $sectionCondition
429 ." AND ".$facetCondition
430 ." AND $tableAlias.VALUE_NUM BETWEEN ".$where["VALUES"][0]." AND ".$where["VALUES"][1]
431 ." AND $tableAlias.VALUE = 0"
432 ;
433 }
434 break;
436 if ($where["OP"] == ">=" || $where["OP"] == "<=")
437 {
438 $sqlWhere = $sectionCondition
439 ." AND ".$facetCondition
440 ." AND $tableAlias.VALUE_NUM ".$where["OP"]." ".$where["VALUES"][0]
441 ;
442 }
443 elseif ($where["OP"] == "><")
444 {
445 $sqlWhere = $sectionCondition
446 ." AND ".$facetCondition
447 ." AND $tableAlias.VALUE_NUM BETWEEN ".$where["VALUES"][0]." AND ".$where["VALUES"][1]
448 ;
449 }
450 elseif ($where["OP"] == "=")
451 {
452 $sqlWhere = $sectionCondition
453 ." AND ".$facetCondition
454 ." AND $tableAlias.VALUE_NUM in (".implode(", ", $where["VALUES"]).")"
455 ;
456 }
457 break;
458 case Storage::PRICE: //TODO AND FC.VALUE = 0
459 if (($where["OP"] == ">=" || $where["OP"] == "<="))
460 {
461 $sqlWhere = $sectionCondition
462 ." AND ".$facetCondition
463 ;
464 if ($this->toCurrencyId && $this->convertCurrencyId)
465 {
466 $sqlOr = array();
467 foreach ($this->convertCurrencyId as $currency => $currencyDictionaryId)
468 {
469 $convertedPrice = \CCurrencyRates::convertCurrency($where["VALUES"][0], $this->toCurrencyId, $currency);
470 $sqlOr[] = "($tableAlias.VALUE = $currencyDictionaryId AND $tableAlias.VALUE_NUM ".$where["OP"]." $convertedPrice)";
471 }
472 $sqlWhere .= " AND (".implode(" OR ", $sqlOr).")";
473 }
474 else
475 {
476 $sqlWhere .= " AND $tableAlias.VALUE_NUM ".$where["OP"]." ".$where["VALUES"][0];
477 }
478 }
479 elseif ($where["OP"] == "><")
480 {
481 $sqlWhere = $sectionCondition
482 ." AND ".$facetCondition
483 ;
484 if ($this->toCurrencyId && $this->convertCurrencyId)
485 {
486 $sqlOr = array();
487 foreach ($this->convertCurrencyId as $currency => $currencyDictionaryId)
488 {
489 $convertedPrice1 = \CCurrencyRates::convertCurrency($where["VALUES"][0], $this->toCurrencyId, $currency);
490 $convertedPrice2 = \CCurrencyRates::convertCurrency($where["VALUES"][1], $this->toCurrencyId, $currency);
491 $sqlOr[] = "($tableAlias.VALUE = $currencyDictionaryId AND $tableAlias.VALUE_NUM BETWEEN $convertedPrice1 AND $convertedPrice2)";
492 }
493 $sqlWhere .= " AND (".implode(" OR ", $sqlOr).")";
494 }
495 else
496 {
497 $sqlWhere .= " AND $tableAlias.VALUE_NUM BETWEEN ".$where["VALUES"][0]." AND ".$where["VALUES"][1];
498 }
499 }
500 break;
501 }
502
503 if ($sqlWhere && $subsectionsCondition !== '')
504 {
505 $sqlWhere .= " and ".$tableAlias.".".$subsectionsCondition;
506 }
507
508 return $sqlWhere;
509 }
510
523 {
524 if (!isset($this->sectionFilter[$sectionId]))
525 {
526 $properties = array();
527 foreach(\CIBlockSectionPropertyLink::getArray($this->iblockId, $sectionId) as $PID => $link)
528 {
529 if($link["SMART_FILTER"] === "Y")
530 {
531 if ($link["PROPERTY_TYPE"] === "N")
532 $properties[$link["PROPERTY_ID"]] = Storage::NUMERIC;
533 elseif ($link["USER_TYPE"] === "DateTime")
534 $properties[$link["PROPERTY_ID"]] = Storage::DATETIME;
535 elseif ($link["PROPERTY_TYPE"] === "S")
536 $properties[$link["PROPERTY_ID"]] = Storage::STRING;
537 else
538 $properties[$link["PROPERTY_ID"]] = Storage::DICTIONARY;
539 }
540 }
541 if ($this->skuIblockId)
542 {
543 foreach(\CIBlockSectionPropertyLink::getArray($this->skuIblockId, $sectionId) as $PID => $link)
544 {
545 if($link["SMART_FILTER"] === "Y")
546 {
547 if ($link["PROPERTY_TYPE"] === "N")
548 $properties[$link["PROPERTY_ID"]] = Storage::NUMERIC;
549 elseif ($link["USER_TYPE"] === "DateTime")
550 $properties[$link["PROPERTY_ID"]] = Storage::DATETIME;
551 elseif ($link["PROPERTY_TYPE"] === "S")
552 $properties[$link["PROPERTY_ID"]] = Storage::STRING;
553 else
554 $properties[$link["PROPERTY_ID"]] = Storage::DICTIONARY;
555 }
556 }
557 }
558 $this->sectionFilter[$sectionId] = $properties;
559 }
560 return $this->sectionFilter[$sectionId];
561 }
562
572 public function setSectionId($sectionId)
573 {
574 $this->sectionId = intval($sectionId);
575 return $this;
576 }
577
587 public function setPrices(array $prices)
588 {
589 $this->priceFilter = array();
590 foreach ($prices as $priceInfo)
591 {
592 $this->priceFilter[] = (int)$priceInfo["ID"];
593 }
594 return $this;
595 }
601 protected function getFilterPrices()
602 {
603 if (!isset($this->priceFilter))
604 {
605 $this->priceFilter = array();
606 if (self::$catalog)
607 {
608 $priceList = Catalog\GroupTable::getList(array(
609 'select' => array('ID'),
610 'order' => array('ID' => 'ASC')
611 ));
612 while($price = $priceList->fetch())
613 {
614 $this->priceFilter[] = (int)$price['ID'];
615 }
616 unset($price, $priceList);
617 }
618 }
619 return $this->priceFilter;
620 }
621
631 public function addNumericPropertyFilter($propertyId, $operator, $value)
632 {
633 $facetId = $this->storage->propertyIdToFacetId($propertyId);
634 if ($operator == "<=" || $operator == ">=")
635 {
636 $this->where[$operator.$facetId] = array(
637 "TYPE" => Storage::NUMERIC,
638 "OP" => $operator,
639 "FACET_ID" => $facetId,
640 "VALUES" => array(doubleval($value)),
641 );
642 if (isset($this->where[">=".$facetId]) && isset($this->where["<=".$facetId]))
643 {
644 $this->where["><".$facetId] = array(
645 "TYPE" => Storage::NUMERIC,
646 "OP" => "><",
647 "FACET_ID" => $facetId,
648 "VALUES" => array(
649 $this->where[">=".$facetId]["VALUES"][0],
650 $this->where["<=".$facetId]["VALUES"][0]
651 ),
652 );
653 unset($this->where[">=".$facetId]);
654 unset($this->where["<=".$facetId]);
655 }
656 }
657 }
658
668 public function addPriceFilter($priceId, $operator, $value)
669 {
670 $facetId = $this->storage->priceIdToFacetId($priceId);
671 if ($operator == "<=" || $operator == ">=")
672 {
673 $this->where[$operator.$facetId] = array(
674 "TYPE" => Storage::PRICE,
675 "OP" => $operator,
676 "FACET_ID" => $facetId,
677 "VALUES" => array(doubleval($value)),
678 );
679 if (isset($this->where[">=".$facetId]) && isset($this->where["<=".$facetId]))
680 {
681 $this->where["><".$facetId] = array(
682 "TYPE" => Storage::PRICE,
683 "OP" => "><",
684 "FACET_ID" => $facetId,
685 "VALUES" => array(
686 $this->where[">=".$facetId]["VALUES"][0],
687 $this->where["<=".$facetId]["VALUES"][0]
688 ),
689 );
690 unset($this->where[">=".$facetId]);
691 unset($this->where["<=".$facetId]);
692 }
693 }
694 }
695
705 public function addDictionaryPropertyFilter($propertyId, $operator, $value)
706 {
707 $facetId = $this->storage->propertyIdToFacetId($propertyId);
708 if ($operator == "=")
709 {
710 if (isset($this->where[$facetId]))
711 {
712 $this->where[$facetId]["VALUES"][] = intval($value);
713 }
714 else
715 {
716 $this->where[$facetId] = array(
717 "TYPE" => Storage::DICTIONARY,
718 "OP" => $operator,
719 "FACET_ID" => $facetId,
720 "VALUES" => array(intval($value)),
721 );
722 }
723 }
724 }
725
735 public function addDatetimePropertyFilter($propertyId, $operator, $value)
736 {
737 $facetId = $this->storage->propertyIdToFacetId($propertyId);
738 if ($operator == "<=" || $operator == ">=")
739 {
740 $this->where[$operator.$facetId] = array(
741 "TYPE" => Storage::DATETIME,
742 "OP" => $operator,
743 "FACET_ID" => $facetId,
744 "VALUES" => array(intval($value)),
745 );
746 if (isset($this->where[">=".$facetId]) && isset($this->where["<=".$facetId]))
747 {
748 $this->where["><".$facetId] = array(
749 "TYPE" => Storage::DATETIME,
750 "OP" => "><",
751 "FACET_ID" => $facetId,
752 "VALUES" => array(
753 $this->where[">=".$facetId]["VALUES"][0],
754 $this->where["<=".$facetId]["VALUES"][0]
755 ),
756 );
757 unset($this->where[">=".$facetId]);
758 unset($this->where["<=".$facetId]);
759 }
760 }
761 elseif ($operator == "=")
762 {
763 if (isset($this->where[$operator.$facetId]))
764 {
765 $this->where[$operator.$facetId]["VALUES"][] = intval($value);
766 }
767 else
768 {
769 $this->where[$operator.$facetId] = array(
770 "TYPE" => Storage::DATETIME,
771 "OP" => $operator,
772 "FACET_ID" => $facetId,
773 "VALUES" => array(intval($value)),
774 );
775 }
776 }
777 }
778
784 public function isEmptyWhere()
785 {
786 return empty($this->where);
787 }
788
799 {
800 if (\Bitrix\Main\Loader::includeModule('currency'))
801 {
802 $this->toCurrencyId = $toCurrencyId;
803 $this->convertCurrencyId = array();
804 foreach ($convertCurrencyId as $currency)
805 {
806 $currencyDictionaryId = $this->dictionary->getStringId($currency, false);
807 if ($currency)
808 $this->convertCurrencyId[$currency] = $currencyDictionaryId;
809 }
810 }
811 }
812}
addDictionaryPropertyFilter($propertyId, $operator, $value)
Definition facet.php:705
enableCurrencyConversion($toCurrencyId, array $convertCurrencyId)
Definition facet.php:798
whereToSql(array $where, $tableAlias, $subsectionsCondition="")
Definition facet.php:399
addDatetimePropertyFilter($propertyId, $operator, $value)
Definition facet.php:735
getFacetFilter(array $facetTypes)
Definition facet.php:352
addNumericPropertyFilter($propertyId, $operator, $value)
Definition facet.php:631
query(array $filter, array $facetTypes=array(), $facetId=0)
Definition facet.php:145
addPriceFilter($priceId, $operator, $value)
Definition facet.php:668