Your IP : 18.119.112.22


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/iblock/lib/propertyindex/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/iblock/lib/propertyindex/querybuilder.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage iblock
 */
namespace Bitrix\Iblock\PropertyIndex;

class QueryBuilder
{
	/** @var \Bitrix\Iblock\PropertyIndex\Facet */
	protected $facet = null;
	/** @var \Bitrix\Iblock\PropertyIndex\Dictionary */
	protected $dictionary = null;
	/** @var \Bitrix\Iblock\PropertyIndex\Storage  */
	protected $storage = null;

	protected $sectionFilter = null;
	protected $priceFilter = null;
	protected $distinct = false;
	protected $options = array();

	/**
	 * @param integer $iblockId Information block identifier.
	 */
	public function __construct($iblockId)
	{
		$this->facet = new Facet($iblockId);
		$this->dictionary = $this->facet->getDictionary();
		$this->storage = $this->facet->getStorage();
	}

	/**
	 * Returns true if filter rewrite is possible.
	 *
	 * @return boolean
	 */
	public function isValid()
	{
		return $this->facet->isValid();
	}

	/**
	 * Returns true if filter needs distinct sql clause.
	 *
	 * @return boolean
	 */
	public function getDistinct()
	{
		return $this->distinct;
	}

	/**
	 * Returns filter join with index tables.
	 *
	 * @param array &$filter Filter which may be rewritten.
	 * @param array &$sqlSearch Additional result of rewrite.
	 *
	 * @return string
	 */
	public function getFilterSql(&$filter, &$sqlSearch)
	{
		if (array_key_exists("FACET_OPTIONS", $filter))
		{
			if (is_array($filter["FACET_OPTIONS"]))
				$this->options = $filter["FACET_OPTIONS"];
			unset($filter["FACET_OPTIONS"]);
		}

		$this->distinct = false;
		$fcJoin = "";
		$toUnset = array();
		if (
			!is_array($filter["IBLOCK_ID"]) && $filter["IBLOCK_ID"] > 0
			&& (
				(!is_array($filter["SECTION_ID"]) && $filter["SECTION_ID"] > 0)
				|| ($this->options && !isset($filter["SECTION_ID"]))
			)
			&& isset($filter["ACTIVE"]) && $filter["ACTIVE"] === "Y"
		)
		{
			$where = array();
			$toUnset[] = array(&$filter, "SECTION_ID");

			if ($filter["INCLUDE_SUBSECTIONS"] === "Y")
			{
				$subsectionsCondition = "";
				$toUnset[] = array(&$filter, "INCLUDE_SUBSECTIONS");
			}
			else
			{
				$subsectionsCondition = "INCLUDE_SUBSECTIONS=1";
				if (array_key_exists("INCLUDE_SUBSECTIONS", $filter))
					$toUnset[] = array(&$filter, "INCLUDE_SUBSECTIONS");
			}

			$hasAdditionalFilters = false;
			$this->fillWhere($where, $hasAdditionalFilters, $toUnset, $filter);

			if (!$where)
			{
				$where[] = array(
					"TYPE" => Storage::DICTIONARY,
					"OP" => "=",
					"FACET_ID" => 1,
					"VALUES" => array(0),
				);
			}

			if (
				isset($filter["=ID"]) && is_object($filter["=ID"])
				&& $filter["=ID"]->arFilter["IBLOCK_ID"] == $this->facet->getSkuIblockId()
				&& $filter["=ID"]->strField === "PROPERTY_".$this->facet->getSkuPropertyId()
			)
			{
				$hasAdditionalFilters = false;
				$this->fillWhere($where, $hasAdditionalFilters, $toUnset, $filter["=ID"]->arFilter);
				if (!$hasAdditionalFilters)
				{
					$toUnset[] = array(&$filter, "=ID");
				}
			}

			if ($where)
			{
				$filter["SECTION_ID"] = (isset($filter["SECTION_ID"]) ? (int)$filter["SECTION_ID"] : 0);
				$this->facet->setSectionId($filter["SECTION_ID"]);
				if ($this->options)
				{
					if ($this->options["CURRENCY_CONVERSION"])
						$this->facet->enableCurrencyConversion($this->options["CURRENCY_CONVERSION"]["TO"], $this->options["CURRENCY_CONVERSION"]["FROM"]);
				}
				$distinctSelectCapable = (\Bitrix\Main\Application::getConnection()->getType() == "mysql");
				if (count($where) == 1 && $distinctSelectCapable)
				{
					$this->distinct = true;
					$fcJoin = "INNER JOIN ".$this->storage->getTableName()." FC on FC.ELEMENT_ID = BE.ID";
					foreach ($where as $facetFilter)
					{
						$sqlWhere = $this->facet->whereToSql($facetFilter, "FC", $subsectionsCondition);
						if ($sqlWhere)
							$sqlSearch[] = $sqlWhere;
					}
				}
				elseif (count($where) <= 5)
				{
					$subJoin = "";
					$subWhere = "";
					$i = 0;
					foreach ($where as $facetFilter)
					{
						if ($i == 0)
							$subJoin .= "FROM ".$this->storage->getTableName()." FC$i\n";
						else
							$subJoin .= "INNER JOIN ".$this->storage->getTableName()." FC$i ON FC$i.ELEMENT_ID = FC0.ELEMENT_ID\n";

						$sqlWhere = $this->facet->whereToSql($facetFilter, "FC$i", $subsectionsCondition);
						if ($sqlWhere)
						{
							if ($subWhere)
								$subWhere .= "\nAND ".$sqlWhere;
							else
								$subWhere .= $sqlWhere;
						}

						$i++;
					}

					$fcJoin = "
						INNER JOIN (
							SELECT ".($distinctSelectCapable? "DISTINCT": "")." FC0.ELEMENT_ID
							$subJoin
							WHERE
							$subWhere
						) FC on FC.ELEMENT_ID = BE.ID
					";
				}
				else
				{
					$condition = array();
					foreach ($where as $facetFilter)
					{
						$sqlWhere = $this->facet->whereToSql($facetFilter, "FC0", $subsectionsCondition);
						if ($sqlWhere)
							$condition[] = $sqlWhere;
					}
					$fcJoin = "
						INNER JOIN (
							SELECT FC0.ELEMENT_ID
							FROM ".$this->storage->getTableName()." FC0
							WHERE FC0.SECTION_ID = ".$filter["SECTION_ID"]."
							AND (
							(".implode(")OR(", $condition).")
							)
						GROUP BY FC0.ELEMENT_ID
						HAVING count(DISTINCT FC0.FACET_ID) = ".count($condition)."
						) FC on FC.ELEMENT_ID = BE.ID
					";
				}

				foreach ($toUnset as $command)
				{
					unset($command[0][$command[1]]);
				}
			}
			else
			{
				$fcJoin = "";
			}
		}
		return $fcJoin;
	}

