Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
querybuilder.php
1<?php
8
10{
12 protected $facet = null;
14 protected $dictionary = null;
16 protected $storage = null;
17
18 protected $sectionFilter = null;
19 protected $priceFilter = null;
20 protected $distinct = false;
21 protected $options = array();
22
26 public function __construct($iblockId)
27 {
28 $this->facet = new Facet($iblockId);
29 $this->dictionary = $this->facet->getDictionary();
30 $this->storage = $this->facet->getStorage();
31 }
32
38 public function isValid()
39 {
40 return $this->facet->isValid();
41 }
42
48 public function getDistinct()
49 {
50 return $this->distinct;
51 }
52
61 public function getFilterSql(&$filter, &$sqlSearch)
62 {
63 if (array_key_exists("FACET_OPTIONS", $filter))
64 {
65 if (is_array($filter["FACET_OPTIONS"]))
66 {
67 $this->options = $filter["FACET_OPTIONS"];
68 }
69 unset($filter["FACET_OPTIONS"]);
70 }
71
72 $this->distinct = false;
73 $fcJoin = "";
74 $toUnset = array();
75 if (
76 isset($filter["IBLOCK_ID"]) && !is_array($filter["IBLOCK_ID"]) && $filter["IBLOCK_ID"] > 0
77 && (
78 (isset($filter["SECTION_ID"]) && !is_array($filter["SECTION_ID"]) && $filter["SECTION_ID"] > 0)
79 || ($this->options && !isset($filter["SECTION_ID"]))
80 )
81 && isset($filter["ACTIVE"]) && $filter["ACTIVE"] === "Y"
82 )
83 {
84 $where = array();
85 $toUnset[] = array(&$filter, "SECTION_ID");
86
87 if (isset($filter["INCLUDE_SUBSECTIONS"]) && $filter["INCLUDE_SUBSECTIONS"] === "Y")
88 {
89 $subsectionsCondition = "";
90 $toUnset[] = array(&$filter, "INCLUDE_SUBSECTIONS");
91 }
92 else
93 {
94 $subsectionsCondition = "INCLUDE_SUBSECTIONS=1";
95 if (array_key_exists("INCLUDE_SUBSECTIONS", $filter))
96 $toUnset[] = array(&$filter, "INCLUDE_SUBSECTIONS");
97 }
98
99 $hasAdditionalFilters = false;
100 $this->fillWhere($where, $hasAdditionalFilters, $toUnset, $filter);
101
102 if (!$where)
103 {
104 $where[] = array(
105 "TYPE" => Storage::DICTIONARY,
106 "OP" => "=",
107 "FACET_ID" => 1,
108 "VALUES" => array(0),
109 );
110 }
111
112 if (
113 isset($filter["=ID"]) && is_object($filter["=ID"])
114 && $filter["=ID"]->arFilter["IBLOCK_ID"] == $this->facet->getSkuIblockId()
115 && $filter["=ID"]->strField === "PROPERTY_".$this->facet->getSkuPropertyId()
116 )
117 {
118 $hasAdditionalFilters = false;
119 $this->fillWhere($where, $hasAdditionalFilters, $toUnset, $filter["=ID"]->arFilter);
120 if (!$hasAdditionalFilters)
121 {
122 $toUnset[] = array(&$filter, "=ID");
123 }
124 }
125
126 if ($where)
127 {
128 $filter["SECTION_ID"] = (isset($filter["SECTION_ID"]) ? (int)$filter["SECTION_ID"] : 0);
129 $this->facet->setSectionId($filter["SECTION_ID"]);
130 if ($this->options)
131 {
132 if (isset($this->options["CURRENCY_CONVERSION"]) && $this->options["CURRENCY_CONVERSION"])
133 {
134 $this->facet->enableCurrencyConversion(
135 $this->options["CURRENCY_CONVERSION"]["TO"] ?? '',
136 $this->options["CURRENCY_CONVERSION"]["FROM"] ?? ''
137 );
138 }
139 }
140 $distinctSelectCapable = (\Bitrix\Main\Application::getConnection()->getType() == "mysql");
141 if (count($where) == 1 && $distinctSelectCapable)
142 {
143 $this->distinct = true;
144 $fcJoin = "INNER JOIN ".$this->storage->getTableName()." FC on FC.ELEMENT_ID = BE.ID";
145 foreach ($where as $facetFilter)
146 {
147 $sqlWhere = $this->facet->whereToSql($facetFilter, "FC", $subsectionsCondition);
148 if ($sqlWhere)
149 $sqlSearch[] = $sqlWhere;
150 }
151 }
152 elseif (count($where) <= 5)
153 {
154 $subJoin = "";
155 $subWhere = "";
156 $i = 0;
157 foreach ($where as $facetFilter)
158 {
159 if ($i == 0)
160 $subJoin .= "FROM ".$this->storage->getTableName()." FC$i\n";
161 else
162 $subJoin .= "INNER JOIN ".$this->storage->getTableName()." FC$i ON FC$i.ELEMENT_ID = FC0.ELEMENT_ID\n";
163
164 $sqlWhere = $this->facet->whereToSql($facetFilter, "FC$i", $subsectionsCondition);
165 if ($sqlWhere)
166 {
167 if ($subWhere)
168 $subWhere .= "\nAND ".$sqlWhere;
169 else
170 $subWhere .= $sqlWhere;
171 }
172
173 $i++;
174 }
175
176 $fcJoin = "
177 INNER JOIN (
178 SELECT ".($distinctSelectCapable? "DISTINCT": "")." FC0.ELEMENT_ID
179 $subJoin
180 WHERE
181 $subWhere
182 ) FC on FC.ELEMENT_ID = BE.ID
183 ";
184 }
185 else
186 {
187 $condition = array();
188 foreach ($where as $facetFilter)
189 {
190 $sqlWhere = $this->facet->whereToSql($facetFilter, "FC0", $subsectionsCondition);
191 if ($sqlWhere)
192 $condition[] = $sqlWhere;
193 }
194 $fcJoin = "
195 INNER JOIN (
196 SELECT FC0.ELEMENT_ID
197 FROM ".$this->storage->getTableName()." FC0
198 WHERE FC0.SECTION_ID = ".$filter["SECTION_ID"]."
199 AND (
200 (".implode(")OR(", $condition).")
201 )
202 GROUP BY FC0.ELEMENT_ID
203 HAVING count(DISTINCT FC0.FACET_ID) = ".count($condition)."
204 ) FC on FC.ELEMENT_ID = BE.ID
205 ";
206 }
207
208 foreach ($toUnset as $command)
209 {
210 unset($command[0][$command[1]]);
211 }
212 }
213 else
214 {
215 $fcJoin = "";
216 }
217 }
218 return $fcJoin;
219 }
220
231 private function fillWhere(&$where, &$hasAdditionalFilters, &$toUnset, &$filter)
232 {
233 $countUnset = count($toUnset);
234 $properties = null;
235 $propertyCodeMap = null;
236
237 $usePriceFilter = isset($this->options['PRICE_FILTER']) && $this->options['PRICE_FILTER'];
238
239 foreach ($filter as $filterKey => $filterValue)
240 {
241 if (preg_match("/^(=)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
242 {
243 if ($properties === null)
244 $properties = $this->getFilterProperty();
245 if ($propertyCodeMap === null)
246 {
247 $propertyCodeMap = $this->getPropertyCodeMap();
248 }
249
250 foreach ($filterValue as $propertyId => $value)
251 {
252 $propertyId = $propertyCodeMap[$propertyId] ?? null;
253 if (
254 $propertyId === null
255 || !isset($properties[$propertyId])
256 )
257 {
258 continue;
259 }
260 $facetId = $this->storage->propertyIdToFacetId($propertyId);
261 if ($properties[$propertyId] == Storage::DICTIONARY || $properties[$propertyId] == Storage::STRING)
262 {
263 $sqlValues = $this->getInSql($value, $properties[$propertyId] == Storage::STRING);
264 if ($sqlValues)
265 {
266 $where[] = array(
267 "TYPE" => $properties[$propertyId],
268 "OP" => $keyDetails[1],
269 "FACET_ID" => $facetId,
270 "VALUES" => $sqlValues,
271 );
272 $toUnset[] = array(&$filter[$filterKey], $propertyId);
273 }
274 }
275 }
276 }
277 elseif (preg_match("/^(=)PROPERTY_(\\d+)\$/i", $filterKey, $keyDetails))
278 {
279 if ($properties === null)
280 $properties = $this->getFilterProperty();
281 if ($propertyCodeMap === null)
282 {
283 $propertyCodeMap = $this->getPropertyCodeMap();
284 }
285
286 $propertyId = $propertyCodeMap[$keyDetails[2]] ?? null;
287 if (
288 $propertyId === null
289 || !isset($properties[$propertyId])
290 )
291 {
292 continue;
293 }
294 $value = $filterValue;
295 $facetId = $this->storage->propertyIdToFacetId($propertyId);
296 if ($properties[$propertyId] == Storage::DICTIONARY || $properties[$propertyId] == Storage::STRING)
297 {
298 $sqlValues = $this->getInSql($value, $properties[$propertyId] == Storage::STRING);
299 if ($sqlValues)
300 {
301 $where[] = array(
302 "TYPE" => $properties[$propertyId],
303 "OP" => $keyDetails[1],
304 "FACET_ID" => $facetId,
305 "VALUES" => $sqlValues,
306 );
307 $toUnset[] = array(&$filter, $filterKey);
308 }
309 }
310 }
311 elseif (preg_match("/^(>=|<=)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
312 {
313 if ($properties === null)
314 $properties = $this->getFilterProperty();
315 if ($propertyCodeMap === null)
316 {
317 $propertyCodeMap = $this->getPropertyCodeMap();
318 }
319
320 foreach ($filterValue as $propertyId => $value)
321 {
322 $propertyId = $propertyCodeMap[$propertyId] ?? null;
323 if (
324 $propertyId === null
325 || !isset($properties[$propertyId])
326 )
327 {
328 continue;
329 }
330 $facetId = $this->storage->propertyIdToFacetId($propertyId);
331 if ($properties[$propertyId] == Storage::NUMERIC)
332 {
333 if (is_array($value))
334 $doubleValue = doubleval(current($value));
335 else
336 $doubleValue = doubleval($value);
337 $where[] = array(
338 "TYPE" => Storage::NUMERIC,
339 "OP" => $keyDetails[1],
340 "FACET_ID" => $facetId,
341 "VALUES" => array($doubleValue),
342 );
343 $toUnset[] = array(&$filter[$filterKey], $propertyId);
344 }
345 elseif ($properties[$propertyId] == Storage::DATETIME)
346 {
347 if (is_array($value))
348 $timestamp = MakeTimeStamp(current($value), "YYYY-MM-DD HH:MI:SS");
349 else
350 $timestamp = MakeTimeStamp($value, "YYYY-MM-DD HH:MI:SS");
351 $where[] = array(
352 "TYPE" => Storage::DATETIME,
353 "OP" => $keyDetails[1],
354 "FACET_ID" => $facetId,
355 "VALUES" => array($timestamp),
356 );
357 $toUnset[] = array(&$filter[$filterKey], $propertyId);
358 }
359 }
360 }
361 elseif (preg_match("/^(><)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
362 {
363 if ($properties === null)
364 $properties = $this->getFilterProperty();
365 if ($propertyCodeMap === null)
366 {
367 $propertyCodeMap = $this->getPropertyCodeMap();
368 }
369
370 foreach ($filterValue as $propertyId => $value)
371 {
372 $propertyId = $propertyCodeMap[$propertyId] ?? null;
373 if (
374 $propertyId === null
375 || !isset($properties[$propertyId])
376 )
377 {
378 continue;
379 }
380 $facetId = $this->storage->propertyIdToFacetId($propertyId);
381 if ($properties[$propertyId] == Storage::NUMERIC)
382 {
383 if (is_array($value) && count($value) == 2)
384 {
385 $doubleMinValue = doubleval(current($value));
386 $doubleMaxValue = doubleval(end($value));
387 $where[] = array(
388 "TYPE" => Storage::NUMERIC,
389 "OP" => $keyDetails[1],
390 "FACET_ID" => $facetId,
391 "VALUES" => array($doubleMinValue, $doubleMaxValue),
392 );
393 $toUnset[] = array(&$filter[$filterKey], $propertyId);
394 }
395 }
396 elseif ($properties[$propertyId] == Storage::DATETIME)
397 {
398 if (is_array($value) && count($value) == 2)
399 {
400 $timestamp1 = MakeTimeStamp(current($value), "YYYY-MM-DD HH:MI:SS");
401 $timestamp2 = MakeTimeStamp(end($value), "YYYY-MM-DD HH:MI:SS");
402 $where[] = array(
403 "TYPE" => Storage::DATETIME,
404 "OP" => $keyDetails[1],
405 "FACET_ID" => $facetId,
406 "VALUES" => array($timestamp1, $timestamp2),
407 );
408 $toUnset[] = array(&$filter[$filterKey], $propertyId);
409 }
410 }
411 }
412 }
413 elseif (
414 $usePriceFilter
415 && preg_match("/^(>=|<=)(?:CATALOG_|)PRICE_(\\d+)\$/i", $filterKey, $keyDetails)
416 && !is_array($filterValue)
417 )
418 {
419 $priceId = $keyDetails[2];
420 $value = $filterValue;
421 $facetId = $this->storage->priceIdToFacetId($priceId);
422 $doubleValue = doubleval($value);
423 $where[] = array(
424 "TYPE" => Storage::PRICE,
425 "OP" => $keyDetails[1],
426 "FACET_ID" => $facetId,
427 "VALUES" => array($doubleValue),
428 );
429 $toUnset[] = array(&$filter, $filterKey);
430 }
431 elseif (
432 $usePriceFilter
433 && preg_match("/^(><)(?:CATALOG_|)PRICE_(\\d+)\$/i", $filterKey, $keyDetails)
434 && is_array($filterValue)
435 )
436 {
437 $priceId = $keyDetails[2];
438 $value = $filterValue;
439 $facetId = $this->storage->priceIdToFacetId($priceId);
440 $doubleValueMin = doubleval($value[0]);
441 $doubleValueMax = doubleval($value[1]);
442 $where[] = array(
443 "TYPE" => Storage::PRICE,
444 "OP" => $keyDetails[1],
445 "FACET_ID" => $facetId,
446 "VALUES" => array($doubleValueMin, $doubleValueMax),
447 );
448 $toUnset[] = array(&$filter, $filterKey);
449 }
450 elseif (
451 $usePriceFilter
452 && is_numeric($filterKey)
453 && is_array($filterValue) && count($filterValue) === 3
454 && isset($filterValue["LOGIC"]) && $filterValue["LOGIC"] === "OR"
455 && isset($filterValue["=ID"]) && is_object($filterValue["=ID"])
456 && preg_match("/^(>=|<=)(?:CATALOG_|)PRICE_(\\d+)\$/i", key($filterValue[0][0]), $keyDetails)
457 && !is_array(current($filterValue[0][0]))
458 )
459 {
460 $priceId = $keyDetails[2];
461 $value = current($filterValue[0][0]);
462 $facetId = $this->storage->priceIdToFacetId($priceId);
463 $doubleValue = doubleval($value);
464 $where[] = array(
465 "TYPE" => Storage::PRICE,
466 "OP" => $keyDetails[1],
467 "FACET_ID" => $facetId,
468 "VALUES" => array($doubleValue),
469 );
470 $toUnset[] = array(&$filter, $filterKey);
471 $toUnset[] = array(&$filter, "CATALOG_SHOP_QUANTITY_".$priceId);
472 }
473 elseif (
474 $usePriceFilter
475 && is_numeric($filterKey)
476 && is_array($filterValue) && count($filterValue) === 3
477 && isset($filterValue["LOGIC"]) && $filterValue["LOGIC"] === "OR"
478 && isset($filterValue["=ID"]) && is_object($filterValue["=ID"])
479 && preg_match("/^(><)(?:CATALOG_|)PRICE_(\\d+)\$/i", key($filterValue[0][0]), $keyDetails)
480 && is_array(current($filterValue[0][0]))
481 )
482 {
483 $priceId = $keyDetails[2];
484 $value = current($filterValue[0][0]);
485 $facetId = $this->storage->priceIdToFacetId($priceId);
486 $doubleValueMin = doubleval($value[0]);
487 $doubleValueMax = doubleval($value[1]);
488 $where[] = array(
489 "TYPE" => Storage::PRICE,
490 "OP" => $keyDetails[1],
491 "FACET_ID" => $facetId,
492 "VALUES" => array($doubleValueMin, $doubleValueMax),
493 );
494 $toUnset[] = array(&$filter, $filterKey);
495 $toUnset[] = array(&$filter, "CATALOG_SHOP_QUANTITY_".$priceId);
496 }
497 elseif (
498 $filterKey !== "IBLOCK_ID"
499 && $filterKey !== "ACTIVE"
500 && $filterKey !== "ACTIVE_DATE"
501 )
502 {
503 $hasAdditionalFilters = true;
504 }
505 }
506 if ($hasAdditionalFilters)
507 {
508 while (count($toUnset) > $countUnset)
509 {
510 array_pop($toUnset);
511 }
512 }
513 }
514
523 protected function getInSql($value, $lookup)
524 {
525 $result = array();
526
527 if (is_array($value))
528 {
529 foreach ($value as $val)
530 {
531 if ((string)$val <> '')
532 {
533 if ($lookup)
534 {
535 $result[] = $this->dictionary->getStringId($val, false);
536 }
537 else
538 {
539 $result[] = (int)$val;
540 }
541 }
542 }
543 }
544 elseif ((string)$value <> '')
545 {
546 if ($lookup)
547 {
548 $result[] = $this->dictionary->getStringId($value, false);
549 }
550 else
551 {
552 $result[] = (int)$value;
553 }
554 }
555
556 return $result;
557 }
558
566 private function getFilterProperty(): array
567 {
568 //TODO: remove this code to \Bitrix\Iblock\Model\Property
569 if (!isset($this->propertyFilter))
570 {
571 $this->propertyFilter = array();
572 $propertyList = \Bitrix\Iblock\SectionPropertyTable::getList(array(
573 "select" => array("PROPERTY_ID", "PROPERTY.PROPERTY_TYPE", "PROPERTY.USER_TYPE"),
574 "filter" => array(
575 "=IBLOCK_ID" => array($this->facet->getIblockId(), $this->facet->getSkuIblockId()),
576 "=SMART_FILTER" => "Y",
577 ),
578 ));
579 while ($link = $propertyList->fetch())
580 {
581 if ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_PROPERTY_TYPE"] === "N")
582 $this->propertyFilter[$link["PROPERTY_ID"]] = Storage::NUMERIC;
583 elseif ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_USER_TYPE"] === "DateTime")
584 $this->propertyFilter[$link["PROPERTY_ID"]] = Storage::DATETIME;
585 elseif ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_PROPERTY_TYPE"] === "S")
586 $this->propertyFilter[$link["PROPERTY_ID"]] = Storage::STRING;
587 else
588 $this->propertyFilter[$link["PROPERTY_ID"]] = Storage::DICTIONARY;
589 }
590 }
591 return $this->propertyFilter;
592 }
593
594 private function getPropertyCodeMap(): array
595 {
596 $result = [];
597
598 $iterator = \Bitrix\Iblock\PropertyTable::getList([
599 'select' => [
600 'ID',
601 'CODE',
602 ],
603 'filter' => [
604 '=IBLOCK_ID' => $this->facet->getIblockId(),
605 ],
606 ]);
607 while ($row = $iterator->fetch())
608 {
609 $id = (int)$row['ID'];
610 $result[$id] = $id;
611 $row['CODE'] = (string)$row['CODE'];
612 if ($row['CODE'] !== '')
613 {
614 $result[$row['CODE']] = $id;
615 }
616 }
617 unset($iterator);
618
619 $skuIblockId = $this->facet->getSkuIblockId();
620 if ($skuIblockId > 0)
621 {
622 $iterator = \Bitrix\Iblock\PropertyTable::getList([
623 'select' => [
624 'ID',
625 ],
626 'filter' => [
627 '=IBLOCK_ID' => $skuIblockId,
628 ],
629 ]);
630 while ($row = $iterator->fetch())
631 {
632 $id = (int)$row['ID'];
633 $result[$id] = $id;
634 }
635 unset($iterator);
636 }
637
638 return $result;
639 }
640}