28 private const NON_FTS = [
'th',
'zh-cn',
'zh-tw',
'ja',
'ko'];
38 [, $runtime, ] = self::processParams($params);
41 foreach ($runtime as $field)
43 $entity->addField($field);
56 public static function getCount(array $filterIn): int
58 [, $runtime, $filter] = self::processParams([
'filter' => $filterIn]);
61 foreach ($runtime as $field)
63 $entity->addField($field);
72 $result = $query->exec()->fetch();
74 return (
int)$result[
'CNT'];
86 [$select, $runtime, $filter] = self::processParams($params);
89 'select' => \array_merge(
91 'PATH_ID' =>
'PATH_ID',
92 'PHRASE_CODE' =>
'CODE',
93 'FILE_PATH' =>
'PATH.PATH',
94 'TITLE' =>
'PATH.NAME',
98 'runtime' => $runtime,
102 if (isset($params[
'order']))
104 $executeParams[
'order'] = $params[
'order'];
106 if (isset($params[
'offset']))
108 $executeParams[
'offset'] = $params[
'offset'];
110 if (isset($params[
'limit']))
112 $executeParams[
'limit'] = $params[
'limit'];
114 if (isset($params[
'count_total']))
116 $executeParams[
'count_total'] =
true;
121 return $entityClass::getList($executeParams);
136 $class = $entity->getDataClass();
149 if ($entity ===
null)
152 ->setSelect([
'PATH_ID',
'CODE'])
153 ->setGroup([
'PATH_ID',
'CODE']);
155 $entity = Main\ORM\Entity::compileEntity(
156 'PathPhraseIndexReference',
158 'PATH_ID' => [
'data_type' =>
'string'],
159 'CODE' => [
'data_type' =>
'string'],
162 'table_name' =>
'('.$subQuery->getQuery().
')',
163 'namespace' => __NAMESPACE__.
'\\Internals',
177 private static function processParams(array $params): array
185 if (isset($params[
'filter']))
187 if (\is_object($params[
'filter']))
189 $filterIn = clone $params[
'filter'];
193 $filterIn = $params[
'filter'];
197 $enabledLanguages = Translate\Config::getEnabledLanguages();
198 $languageUpperKeys = \array_combine($enabledLanguages, \array_map(
'mb_strtoupper', $enabledLanguages));
200 foreach ($languageUpperKeys as $langId => $langUpper)
202 $tbl =
"{$langUpper}_LNG";
203 $alias =
"{$langUpper}_LANG";
206 !empty($params[
'select']) && in_array($alias, $params[
'select'])
207 || isset($params[
'order'], $params[
'order'][$alias])
210 $i = count($runtime);
211 $runtimeInx[$tbl] = $i;
212 $runtime[$i] =
new Main\ORM\Fields\Relations\Reference(
214 Index\Internals\PhraseFts::getFtsEntityClass($langId),
215 Main\ORM\Query\Join::on(
'ref.PATH_ID',
'=',
'this.PATH_ID')
216 ->whereColumn(
'ref.CODE',
'=',
'this.CODE'),
217 [
'join_type' =>
'LEFT']
220 $runtimeInx[$alias] = $i++;
221 $runtime[$i] =
new ExpressionField($alias,
'%s',
"{$tbl}.PHRASE");
226 if (!isset($filterIn[
'PHRASE_ENTRY']))
228 $filterIn[
'PHRASE_ENTRY'] = [];
230 if (!isset($filterIn[
'CODE_ENTRY']))
232 $filterIn[
'CODE_ENTRY'] = [];
236 if (!empty($filterIn[
'PATH']))
238 $topIndexPath = Index\PathIndex::loadByPath($filterIn[
'PATH']);
239 if ($topIndexPath instanceof Index\PathIndex)
241 $filterOut[
'=PATH.DESCENDANTS.PARENT_ID'] = $topIndexPath->getId();
243 unset($filterIn[
'PATH']);
247 if (!empty($filterIn[
'INCLUDE_PHRASE_CODES']))
249 $codes = \preg_split(
"/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn[
'INCLUDE_PHRASE_CODES']);
250 $codes = \array_filter($codes);
251 if (\count($codes) > 0)
254 foreach ($codes as $code)
256 if (\mb_strpos($code,
'%') !==
false)
264 $filterOut[
'=%CODE'] = $codes;
268 $filterOut[
'=CODE'] = $codes;
271 unset($filterIn[
'INCLUDE_PHRASE_CODES']);
273 if (!empty($filterIn[
'EXCLUDE_PHRASE_CODES']))
275 $codes = \preg_split(
"/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn[
'EXCLUDE_PHRASE_CODES']);
276 $codes = \array_filter($codes);
277 if (\count($codes) > 0)
280 foreach ($codes as $code)
282 if (\mb_strpos($code,
'%') !==
false)
290 $filterOut[
"!=%CODE"] = $codes;
294 $filterOut[
"!=CODE"] = $codes;
297 unset($filterIn[
'EXCLUDE_PHRASE_CODES']);
300 if (!empty($filterIn[
'PHRASE_CODE']))
302 if (\in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn[
'CODE_ENTRY']))
304 if (\in_array(self::SEARCH_METHOD_EQUAL, $filterIn[
'CODE_ENTRY']))
306 $filterOut[
"=CODE"] = $filterIn[
'PHRASE_CODE'];
308 elseif (\in_array(self::SEARCH_METHOD_START_WITH, $filterIn[
'CODE_ENTRY']))
310 $filterOut[
"=%CODE"] = $filterIn[
'PHRASE_CODE'].
'%';
312 elseif (\in_array(self::SEARCH_METHOD_END_WITH, $filterIn[
'CODE_ENTRY']))
314 $filterOut[
"=%CODE"] =
'%'.$filterIn[
'PHRASE_CODE'];
318 $filterOut[
"=%CODE"] =
'%'.$filterIn[
'PHRASE_CODE'].
'%';
323 $runtime[] =
new ExpressionField(
'CODE_UPPER',
'UPPER(CONVERT(%s USING latin1))',
'CODE');
324 if (\in_array(self::SEARCH_METHOD_EQUAL, $filterIn[
'CODE_ENTRY']))
326 $filterOut[
'=CODE_UPPER'] = \mb_strtoupper($filterIn[
'PHRASE_CODE']);
328 elseif (\in_array(self::SEARCH_METHOD_START_WITH, $filterIn[
'CODE_ENTRY']))
330 $filterOut[
'=%CODE_UPPER'] = \mb_strtoupper($filterIn[
'PHRASE_CODE']).
'%';
332 elseif (\in_array(self::SEARCH_METHOD_END_WITH, $filterIn[
'CODE_ENTRY']))
334 $filterOut[
'=%CODE_UPPER'] =
'%'.\mb_strtoupper($filterIn[
'PHRASE_CODE']);
338 $filterOut[
'=%CODE_UPPER'] =
'%'.\mb_strtoupper($filterIn[
'PHRASE_CODE']).
'%';
342 unset($filterIn[
'PHRASE_CODE'], $filterIn[
'CODE_ENTRY']);
344 $runtime[] =
new Main\ORM\Fields\Relations\Reference(
346 Index\Internals\PathIndexTable::class,
347 Main\ORM\Query\Join::on(
'ref.ID',
'=',
'this.PATH_ID'),
348 [
'join_type' =>
'INNER']
351 $filterOut[
'=PATH.IS_DIR'] =
'N';
353 $replaceLangId =
function(&$val)
355 $val = Translate\IO\Path::replaceLangId($val,
'#LANG_ID#');
357 $trimSlash =
function(&$val)
359 if (\mb_strpos($val,
'%') ===
false)
361 if (Translate\
IO\Path::isPhpFile($val))
363 $val =
'/'. \trim($val,
'/');
367 $val =
'/'. \trim($val,
'/').
'/%';
372 if (!empty($filterIn[
'INCLUDE_PATHS']))
374 $pathIncludes = \preg_split(
"/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn[
'INCLUDE_PATHS']);
375 $pathIncludes = \array_filter($pathIncludes);
376 if (\count($pathIncludes) > 0)
378 $pathPathIncludes = [];
379 $pathNameIncludes = [];
380 foreach ($pathIncludes as $testPath)
382 if (!empty($testPath) && \trim($testPath) !==
'')
384 if (\mb_strpos($testPath,
'/') ===
false)
386 $pathNameIncludes[] = $testPath;
390 $pathPathIncludes[] = $testPath;
394 if (\count($pathNameIncludes) > 0 && \count($pathPathIncludes) > 0)
396 \array_walk($pathNameIncludes, $replaceLangId);
397 \array_walk($pathPathIncludes, $replaceLangId);
398 \array_walk($pathPathIncludes, $trimSlash);
401 '%=PATH.NAME' => $pathNameIncludes,
402 '%=PATH.PATH' => $pathPathIncludes,
405 elseif (\count($pathNameIncludes) > 0)
407 \array_walk($pathNameIncludes, $replaceLangId);
410 '%=PATH.NAME' => $pathNameIncludes,
411 '%=PATH.PATH' => $pathNameIncludes,
414 elseif (\count($pathPathIncludes) > 0)
416 \array_walk($pathPathIncludes, $replaceLangId);
417 \array_walk($pathPathIncludes, $trimSlash);
418 $filterOut[
'%=PATH.PATH'] = $pathPathIncludes;
421 unset($testPath, $pathIncludes, $pathNameIncludes, $pathPathIncludes);
423 if (!empty($filterIn[
'EXCLUDE_PATHS']))
425 $pathExcludes = \preg_split(
"/[\r\n\t,; ]+/".\BX_UTF_PCRE_MODIFIER, $filterIn[
'EXCLUDE_PATHS']);
426 $pathExcludes = \array_filter($pathExcludes);
427 if (\count($pathExcludes) > 0)
429 $pathPathExcludes = [];
430 $pathNameExcludes = [];
431 foreach ($pathExcludes as $testPath)
433 if (!empty($testPath) && \trim($testPath) !==
'')
435 if (\mb_strpos($testPath,
'/') ===
false)
437 $pathNameExcludes[] = $testPath;
441 $pathPathExcludes[] = $testPath;
445 if (\count($pathNameExcludes) > 0 && \count($pathPathExcludes) > 0)
447 \array_walk($pathNameExcludes, $replaceLangId);
448 \array_walk($pathPathExcludes, $replaceLangId);
449 \array_walk($pathPathExcludes, $trimSlash);
452 '!=%PATH.NAME' => $pathNameExcludes,
453 '!=%PATH.PATH' => $pathPathExcludes,
456 elseif (\count($pathNameExcludes) > 0)
458 \array_walk($pathNameExcludes, $replaceLangId);
461 '!=%PATH.NAME' => $pathNameExcludes,
462 '!=%PATH.PATH' => $pathNameExcludes,
465 elseif (\count($pathPathExcludes) > 0)
467 \array_walk($pathPathExcludes, $replaceLangId);
468 \array_walk($pathPathExcludes, $trimSlash);
469 $filterOut[
"!=%PATH.PATH"] = $pathPathExcludes;
472 unset($testPath, $pathExcludes, $pathPathExcludes, $pathNameExcludes);
474 unset($filterIn[
'INCLUDE_PATHS'], $filterIn[
'EXCLUDE_PATHS']);
477 if (!empty($filterIn[
'PHRASE_TEXT']))
479 $langId = !empty($filterIn[
'LANGUAGE_ID']) ? $filterIn[
'LANGUAGE_ID'] :
Loc::getCurrentLang();
481 $langUpper = $languageUpperKeys[$langId];
482 $tbl =
"{$langUpper}_LNG";
483 $alias =
"{$langUpper}_LANG";
484 $fieldAlias =
"{$tbl}.PHRASE";
497 $i = isset($runtimeInx[$tbl]) ? $runtimeInx[$tbl] : count($runtime);
499 $runtime[$i] =
new Main\ORM\Fields\Relations\Reference(
501 Index\Internals\PhraseFts::getFtsEntityClass($langId),
502 Main\ORM\Query\Join::on(
'ref.PATH_ID',
'=',
'this.PATH_ID')
503 ->whereColumn(
'ref.CODE',
'=',
'this.CODE'),
504 [
'join_type' =>
'INNER']
507 if (!isset($runtimeInx[$alias]))
509 $select[$alias] = $fieldAlias;
511 $select[
"{$langUpper}_FILE_ID"] =
"{$tbl}.FILE_ID";
513 $exact = \in_array(self::SEARCH_METHOD_EXACT, $filterIn[
'PHRASE_ENTRY']);
514 $entry = \in_array(self::SEARCH_METHOD_ENTRY_WORD, $filterIn[
'PHRASE_ENTRY']);
515 $case = \in_array(self::SEARCH_METHOD_CASE_SENSITIVE, $filterIn[
'PHRASE_ENTRY']);
516 $start = \in_array(self::SEARCH_METHOD_START_WITH, $filterIn[
'PHRASE_ENTRY']);
517 $end = \in_array(self::SEARCH_METHOD_END_WITH, $filterIn[
'PHRASE_ENTRY']);
518 $equal = \in_array(self::SEARCH_METHOD_EQUAL, $filterIn[
'PHRASE_ENTRY']);
522 $phraseSearch = [
"={$fieldAlias}" => $filterIn[
'PHRASE_TEXT']];
526 $sqlHelper = Main\Application::getConnection()->getSqlHelper();
527 $str = $sqlHelper->forSql($filterIn[
'PHRASE_TEXT']);
535 if (!self::disallowFtsIndex($langId))
539 if (\mb_strlen($fulltextIndexSearchStr) > $minLengthFulltextWorld)
574 $phraseSearch[
"*={$fieldAlias}"] = $fulltextIndexSearchStr;
581 $phraseSearch[
"*{$fieldAlias}"] = $fulltextIndexSearchStr;
592 $likeStr =
"{$str}%%";
596 $likeStr =
"%%{$str}";
600 $likeStr =
"%%{$str}%%";
602 elseif (self::disallowFtsIndex($langId))
605 $likeStr =
"%%" . \preg_replace(
"/\s+/i" . \BX_UTF_PCRE_MODIFIER,
"%%", $str) .
"%%";
609 $likeStr =
"%%" . \preg_replace(
"/\W+/i" . \BX_UTF_PCRE_MODIFIER,
"%%", $str) .
"%%";
612 if (self::allowICURegularExpression())
614 $regStr = \preg_replace(
"/\s+/i" . \BX_UTF_PCRE_MODIFIER,
'[[:blank:]]+', $str);
620 $regStr = \preg_replace(
"/\s+/i" . \BX_UTF_PCRE_MODIFIER,
'[[:blank:]]+', $str);
625 $regChars = [
'?',
'*',
'|',
'[',
']',
'(',
')',
'-',
'+',
'.'];
626 for ($p = 0, $len = Translate\Text\StringHelper::getLength($str); $p < $len; $p++)
628 $c0 = Translate\Text\StringHelper::getSubstring($str, $p, 1);
629 if (\in_array($c0, $regChars))
631 $regStr .=
"\\\\" . $c0;
634 $c1 = Translate\Text\StringHelper::changeCaseToLower($c0);
635 $c2 = Translate\Text\StringHelper::changeCaseToUpper($c0);
638 $regStr .=
'(' . $c0 .
'|' . $c1 .
'){1}';
642 $regStr .=
'(' . $c0 .
'|' . $c2 .
'){1}';
649 $regStr = \preg_replace(
"/\s+/i" . \BX_UTF_PCRE_MODIFIER,
'[[:blank:]]+', $regStr);
655 if (\preg_match(
"/^[[:alnum:]]+/i" . \BX_UTF_PCRE_MODIFIER, $str))
657 if (self::allowICURegularExpression())
659 $regExpStart =
'\\\\b';
663 $regExpStart =
'[[:<:]]';
666 if (\preg_match(
"/[[:alnum:]]+$/i" . \BX_UTF_PCRE_MODIFIER, $str))
668 if (self::allowICURegularExpression())
670 $regExpEnd =
'\\\\b';
674 $regExpEnd =
'[[:>:]]';
681 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
685 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}";
689 $regStr =
"{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
693 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
697 $binarySensitive = $case ?
'BINARY' :
'';
698 $runtime[] = (
new ExpressionField(
700 "CASE WHEN %s LIKE {$binarySensitive} '{$likeStr}' THEN 1 ELSE 0 END",
702 ))->configureValueType(IntegerField::class);
703 $phraseSearch[
"=PHRASE_LIKE"] = 1;
705 if (self::allowICURegularExpression())
709 $regCaseSensitive = $case ?
'c' :
'i';
710 $runtime[] = (
new ExpressionField(
712 "REGEXP_LIKE(%s, '{$regStr}', '{$regCaseSensitive}')",
714 ))->configureValueType(IntegerField::class);
718 $runtime[] = (
new ExpressionField(
720 "CASE WHEN %s REGEXP '{$regStr}' THEN 1 ELSE 0 END",
722 ))->configureValueType(IntegerField::class);
724 $phraseSearch[
"=PHRASE_REGEXP"] = 1;
727 $filterOut[] = $phraseSearch;
729 unset($filterIn[
'PHRASE_ENTRY'], $filterIn[
'PHRASE_TEXT'], $filterIn[
'LANGUAGE_ID']);
732 if (!empty($filterIn[
'FILE_NAME']))
734 $filterOut[
"=%PATH.NAME"] =
'%'. $filterIn[
'FILE_NAME'].
'%';
735 unset($filterIn[
'FILE_NAME']);
737 if (!empty($filterIn[
'FOLDER_NAME']))
739 $filterOut[
'=%PATH.PATH'] =
'%/'. $filterIn[
'FOLDER_NAME'].
'/%';
740 unset($filterIn[
'FOLDER_NAME']);
743 foreach ($filterIn as $key => $value)
745 if (\in_array($key, [
'tabId',
'FILTER_ID',
'PRESET_ID',
'FILTER_APPLIED',
'FIND']))
749 $filterOut[$key] = $value;
752 return [$select, $runtime, $filterOut];
768 $iterator = Main\Localization\LanguageTable::getList([
769 'select' => [
'ID',
'CODE'],
770 'filter' => [
'=ACTIVE' =>
'Y'],
772 while ($row = $iterator->fetch())
774 if (!empty($row[
'CODE']))
776 $cache[mb_strtolower($row[
'ID'])] = trim(mb_strtolower($row[
'CODE']));
781 return isset($cache[$langId]) && in_array($cache[$langId], self::NON_FTS);
793 if ($allowICURE ===
null)
796 $allowICURE = (int)$majorVersion >= 8;
813 $text = \preg_replace(
"/\b\w{1,{$minLengthFulltextWorld}}\b/i".\BX_UTF_PCRE_MODIFIER,
'', $text);
816 foreach ($stopWorlds as $stopWorld)
818 $text = \preg_replace(
"/\b{$stopWorld}\b/i".\BX_UTF_PCRE_MODIFIER,
'', $text);
821 $text = \preg_replace(
"/^\W+/i".\BX_UTF_PCRE_MODIFIER,
'', $text);
822 $text = \preg_replace(
"/\W+$/i".\BX_UTF_PCRE_MODIFIER,
'', $text);
823 $text = \preg_replace(
"/\W+/i".\BX_UTF_PCRE_MODIFIER,
' ', $text);
835 if ($available ===
null)
838 $cache = Cache::createInstance();
839 if ($cache->initCache(3600,
'translate::isInnodbEngine'))
841 $available = (bool)$cache->getVars();
843 elseif ($cache->startDataCache())
848 "SHOW TABLE STATUS WHERE Name = 'b_translate_phrase' AND Engine = 'InnoDB'"
857 $cache->endDataCache((
int)$available);
873 if ($worldList ===
null)
877 $cache = Cache::createInstance();
878 if ($cache->initCache(3600,
'translate::FullTextStopWords'))
880 $worldList = $cache->getVars();
882 elseif ($cache->startDataCache())
886 if (self::isInnodbEngine())
889 "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD"
891 while ($row = $res->fetch())
893 if (mb_strlen($row[
'value']) > $minLengthFulltextWorld)
895 $worldList[] = $row[
'value'];
902 $cache->endDataCache($worldList);
918 static $fullTextMinLength;
919 if ($fullTextMinLength ===
null)
921 $fullTextMinLength = 4;
922 $cache = Cache::createInstance();
923 if ($cache->initCache(3600,
'translate::FullTextMinLength'))
925 $fullTextMinLength = $cache->getVars();
927 elseif ($cache->startDataCache())
929 if (self::isInnodbEngine())
931 $var =
'innodb_ft_min_token_size';
935 $var =
'ft_min_word_len';
940 if ($row = $res->fetch())
942 $fullTextMinLength = (int)$row[
'Value'];
947 $cache->endDataCache($fullTextMinLength);
951 return $fullTextMinLength;