	/**
	 * Goes through the $filter and creates unified conditions in $where array.
	 *
	 * @param array &$where Output result.
	 * @param boolean &$hasAdditionalFilters Whenever has some filters left or not.
	 * @param array &$toUnset Output $filter keys which may be excluded.
	 * @param array &$filter Filter to go through.
	 *
	 * @return void
	 */
	private function fillWhere(&$where, &$hasAdditionalFilters, &$toUnset, &$filter)
	{
		$properties = $this->getFilterProperty();
		foreach ($filter as $filterKey => $filterValue)
		{
			if (preg_match("/^(=)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
			{
				foreach ($filterValue as $propertyId => $value)
				{
					$facetId = $this->storage->propertyIdToFacetId($propertyId);
					if ($properties[$propertyId] == Storage::DICTIONARY || $properties[$propertyId] == Storage::STRING)
					{
						$sqlValues = $this->getInSql($value, $properties[$propertyId] == Storage::STRING);
						if ($sqlValues)
						{
							$where[] = array(
								"TYPE" => $properties[$propertyId],
								"OP" => $keyDetails[1],
								"FACET_ID" => $facetId,
								"VALUES" => $sqlValues,
							);
							$toUnset[] = array(&$filter[$filterKey], $propertyId);
						}
					}
				}
			}
			elseif (preg_match("/^(=)PROPERTY_(\\d+)\$/i", $filterKey, $keyDetails))
			{
				$propertyId = $keyDetails[2];
				$value = $filterValue;
				$facetId = $this->storage->propertyIdToFacetId($propertyId);
				if ($properties[$propertyId] == Storage::DICTIONARY || $properties[$propertyId] == Storage::STRING)
				{
					$sqlValues = $this->getInSql($value, $properties[$propertyId] == Storage::STRING);
					if ($sqlValues)
					{
						$where[] = array(
							"TYPE" => $properties[$propertyId],
							"OP" => $keyDetails[1],
							"FACET_ID" => $facetId,
							"VALUES" => $sqlValues,
						);
						$toUnset[] = array(&$filter, $filterKey);
					}
				}
			}
			elseif (preg_match("/^(>=|<=)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
			{
				foreach ($filterValue as $propertyId => $value)
				{
					$facetId = $this->storage->propertyIdToFacetId($propertyId);
					if ($properties[$propertyId] == Storage::NUMERIC)
					{
						if (is_array($value))
							$doubleValue = doubleval(current($value));
						else
							$doubleValue = doubleval($value);
						$where[] = array(
							"TYPE" => Storage::NUMERIC,
							"OP" => $keyDetails[1],
							"FACET_ID" => $facetId,
							"VALUES" => array($doubleValue),
						);
						$toUnset[] = array(&$filter[$filterKey], $propertyId);
					}
					elseif ($properties[$propertyId] == Storage::DATETIME)
					{
						if (is_array($value))
							$timestamp = MakeTimeStamp(current($value), "YYYY-MM-DD HH:MI:SS");
						else
							$timestamp = MakeTimeStamp($value, "YYYY-MM-DD HH:MI:SS");
						$where[] = array(
							"TYPE" => Storage::DATETIME,
							"OP" => $keyDetails[1],
							"FACET_ID" => $facetId,
							"VALUES" => array($timestamp),
						);
						$toUnset[] = array(&$filter[$filterKey], $propertyId);
					}
				}
			}
			elseif (preg_match("/^(><)PROPERTY\$/i", $filterKey, $keyDetails) && is_array($filterValue))
			{
				foreach ($filterValue as $propertyId => $value)
				{
					$facetId = $this->storage->propertyIdToFacetId($propertyId);
					if ($properties[$propertyId] == Storage::NUMERIC)
					{
						if (is_array($value) && count($value) == 2)
						{
							$doubleMinValue = doubleval(current($value));
							$doubleMaxValue = doubleval(end($value));
							$where[] = array(
								"TYPE" => Storage::NUMERIC,
								"OP" => $keyDetails[1],
								"FACET_ID" => $facetId,
								"VALUES" => array($doubleMinValue, $doubleMaxValue),
							);
							$toUnset[] = array(&$filter[$filterKey], $propertyId);
						}
					}
					elseif ($properties[$propertyId] == Storage::DATETIME)
					{
						if (is_array($value) && count($value) == 2)
						{
							$timestamp1 = MakeTimeStamp(current($value), "YYYY-MM-DD HH:MI:SS");
							$timestamp2 = MakeTimeStamp(end($value), "YYYY-MM-DD HH:MI:SS");
							$where[] = array(
								"TYPE" => Storage::DATETIME,
								"OP" => $keyDetails[1],
								"FACET_ID" => $facetId,
								"VALUES" => array($timestamp1, $timestamp2),
							);
							$toUnset[] = array(&$filter[$filterKey], $propertyId);
						}
					}
				}
			}
			elseif (
				$this->options["PRICE_FILTER"]
				&& preg_match("/^(>=|<=)CATALOG_PRICE_(\\d+)\$/i", $filterKey, $keyDetails)
				&& !is_array($filterValue)
			)
			{
				$priceId = $keyDetails[2];
				$value = $filterValue;
				$facetId = $this->storage->priceIdToFacetId($priceId);
				$doubleValue = doubleval($value);
				$where[] = array(
					"TYPE" => Storage::PRICE,
					"OP" => $keyDetails[1],
					"FACET_ID" => $facetId,
					"VALUES" => array($doubleValue),
				);
				$toUnset[] = array(&$filter, $filterKey);
			}
			elseif (
				$this->options["PRICE_FILTER"]
				&& preg_match("/^(><)CATALOG_PRICE_(\\d+)\$/i", $filterKey, $keyDetails)
				&& is_array($filterValue)
			)
			{
				$priceId = $keyDetails[2];
				$value = $filterValue;
				$facetId = $this->storage->priceIdToFacetId($priceId);
				$doubleValueMin = doubleval($value[0]);
				$doubleValueMax = doubleval($value[1]);
				$where[] = array(
					"TYPE" => Storage::PRICE,
					"OP" => $keyDetails[1],
					"FACET_ID" => $facetId,
					"VALUES" => array($doubleValueMin, $doubleValueMax),
				);
				$toUnset[] = array(&$filter, $filterKey);
			}
			elseif (
				$this->options["PRICE_FILTER"]
				&& is_numeric($filterKey)
				&& is_array($filterValue) && count($filterValue) === 3
				&& isset($filterValue["LOGIC"]) && $filterValue["LOGIC"] === "OR"
				&& isset($filterValue["=ID"]) && is_object($filterValue["=ID"])
				&& preg_match("/^(>=|<=)CATALOG_PRICE_(\\d+)\$/i", key($filterValue[0][0]), $keyDetails)
				&& !is_array(current($filterValue[0][0]))
			)
			{
				$priceId = $keyDetails[2];
				$value = current($filterValue[0][0]);
				$facetId = $this->storage->priceIdToFacetId($priceId);
				$doubleValue = doubleval($value);
				$where[] = array(
					"TYPE" => Storage::PRICE,
					"OP" => $keyDetails[1],
					"FACET_ID" => $facetId,
					"VALUES" => array($doubleValue),
				);
				$toUnset[] = array(&$filter, $filterKey);
				$toUnset[] = array(&$filter, "CATALOG_SHOP_QUANTITY_1");
			}
			elseif (
				$this->options["PRICE_FILTER"]
				&& is_numeric($filterKey)
				&& is_array($filterValue) && count($filterValue) === 3
				&& isset($filterValue["LOGIC"]) && $filterValue["LOGIC"] === "OR"
				&& isset($filterValue["=ID"]) && is_object($filterValue["=ID"])
				&& preg_match("/^(><)CATALOG_PRICE_(\\d+)\$/i", key($filterValue[0][0]), $keyDetails)
				&& is_array(current($filterValue[0][0]))
			)
			{
				$priceId = $keyDetails[2];
				$value = current($filterValue[0][0]);
				$facetId = $this->storage->priceIdToFacetId($priceId);
				$doubleValueMin = doubleval($value[0]);
				$doubleValueMax = doubleval($value[1]);
				$where[] = array(
					"TYPE" => Storage::PRICE,
					"OP" => $keyDetails[1],
					"FACET_ID" => $facetId,
					"VALUES" => array($doubleValueMin, $doubleValueMax),
				);
				$toUnset[] = array(&$filter, $filterKey);
				$toUnset[] = array(&$filter, "CATALOG_SHOP_QUANTITY_1");
			}
			elseif (
				$filterKey !== "IBLOCK_ID"
				&& $filterKey !== "ACTIVE"
				&& $filterKey !== "ACTIVE_DATE"
			)
			{
				$hasAdditionalFilters = true;
			}
		}
	}

	/**
	 * Returns array on integers representing values for sql query.
	 *
	 * @param mixed $value Value to be intvaled.
	 * @param boolean $lookup Whenever to convert the value from string to dictionary or not.
	 *
	 * @return integer[]
	 */
	protected function getInSql($value, $lookup)
	{
		$result = array();

		if (is_array($value))
		{
			foreach ($value as $val)
			{
				if (strlen($val) > 0)
				{
					if ($lookup)
					{
						$result[] = $this->dictionary->getStringId($val, false);
					}
					else
					{
						$result[] = intval($val);
					}
				}
			}
		}
		elseif (strlen($value) > 0)
		{
			if ($lookup)
			{
				$result[] = $this->dictionary->getStringId($value, false);
			}
			else
			{
				$result[] = intval($value);
			}
		}

		return $result;
	}

	/**
	 * Returns map of properties to their types.
	 * Properties of iblock and its sku returned
	 * which marked as for smart filter.
	 *
	 * @return integer[]
	 */
	private function getFilterProperty()
	{
		if (!isset($this->propertyFilter))
		{
			$this->propertyFilter = array();
			$propertyList = \Bitrix\Iblock\SectionPropertyTable::getList(array(
				"select" => array("PROPERTY_ID", "PROPERTY.PROPERTY_TYPE", "PROPERTY.USER_TYPE"),
				"filter" => array(
					"=IBLOCK_ID" => array($this->facet->getIblockId(), $this->facet->getSkuIblockId()),
					"=SMART_FILTER" => "Y",
				),
			));
			while ($link = $propertyList->fetch())
			{
				if ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_PROPERTY_TYPE"] === "N")
					$this->propertyFilter[$link["PROPERTY_ID"]] = Storage::NUMERIC;
				elseif ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_USER_TYPE"] === "DateTime")
					$this->propertyFilter[$link["PROPERTY_ID"]] = Storage::DATETIME;
				elseif ($link["IBLOCK_SECTION_PROPERTY_PROPERTY_PROPERTY_TYPE"] === "S")
					$this->propertyFilter[$link["PROPERTY_ID"]] = Storage::STRING;
				else
					$this->propertyFilter[$link["PROPERTY_ID"]] = Storage::DICTIONARY;
			}
		}
		return $this->propertyFilter;
	}
}