• Модуль: translate
  • Путь к файлу: ~/bitrix/modules/translate/lib/index/phraseindexsearch.php
  • Класс: BitrixTranslateIndexPhraseIndexSearch
  • Вызов: PhraseIndexSearch::processParams
static function processParams(array $params): array
{
	$select = [];
	$runtime = [];
	$filterIn = [];
	$filterOut = [];

	if (isset($params['filter']))
	{
		if (is_object($params['filter']))
		{
			$filterIn = clone $params['filter'];
		}
		else
		{
			$filterIn = $params['filter'];
		}
	}

	$enabledLanguages = TranslateConfig::getEnabledLanguages();
	$languageUpperKeys = array_combine($enabledLanguages, array_map('mb_strtoupper', $enabledLanguages));

	/*
	foreach ($languageUpperKeys as $langId => $langUpper)
	{
		$alias = "{$langUpper}_LANG";

		if (
			!empty($params['select']) && in_array($alias, $params['select'])
			|| isset($params['order'], $params['order'][$alias])
		)
		{
			$tblAlias = "Phrase{$alias}";
			$runtime[] = new MainORMFieldsRelationsReference(
				$tblAlias,
				IndexInternalsPhraseFts::getFtsEntityClass($langId),
				MainORMQueryJoin::on('ref.PATH_ID', '=', 'this.PATH_ID')
					->whereColumn('ref.CODE', '=', 'this.CODE')
					->where('ref.LANG_ID', '=', $langId),
				['join_type' => 'LEFT']
			);
		}
	}
	*/

	if (!isset($filterIn['PHRASE_ENTRY']))
	{
		$filterIn['PHRASE_ENTRY'] = [];
	}
	if (!isset($filterIn['CODE_ENTRY']))
	{
		$filterIn['CODE_ENTRY'] = [];
	}

	// top folder
	if (!empty($filterIn['PATH']))
	{
		$topIndexPath = IndexPathIndex::loadByPath($filterIn['PATH']);
		if ($topIndexPath instanceof IndexPathIndex)
		{
			$filterOut['=PATH.DESCENDANTS.PARENT_ID'] = $topIndexPath->getId();//ancestor
		}
		unset($filterIn['PATH']);
	}

	// search by code
	if (!empty($filterIn['INCLUDE_PHRASE_CODES']))
	{
		$codes = preg_split("/[rnt,; ]+/".BX_UTF_PCRE_MODIFIER, $filterIn['INCLUDE_PHRASE_CODES']);
		$codes = array_filter($codes);
		if (count($codes) > 0)
		{
			$useLike = false;
			foreach ($codes as $code)
			{
				if (mb_strpos($code, '%') !== false)
				{
					$useLike = true;
					break;
				}
			}
			if ($useLike)
			{
				$filterOut['=%CODE'] = $codes;
			}
			else
			{
				$filterOut['=CODE'] = $codes;
			}
		}
		unset($filterIn['INCLUDE_PHRASE_CODES']);
	}
	if (!empty($filterIn['EXCLUDE_PHRASE_CODES']))
	{
		$codes = preg_split("/[rnt,; ]+/".BX_UTF_PCRE_MODIFIER, $filterIn['EXCLUDE_PHRASE_CODES']);
		$codes = array_filter($codes);
		if (count($codes) > 0)
		{
			$useLike = false;
			foreach ($codes as $code)
			{
				if (mb_strpos($code, '%') !== false)
				{
					$useLike = true;
					break;
				}
			}
			if ($useLike)
			{
				$filterOut["!=%CODE"] = $codes;
			}
			else
			{
				$filterOut["!=CODE"] = $codes;
			}
		}
		unset($filterIn['EXCLUDE_PHRASE_CODES']);
	}

	if (!empty($filterIn['PHRASE_CODE']))
	{
		if (in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn['CODE_ENTRY']))
		{
			if (in_array(self::SEARCH_METHOD_EQUAL, $filterIn['CODE_ENTRY']))
			{
				$filterOut["=CODE"] = $filterIn['PHRASE_CODE'];
			}
			elseif (in_array(self::SEARCH_METHOD_START_WITH, $filterIn['CODE_ENTRY']))
			{
				$filterOut["=%CODE"] = $filterIn['PHRASE_CODE'].'%';
			}
			elseif (in_array(self::SEARCH_METHOD_END_WITH, $filterIn['CODE_ENTRY']))
			{
				$filterOut["=%CODE"] = '%'.$filterIn['PHRASE_CODE'];
			}
			else
			{
				$filterOut["=%CODE"] = '%'.$filterIn['PHRASE_CODE'].'%';
			}
		}
		else
		{
			$runtime[] = new MainORMFieldsExpressionField('CODE_UPPER', 'UPPER(%s)', 'CODE');
			if (in_array(self::SEARCH_METHOD_EQUAL, $filterIn['CODE_ENTRY']))
			{
				$filterOut['=CODE_UPPER'] = mb_strtoupper($filterIn['PHRASE_CODE']);
			}
			elseif (in_array(self::SEARCH_METHOD_START_WITH, $filterIn['CODE_ENTRY']))
			{
				$filterOut['=%CODE_UPPER'] = mb_strtoupper($filterIn['PHRASE_CODE']).'%';
			}
			elseif (in_array(self::SEARCH_METHOD_END_WITH, $filterIn['CODE_ENTRY']))
			{
				$filterOut['=%CODE_UPPER'] = '%'.mb_strtoupper($filterIn['PHRASE_CODE']);
			}
			else
			{
				$filterOut['=%CODE_UPPER'] = '%'.mb_strtoupper($filterIn['PHRASE_CODE']).'%';
			}
		}
	}
	unset($filterIn['PHRASE_CODE'], $filterIn['CODE_ENTRY']);

	$runtime[] = new MainORMFieldsRelationsReference(
		'PATH',
		IndexInternalsPathIndexTable::class,
		MainORMQueryJoin::on('ref.ID', '=', 'this.PATH_ID'),
		['join_type' => 'INNER']
	);

	$filterOut['=PATH.IS_DIR'] = 'N';

	$replaceLangId = function(&$val)
	{
		$val = TranslateIOPath::replaceLangId($val, '#LANG_ID#');
	};
	$trimSlash = function(&$val)
	{
		if (mb_strpos($val, '%') === false)
		{
			if (TranslateIOPath::isPhpFile($val))
			{
				$val = '/'. trim($val, '/');
			}
			else
			{
				$val = '/'. trim($val, '/'). '/%';
			}
		}
	};

	if (!empty($filterIn['INCLUDE_PATHS']))
	{
		$pathIncludes = preg_split("/[rnt,; ]+/".BX_UTF_PCRE_MODIFIER, $filterIn['INCLUDE_PATHS']);
		$pathIncludes = array_filter($pathIncludes);
		if (count($pathIncludes) > 0)
		{
			$pathPathIncludes = [];
			$pathNameIncludes = [];
			foreach ($pathIncludes as $testPath)
			{
				if (!empty($testPath) && trim($testPath) !== '')
				{
					if (mb_strpos($testPath, '/') === false)
					{
						$pathNameIncludes[] = $testPath;
					}
					else
					{
						$pathPathIncludes[] = $testPath;
					}
				}
			}
			if (count($pathNameIncludes) > 0 && count($pathPathIncludes) > 0)
			{
				array_walk($pathNameIncludes, $replaceLangId);
				array_walk($pathPathIncludes, $replaceLangId);
				array_walk($pathPathIncludes, $trimSlash);
				$filterOut[] = [
					'LOGIC' => 'OR',
					'%=PATH.NAME' => $pathNameIncludes,
					'%=PATH.PATH' => $pathPathIncludes,
				];
			}
			elseif (count($pathNameIncludes) > 0)
			{
				array_walk($pathNameIncludes, $replaceLangId);
				$filterOut[] = [
					'LOGIC' => 'OR',
					'%=PATH.NAME' => $pathNameIncludes,
					'%=PATH.PATH' => $pathNameIncludes,
				];
			}
			elseif (count($pathPathIncludes) > 0)
			{
				array_walk($pathPathIncludes, $replaceLangId);
				array_walk($pathPathIncludes, $trimSlash);
				$filterOut['%=PATH.PATH'] = $pathPathIncludes;
			}
		}
		unset($testPath, $pathIncludes, $pathNameIncludes, $pathPathIncludes);
	}
	if (!empty($filterIn['EXCLUDE_PATHS']))
	{
		$pathExcludes = preg_split("/[rnt,; ]+/".BX_UTF_PCRE_MODIFIER, $filterIn['EXCLUDE_PATHS']);
		$pathExcludes = array_filter($pathExcludes);
		if (count($pathExcludes) > 0)
		{
			$pathPathExcludes = [];
			$pathNameExcludes = [];
			foreach ($pathExcludes as $testPath)
			{
				if (!empty($testPath) && trim($testPath) !== '')
				{
					if (mb_strpos($testPath, '/') === false)
					{
						$pathNameExcludes[] = $testPath;
					}
					else
					{
						$pathPathExcludes[] = $testPath;
					}
				}
			}
			if (count($pathNameExcludes) > 0 && count($pathPathExcludes) > 0)
			{
				array_walk($pathNameExcludes, $replaceLangId);
				array_walk($pathPathExcludes, $replaceLangId);
				array_walk($pathPathExcludes, $trimSlash);
				$filterOut[] = [
					'LOGIC' => 'AND',
					'!=%PATH.NAME' => $pathNameExcludes,
					'!=%PATH.PATH' => $pathPathExcludes,
				];
			}
			elseif (count($pathNameExcludes) > 0)
			{
				array_walk($pathNameExcludes, $replaceLangId);
				$filterOut[] = [
					'LOGIC' => 'AND',
					'!=%PATH.NAME' => $pathNameExcludes,
					'!=%PATH.PATH' => $pathNameExcludes,
				];
			}
			elseif (count($pathPathExcludes) > 0)
			{
				array_walk($pathPathExcludes, $replaceLangId);
				array_walk($pathPathExcludes, $trimSlash);
				$filterOut["!=%PATH.PATH"] = $pathPathExcludes;
			}
		}
		unset($testPath, $pathExcludes, $pathPathExcludes, $pathNameExcludes);
	}
	unset($filterIn['INCLUDE_PATHS'], $filterIn['EXCLUDE_PATHS']);

	// search by phrase
	if (!empty($filterIn['PHRASE_TEXT']))
	{
		$langId = !empty($filterIn['LANGUAGE_ID']) ? $filterIn['LANGUAGE_ID'] : Loc::getCurrentLang();

		$langUpper = $languageUpperKeys[$langId];
		$tbl = "{$langUpper}_LNG";
		$alias = "{$langUpper}_LANG";
		$tblAlias = "{$tbl}.PHRASE_{$langUpper}";
		$fieldAlias = "{$tblAlias}.PHRASE";

		$runtime[] = new MainORMFieldsRelationsReference(
			$tbl,
			IndexInternalsPhraseIndexTable::class,
			MainORMQueryJoin::on('ref.PATH_ID', '=', 'this.PATH_ID')
				->whereColumn('ref.CODE', '=', 'this.CODE')
				->where('ref.LANG_ID', '=', $langId),
			['join_type' => 'INNER']
		);

		$select[$alias] = "{$tblAlias}.PHRASE";
		$select["{$langUpper}_FILE_ID"] = "{$tblAlias}.FILE_ID";

		$exact = in_array(self::SEARCH_METHOD_EXACT, $filterIn['PHRASE_ENTRY']);
		$entry = in_array(self::SEARCH_METHOD_ENTRY_WORD, $filterIn['PHRASE_ENTRY']);
		$case = in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn['PHRASE_ENTRY']);
		$start = in_array(self::SEARCH_METHOD_START_WITH, $filterIn['PHRASE_ENTRY']);
		$end = in_array(self::SEARCH_METHOD_END_WITH, $filterIn['PHRASE_ENTRY']);
		$equal = in_array(self::SEARCH_METHOD_EQUAL, $filterIn['PHRASE_ENTRY']);

		if ($exact)
		{
			$phraseSearch = ["={$fieldAlias}" => $filterIn['PHRASE_TEXT']];
		}
		else
		{
			$sqlHelper = MainApplication::getConnection()->getSqlHelper();
			$str = $sqlHelper->forSql($filterIn['PHRASE_TEXT']);

			$phraseSearch = [
				'LOGIC' => 'AND'
			];

			// use fulltext index to help like operator
			$minLengthFulltextWorld = self::getFullTextMinLength();
			$fulltextIndexSearchStr = self::prepareTextForFulltextSearch($filterIn['PHRASE_TEXT']);
			if (mb_strlen($fulltextIndexSearchStr) > $minLengthFulltextWorld)
			{
				if ($entry)
				{
					// identical full text match
					// MATCH(PHRASE) AGAINST ('+smth' IN BOOLEAN MODE)
					$phraseSearch["*={$fieldAlias}"] = $fulltextIndexSearchStr;
				}
				else
				{
					// use fulltext index to help like operator
					// partial full text match
					// MATCH(PHRASE) AGAINST ('+smth*' IN BOOLEAN MODE)
					$phraseSearch["*{$fieldAlias}"] = $fulltextIndexSearchStr;
				}
			}

			if ($equal)
			{
				$likeStr = "{$str}";
			}
			elseif ($start)
			{
				$likeStr = "{$str}%%";
			}
			elseif ($end)
			{
				$likeStr = "%%{$str}";
			}
			elseif ($entry)
			{
				$likeStr = "%%{$str}%%";
			}
			else
			{
				$likeStr = "%%" . preg_replace("/W+/i" . BX_UTF_PCRE_MODIFIER, "%%", $str) . "%%";
			}

			if (self::allowICURegularExpression())
			{
				$regStr = preg_replace("/s+/i" . BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $str);
			}
			else
			{
				if ($case)
				{
					$regStr = preg_replace("/s+/i" . BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $str);
				}
				else
				{
					$regStr = '';
					$regChars = ['?', '*', '|', '[', ']', '(', ')', '-', '+', '.'];
					for ($p = 0, $len = TranslateTextStringHelper::getLength($str); $p < $len; $p++)
					{
						$c0 = TranslateTextStringHelper::getSubstring($str, $p, 1);
						if (in_array($c0, $regChars))
						{
							$regStr .= "\\" . $c0;
							continue;
						}
						$c1 = TranslateTextStringHelper::changeCaseToLower($c0);
						$c2 = TranslateTextStringHelper::changeCaseToUpper($c0);
						if ($c0 != $c1)
						{
							$regStr .= '(' . $c0 . '|' . $c1 . '){1}';
						}
						elseif ($c0 != $c2)
						{
							$regStr .= '(' . $c0 . '|' . $c2 . '){1}';
						}
						else
						{
							$regStr .= $c0;
						}
					}
					$regStr = preg_replace("/s+/i" . BX_UTF_PCRE_MODIFIER, '[[:blank:]]+', $regStr);
				}
			}

			$regExpStart = '';
			$regExpEnd = '';
			if (preg_match("/^[[:alnum:]]+/i" . BX_UTF_PCRE_MODIFIER, $str))
			{
				if (self::allowICURegularExpression())
				{
					$regExpStart = '\\b';
				}
				else
				{
					$regExpStart = '[[:<:]]';
				}
			}
			if (preg_match("/[[:alnum:]]+$/i" . BX_UTF_PCRE_MODIFIER, $str))
			{
				if (self::allowICURegularExpression())
				{
					$regExpEnd = '\\b';
				}
				else
				{
					$regExpEnd = '[[:>:]]';
				}
			}

			// Exact word match
			if ($equal)
			{
				$regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
			}
			elseif ($start)
			{
				$regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}";
			}
			elseif ($end)
			{
				$regStr = "{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
			}
			elseif ($entry)
			{
				$regStr = "[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
			}

			// regexp binary mode works not exactly we want using like binary to fix it
			$binarySensitive = $case ? 'BINARY' : '';
			$runtime[] =
				new MainORMFieldsExpressionField(
					'PHRASE_LIKE',
					"CASE WHEN %s LIKE {$binarySensitive} '{$likeStr}' THEN 1 ELSE 0 END",
					"{$fieldAlias}"
				);
			$phraseSearch["=PHRASE_LIKE"] = 1;

			if (self::allowICURegularExpression())
			{
				// c meaning case-sensitive matching
				// i meaning case-insensitive matching
				$regCaseSensitive = $case ? 'c' : 'i';
				$runtime[] =
					new MainORMFieldsExpressionField(
						'PHRASE_REGEXP',
						"REGEXP_LIKE(%s, '{$regStr}', '{$regCaseSensitive}')",
						"{$fieldAlias}"
					);
			}
			else
			{
				$runtime[] =
					new MainORMFieldsExpressionField(
						'PHRASE_REGEXP',
						"CASE WHEN %s REGEXP '{$regStr}' THEN 1 ELSE 0 END",
						"{$fieldAlias}"
					);
			}
			$phraseSearch["=PHRASE_REGEXP"] = 1;
		}

		$filterOut[] = $phraseSearch;
	}
	unset($filterIn['PHRASE_ENTRY'], $filterIn['PHRASE_TEXT'], $filterIn['LANGUAGE_ID']);


	if (!empty($filterIn['FILE_NAME']))
	{
		$filterOut["=%PATH.NAME"] = '%'. $filterIn['FILE_NAME']. '%';
		unset($filterIn['FILE_NAME']);
	}
	if (!empty($filterIn['FOLDER_NAME']))
	{
		$filterOut['=%PATH.PATH'] = '%/'. $filterIn['FOLDER_NAME']. '/%';
		unset($filterIn['FOLDER_NAME']);
	}

	foreach ($filterIn as $key => $value)
	{
		if (in_array($key, ['tabId', 'FILTER_ID', 'PRESET_ID', 'FILTER_APPLIED', 'FIND']))
		{
			continue;
		}
		$filterOut[$key] = $value;
	}

	return [$select, $runtime, $filterOut];
}