28 private const NON_FTS = [
'th',
'zh-cn',
'zh-tw',
'ja',
'ko'];
38 [, $runtime, ] = self::processParams(
$params);
41 foreach ($runtime as $field)
58 [, $runtime,
$filter] = self::processParams([
'filter' => $filterIn]);
61 foreach ($runtime as $field)
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,
104 $executeParams[
'order'] =
$params[
'order'];
108 $executeParams[
'offset'] =
$params[
'offset'];
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();
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',
187 if (\is_object(
$params[
'filter']))
189 $filterIn = clone
$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";
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,; ]+/u", $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,; ]+/u", $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))
372 if (!empty($filterIn[
'INCLUDE_PATHS']))
374 $pathIncludes = \preg_split(
"/[\r\n\t,; ]+/u", $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,
407 \array_walk($pathNameIncludes, $replaceLangId);
410 '%=PATH.NAME' => $pathNameIncludes,
411 '%=PATH.PATH' => $pathNameIncludes,
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,; ]+/u", $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,
458 \array_walk($pathNameExcludes, $replaceLangId);
461 '!=%PATH.NAME' => $pathNameExcludes,
462 '!=%PATH.PATH' => $pathNameExcludes,
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]))
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+/iu",
"%%",
$str) .
"%%";
609 $likeStr =
"%%" . \preg_replace(
"/\W+/iu",
"%%",
$str) .
"%%";
611 $likeStr = str_replace(
'%%%%',
'%%', $likeStr);
613 if (self::allowICURegularExpression())
615 $regStr = \preg_replace(
"/\s+/iu",
'[[:blank:]]+',
$str);
621 $regStr = \preg_replace(
"/\s+/iu",
'[[:blank:]]+',
$str);
626 $regChars = [
'?',
'*',
'|',
'[',
']',
'(',
')',
'-',
'+',
'.'];
627 for (
$p = 0, $len = Translate\Text\StringHelper::getLength(
$str);
$p < $len;
$p++)
629 $c0 = Translate\Text\StringHelper::getSubstring(
$str,
$p, 1);
630 if (\in_array($c0, $regChars))
632 $regStr .=
"\\\\" . $c0;
635 $c1 = Translate\Text\StringHelper::changeCaseToLower($c0);
636 $c2 = Translate\Text\StringHelper::changeCaseToUpper($c0);
639 $regStr .=
'(' . $c0 .
'|' . $c1 .
'){1}';
643 $regStr .=
'(' . $c0 .
'|' . $c2 .
'){1}';
650 $regStr = \preg_replace(
"/\s+/iu",
'[[:blank:]]+', $regStr);
653 $regStr = str_replace(
'%',
'%%', $regStr);
657 if (\preg_match(
"/^[[:alnum:]]+/iu",
$str))
659 if (self::allowICURegularExpression())
661 $regExpStart =
'\\\\b';
665 $regExpStart =
'[[:<:]]';
668 if (\preg_match(
"/[[:alnum:]]+$/iu",
$str))
670 if (self::allowICURegularExpression())
672 $regExpEnd =
'\\\\b';
676 $regExpEnd =
'[[:>:]]';
683 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
687 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}";
691 $regStr =
"{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
695 $regStr =
"[[:blank:]]*{$regExpStart}({$regStr}){$regExpEnd}[[:blank:]]*";
699 $binarySensitive = $case ?
'BINARY' :
'';
700 $runtime[] = (
new ExpressionField(
702 "CASE WHEN %s LIKE {$binarySensitive} '{$likeStr}' THEN 1 ELSE 0 END",
704 ))->configureValueType(IntegerField::class);
705 $phraseSearch[
"=PHRASE_LIKE"] = 1;
707 if (self::allowICURegularExpression())
711 $regCaseSensitive = $case ?
'c' :
'i';
712 $runtime[] = (
new ExpressionField(
714 "REGEXP_LIKE(%s, '{$regStr}', '{$regCaseSensitive}')",
716 ))->configureValueType(IntegerField::class);
720 $runtime[] = (
new ExpressionField(
722 "CASE WHEN %s REGEXP '{$regStr}' THEN 1 ELSE 0 END",
724 ))->configureValueType(IntegerField::class);
726 $phraseSearch[
"=PHRASE_REGEXP"] = 1;
729 $filterOut[] = $phraseSearch;
731 unset($filterIn[
'PHRASE_ENTRY'], $filterIn[
'PHRASE_TEXT'], $filterIn[
'LANGUAGE_ID']);
734 if (!empty($filterIn[
'FILE_NAME']))
736 $filterOut[
"=%PATH.NAME"] =
'%'. $filterIn[
'FILE_NAME'].
'%';
737 unset($filterIn[
'FILE_NAME']);
739 if (!empty($filterIn[
'FOLDER_NAME']))
741 $filterOut[
'=%PATH.PATH'] =
'%/'. $filterIn[
'FOLDER_NAME'].
'/%';
742 unset($filterIn[
'FOLDER_NAME']);
745 foreach ($filterIn as
$key => $value)
747 if (\in_array(
$key, [
'tabId',
'FILTER_ID',
'PRESET_ID',
'FILTER_APPLIED',
'FIND']))
754 return [
$select, $runtime, $filterOut];
771 'select' => [
'ID',
'CODE'],
772 'filter' => [
'=ACTIVE' =>
'Y'],
776 if (!empty($row[
'CODE']))
778 $cache[mb_strtolower($row[
'ID'])] = trim(mb_strtolower($row[
'CODE']));
783 return isset($cache[$langId]) && in_array($cache[$langId], self::NON_FTS);
795 if ($allowICURE ===
null)
797 $majorVersion = \mb_substr(Application::getConnection()->getVersion()[0], 0, 1);
798 $allowICURE = (int)$majorVersion >= 8;
815 $text = \preg_replace(
"/\b\w{1,{$minLengthFulltextWorld}}\b/iu",
'',
$text);
818 foreach ($stopWorlds as $stopWorld)
820 $text = \preg_replace(
"/\b{$stopWorld}\b/iu",
'',
$text);
837 if ($available ===
null)
840 $cache = Cache::createInstance();
841 if ($cache->initCache(3600,
'translate::isInnodbEngine'))
843 $available = (bool)$cache->getVars();
845 elseif ($cache->startDataCache())
849 $check = Application::getConnection()->query(
850 "SHOW TABLE STATUS WHERE Name = 'b_translate_phrase' AND Engine = 'InnoDB'"
859 $cache->endDataCache((
int)$available);
875 if ($worldList ===
null)
879 $cache = Cache::createInstance();
880 if ($cache->initCache(3600,
'translate::FullTextStopWords'))
882 $worldList = $cache->getVars();
884 elseif ($cache->startDataCache())
888 if (self::isInnodbEngine())
890 $res = Application::getConnection()->query(
891 "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD"
893 while ($row =
$res->fetch())
895 if (mb_strlen($row[
'value']) > $minLengthFulltextWorld)
897 $worldList[] = $row[
'value'];
904 $cache->endDataCache($worldList);
920 static $fullTextMinLength;
921 if ($fullTextMinLength ===
null)
923 $fullTextMinLength = 4;
924 $cache = Cache::createInstance();
925 if ($cache->initCache(3600,
'translate::FullTextMinLength'))
927 $fullTextMinLength = $cache->getVars();
929 elseif ($cache->startDataCache())
931 if (self::isInnodbEngine())
933 $var =
'innodb_ft_min_token_size';
937 $var =
'ft_min_word_len';
941 $res = Application::getConnection()->query(
"SHOW VARIABLES LIKE '{$var}'");
942 if ($row =
$res->fetch())
944 $fullTextMinLength = (int)$row[
'Value'];
949 $cache->endDataCache($fullTextMinLength);
953 return $fullTextMinLength;